]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
Equivalent to V1.19.99.1
authorRichard P. Curnow <rc@rc0.org.uk>
Thu, 19 Jan 2006 21:34:28 +0000 (21:34 +0000)
committerRichard P. Curnow <rc@rc0.org.uk>
Thu, 19 Jan 2006 21:34:28 +0000 (21:34 +0000)
This is a verbatim copy of the files at that stage of the repository that was
built from the CVS import.  It allows future development to see a bit of recent
history, but without carrying around the baggage going back to 1997.  If that
is really required, git grafts can be used.

110 files changed:
COPYING [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
acquire.c [new file with mode: 0644]
acquire.h [new file with mode: 0644]
addressing.h [new file with mode: 0644]
addrfilt.c [new file with mode: 0644]
addrfilt.h [new file with mode: 0644]
broadcast.c [new file with mode: 0644]
broadcast.h [new file with mode: 0644]
build_kit [new file with mode: 0755]
candm.h [new file with mode: 0644]
chrony.1 [new file with mode: 0644]
chrony.conf.5 [new file with mode: 0644]
chrony.lsm [new file with mode: 0644]
chrony.spec.sample [new file with mode: 0644]
chrony.texi [new file with mode: 0644]
chronyc.1 [new file with mode: 0644]
chronyd.8 [new file with mode: 0644]
client.c [new file with mode: 0644]
clientlog.c [new file with mode: 0644]
clientlog.h [new file with mode: 0644]
cmdmon.c [new file with mode: 0644]
cmdmon.h [new file with mode: 0644]
cmdparse.c [new file with mode: 0644]
cmdparse.h [new file with mode: 0644]
conf.c [new file with mode: 0644]
conf.h [new file with mode: 0644]
configure [new file with mode: 0755]
contrib/DNSchrony/COPYING [new file with mode: 0644]
contrib/DNSchrony/DNSchrony.pl [new file with mode: 0755]
contrib/DNSchrony/DNSchronyADD [new file with mode: 0755]
contrib/DNSchrony/DNSchronyDELETE [new file with mode: 0755]
contrib/DNSchrony/DNSchronyUPDATE [new file with mode: 0755]
contrib/DNSchrony/README [new file with mode: 0644]
contrib/DNSchrony/ip-up.local [new file with mode: 0644]
contrib/andrew_bishop_1 [new file with mode: 0644]
contrib/andrew_bishop_2 [new file with mode: 0644]
contrib/erik_bryer_1 [new file with mode: 0644]
contrib/ken_gillett_1 [new file with mode: 0644]
contrib/stephan_boettcher_1 [new file with mode: 0644]
contrib/wolfgang_weisselberg1 [new file with mode: 0644]
examples/chrony.conf.example [new file with mode: 0644]
examples/chrony.keys.example [new file with mode: 0644]
faq.txt [new file with mode: 0644]
faqgen.pl [new file with mode: 0644]
getdate.c [new file with mode: 0644]
getdate.h [new file with mode: 0644]
keys.c [new file with mode: 0644]
keys.h [new file with mode: 0644]
local.c [new file with mode: 0644]
local.h [new file with mode: 0644]
localp.h [new file with mode: 0644]
logging.c [new file with mode: 0644]
logging.h [new file with mode: 0644]
main.c [new file with mode: 0644]
main.h [new file with mode: 0644]
manual.c [new file with mode: 0644]
manual.h [new file with mode: 0644]
md5.c [new file with mode: 0644]
md5.h [new file with mode: 0644]
memory.h [new file with mode: 0644]
mkdirpp.c [new file with mode: 0644]
mkdirpp.h [new file with mode: 0644]
nameserv.c [new file with mode: 0644]
nameserv.h [new file with mode: 0644]
ntp.h [new file with mode: 0644]
ntp_core.c [new file with mode: 0644]
ntp_core.h [new file with mode: 0644]
ntp_io.c [new file with mode: 0644]
ntp_io.h [new file with mode: 0644]
ntp_sources.c [new file with mode: 0644]
ntp_sources.h [new file with mode: 0644]
pktlength.c [new file with mode: 0644]
pktlength.h [new file with mode: 0644]
reference.c [new file with mode: 0644]
reference.h [new file with mode: 0644]
regress.c [new file with mode: 0644]
regress.h [new file with mode: 0644]
reports.h [new file with mode: 0644]
rtc.c [new file with mode: 0644]
rtc.h [new file with mode: 0644]
rtc_linux.c [new file with mode: 0644]
rtc_linux.h [new file with mode: 0644]
sched.c [new file with mode: 0644]
sched.h [new file with mode: 0644]
sources.c [new file with mode: 0644]
sources.h [new file with mode: 0644]
sourcestats.c [new file with mode: 0644]
sourcestats.h [new file with mode: 0644]
srcparams.h [new file with mode: 0644]
strerror.c [new file with mode: 0644]
sys.c [new file with mode: 0644]
sys.h [new file with mode: 0644]
sys_linux.c [new file with mode: 0644]
sys_linux.h [new file with mode: 0644]
sys_netbsd.c [new file with mode: 0644]
sys_netbsd.h [new file with mode: 0644]
sys_solaris.c [new file with mode: 0644]
sys_solaris.h [new file with mode: 0644]
sys_sunos.c [new file with mode: 0644]
sys_sunos.h [new file with mode: 0644]
sysincl.h [new file with mode: 0644]
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]
version.txt [new file with mode: 0644]
wrap_adjtimex.c [new file with mode: 0644]
wrap_adjtimex.h [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..916d1f0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+       Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..2299780
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,89 @@
+The software is distributed as source code which has to be compiled.
+
+PARTS OF THE SOFTWARE ARE HIGHLY SYSTEM-SPECIFIC AND NON-PORTABLE.
+UNLESS YOU ARE RUNNING A SUPPORTED SYSTEM, BE PREPARED FOR SOME
+PROGRAMMING!
+
+After unpacking the source code, change directory into it, and type
+
+    ./configure
+
+This is a shell script that automatically determines the system type.
+There is a single optional parameter, --prefix which indicates the
+directory tree where the software should be installed.  For example,
+
+    ./configure --prefix=/opt/free
+
+will install the chronyd daemon into /opt/free/sbin and the chronyc
+control program into /opt/free/bin.  The default value for the prefix
+is /usr/local.
+
+The configure script assumes you want to use gcc as your compiler.
+If you want to use a different compiler, you can configure this way:
+
+    CC=cc CFLAGS=-O ./configure --prefix=/opt/free
+
+for Bourne-family shells, or
+    
+    setenv CC cc
+    setenv CFLAGS -O
+    ./configure --prefix=/opt/free
+
+for C-family shells.
+
+If the software cannot (yet) be built on your system, an error message
+will be shown.  Otherwise, the files `options.h' and `Makefile' will
+be generated.
+
+By default, chronyc will be built to make use of the readline library.  If you
+don't want this, specify the --disable-readline flag to configure.  If you have
+readline and/or ncurses installed in a non-standard location, please refer to
+the chrony.txt file for information.
+
+Now type
+
+    make
+
+to build the programs.
+
+If you want to build the manual in plain text, HTML and info versions, type
+
+    make docs
+
+Once the programs have been successfully compiled, they need to be
+installed in their target locations.  This step normally needs to be
+performed by the superuser, and requires the following command to be
+entered.
+
+    make install
+
+This will install the binaries, plain text manual and manpages.
+
+To install the HTML and info versions of the manual as well, enter the command
+
+    make install-docs
+
+If you want chrony to appear in the top level info directory listing, you need
+to run the install-info command manually after this step.  install-info takes 2
+arguments.  The first is the path to the chrony.info file you have just
+installed.  This will be the argument you gave to --prefix when you configured
+(/usr/local by default), with /info/chrony.info on the end.  The second
+argument is the location of the file called 'dir'.  This will typically be
+/usr/info/dir.  So the typical command line would be
+
+    install-info /usr/local/info/chrony.info /usr/info/dir
+
+Now that the software is successfully installed, the next step is to
+set up a configuration file.  The contents of this depend on the
+network environment in which the computer operates.  Typical scenarios
+are described in the manual.  The simplest case is for a computer with
+a permanent Internet connection - suppose you want to use the NTP
+server ntp1.foobar.com as your time reference.  You would create an
+/etc/chrony.conf file containing
+
+    server ntp1.foobar.com
+    driftfile /etc/chrony.drift
+
+and then run /usr/local/sbin/chronyd.
+
+
diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..0a91caf
--- /dev/null
@@ -0,0 +1,161 @@
+##################################################
+#
+# $Header: /cvs/src/chrony/Makefile.in,v 1.47 2003/04/07 21:43:54 richard Exp $
+#
+# =======================================================================
+#
+# chronyd/chronyc - Programs for keeping computer clocks accurate.
+# 
+# Copyright (C) Richard P. Curnow  1997-2003
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+# 
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+#
+# =======================================================================
+#
+# Makefile template
+
+INSTALL_PREFIX=@INSTALL_PREFIX@
+MANDIR=@MANDIR@
+INFODIR=@INFODIR@
+
+CC = @CC@
+CCWARNFLAGS = @CCWARNFLAGS@
+OPTFLAGS = @CFLAGS@ @EXTRA_DEFS@
+
+DESTDIR=
+
+OBJS = util.o sched.o regress.o local.o \
+       sys.o main.o ntp_io.o ntp_core.o ntp_sources.o \
+       sources.o sourcestats.o reference.o \
+       logging.o conf.o cmdmon.o md5.o keys.o \
+       nameserv.o acquire.o manual.o addrfilt.o \
+       cmdparse.o mkdirpp.o rtc.o pktlength.o clientlog.o \
+       broadcast.o
+
+EXTRA_OBJS=@EXTRA_OBJECTS@
+
+CLI_OBJS = client.o md5.o nameserv.o getdate.o cmdparse.o \
+           pktlength.o
+
+SRCS = $(patsubst %.o,%.c,$(OBJS))
+EXTRA_SRCS = $(patsubst %.o,%.c,$(EXTRA_OBJS))
+
+CLI_SRCS = $(patsubst %.o,%.c,$(CLI_OBJS))
+
+LIBS = @LIBS@
+
+EXTRA_LIBS=@EXTRA_LIBS@
+EXTRA_CLI_LIBS=@EXTRA_CLI_LIBS@
+
+DEFS=@SYSDEFS@
+
+CFLAGS = $(CCWARNFLAGS) $(OPTFLAGS)
+
+# Until we have a main procedure we can link, just build object files
+# to test compilation
+
+all : chronyd chronyc
+
+chronyd : $(OBJS) $(EXTRA_OBJS)
+       $(CC) $(OPTFLAGS) -o chronyd $(OBJS) $(EXTRA_OBJS) $(LIBS) $(EXTRA_LIBS)
+
+chronyc : $(CLI_OBJS)
+       $(CC) $(OPTFLAGS) -o chronyc $(CLI_OBJS) @READLINE_LINK@ $(LIBS) $(EXTRA_CLI_LIBS)
+
+client.o : client.c
+       $(CC) $(CFLAGS) $(DEFS) @READLINE_COMPILE@ -c $<
+
+.depend : 
+       gcc -MM $(SRCS) $(EXTRA_SRCS) > .depend
+
+distclean :
+       -rm -f *.o *.s chronyc chronyd core options.h Makefile *~
+
+clean :
+       -rm -f *.o *.s chronyc chronyd core *~
+
+version.h : version.txt
+       sed -e 's/[$$]Name: \(.*\) [$$]/\1/;' < version.txt > version.h
+
+
+# For install, don't use the install command, because its switches
+# seem to vary between systems.
+
+install: chronyd chronyc
+       [ -d $(DESTDIR)$(INSTALL_PREFIX) ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)
+       [ -d $(DESTDIR)$(INSTALL_PREFIX)/sbin ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/sbin
+       [ -d $(DESTDIR)$(INSTALL_PREFIX)/bin ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/bin
+       [ -d $(DESTDIR)$(INSTALL_PREFIX)/doc ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/doc
+       [ -d $(DESTDIR)$(MANDIR)/man1 ] || mkdir -p $(DESTDIR)$(MANDIR)/man1
+       [ -d $(DESTDIR)$(MANDIR)/man5 ] || mkdir -p $(DESTDIR)$(MANDIR)/man5
+       [ -d $(DESTDIR)$(MANDIR)/man8 ] || mkdir -p $(DESTDIR)$(MANDIR)/man8
+       [ -d $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony
+       cp chronyd $(DESTDIR)$(INSTALL_PREFIX)/sbin/chronyd
+       chmod 555 $(DESTDIR)$(INSTALL_PREFIX)/sbin/chronyd
+       cp chronyc $(DESTDIR)$(INSTALL_PREFIX)/bin/chronyc
+       chmod 555 $(DESTDIR)$(INSTALL_PREFIX)/bin/chronyc
+       cp chrony.txt $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt
+       chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt
+       cp COPYING $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/COPYING
+       chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/COPYING
+       cp README $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/README
+       chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/README
+       cp chrony.1 $(DESTDIR)$(MANDIR)/man1
+       chmod 444 $(DESTDIR)$(MANDIR)/man1/chrony.1
+       cp chronyc.1 $(DESTDIR)$(MANDIR)/man1
+       chmod 444 $(DESTDIR)$(MANDIR)/man1/chronyc.1
+       cp chronyd.8 $(DESTDIR)$(MANDIR)/man8
+       chmod 444 $(DESTDIR)$(MANDIR)/man8/chronyd.8
+       cp chrony.conf.5 $(DESTDIR)$(MANDIR)/man5
+       chmod 444 $(DESTDIR)$(MANDIR)/man5/chrony.conf.5
+
+%.o : %.c
+       $(CC) $(CFLAGS) $(DEFS) -c $<
+
+%.s : %.c
+       $(CC) $(CFLAGS) $(DEFS) -S $<
+
+main.o logging.o client.o : version.h
+
+# makeinfo v4 required to generate plain text and html
+MAKEINFO:=makeinfo
+
+install-docs : docs
+       [ -d $(DESTDIR)$(INSTALL_PREFIX)/doc ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/doc
+       cp chrony.txt $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt
+       chown root $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt
+       chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt
+       cp chrony.html $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.html
+       chown root $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.html
+       chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.html
+       [ -d $(DESTDIR)$(INFODIR) ] || mkdir -p $(DESTDIR)$(INFODIR)
+       cp chrony.info* $(DESTDIR)$(INFODIR)
+       chown root $(DESTDIR)$(INFODIR)/chrony.info*
+       chmod 444 $(DESTDIR)$(INFODIR)/chrony.info*
+
+docs : chrony.txt chrony.html chrony.info
+
+chrony.txt : chrony.texi
+       $(MAKEINFO) --no-headers --number-sections -o chrony.txt chrony.texi
+
+chrony.html : chrony.texi
+       $(MAKEINFO) --no-split --html --number-sections -o chrony.html chrony.texi
+
+chrony.info : chrony.texi
+       $(MAKEINFO) chrony.texi
+
+# This is only relevant if you're maintaining the website!
+faq.php : faq.txt faqgen.pl
+       perl faqgen.pl < faq.txt > faq.php
+
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..aeee89b
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,267 @@
+New in version 1.20
+===================
+
+* Many small tidy-ups and security improvements
+* Improve documentation (RTC support in post 2.0 kernels)
+* Remove trailing \n from syslog messages
+* Syslog messages now include IP and port number when packet cannot be sent.
+* Added the "acquisitionport" directive.  (Kalle Olavi Niemitalo)
+* Use uname(2) instead of /proc/version to get kernel version.
+* Merge support for Linux on Alpha
+* Merge support for 64bit architectures
+* Don't link -lm if it's not needed
+* Fix Solaris build (broken by 64bit change)
+* Fix for Linux on SH-5 (floating point denorm handling).
+* Add detection of Linux 2.5
+* Allow arbitrary value of HZ in Linux kernel
+
+New in version 1.19
+===================
+
+* Auto-detect kernel's timer interrupt rate (so-called 'HZ') when chronyd
+  starts instead of relying on compiled-in value.
+* Fix 2 bugs in function that creates the directory for the log and dump files.
+* Amended webpage URL and contact details.
+* Generate more informative syslog messages before exiting on failed
+  assertions.
+* Fix bugs in clamping code for the tick value used when slewing a large
+  offset.
+* Don't chown files to root during install (should be pointless, and makes RPM
+  building awkward as ordinary user.)
+* Include chrony.spec file for building RPMs
+
+New in version 1.18
+===================
+* Amend homepage and mailing list information to chrony.sunsite.dk
+* Delete pidfile on exit from chronyd.
+* Improvements to readline interface to chronyc
+* Only generate syslog message when synchronisation is initially lost (instead
+  of on every failed synchronisation attempt)
+* Use double fork approach when initialising daemon.
+* More things in contrib directory.
+* New options to help package builders: --infodir/--mandir for configure, and
+  DESTDIR=xxx for make.  (See section 2.2 of chrony.txt for details).
+* Changed the wording of the messages generated by mailonchange and logchange
+  directives.
+
+New in version 1.17
+===================
+* Port to NetBSD
+* Configuration supports Linux on PPC
+* Fix compilation warnings
+* Several documentation improvements
+* Bundled manpages (taken from the 'missing manpages project')
+* Cope with lack of bzero function for Solaris 2.3 systems
+* Store chronyd's pid in a file (default /var/run/chronyd.pid) and check if
+  chronyd may already be running when starting up.  New pidfile directive in
+  configuration file.
+* Any size subnet is now allowed in allow and deny commands.  (Example:
+  6.7.8/20 or 6.7.8.x/20 (any x) mean a 20 bit subnet).
+* The environment variables CC and CFLAGS passed to configure can now be used
+  to select the compiler and optimisation/debug options to use
+* Write syslog messages when chronyd loses synchronisation.
+* Print GPL text when chronyc is run.
+* Add NTP broadcast server capability (new broadcast directive).
+* Add 'auto_offline' option to server/peer (conf file) or add server/peer (via
+  chronyc).
+* Add 'activity' command to chronyc, to report how many servers/peers are
+  currently online/offline.
+* Fix long-standing bug with how the system time quantum was calculated.
+* Include support for systems with HZ!=100 (HZ is the timer interrupt
+  frequency).
+* Include example chrony.conf and chrony.keys files (examples subdirectory).
+* Include support for readline in chronyc.
+
+New in version 1.16.1
+=====================
+* Fix compilation problem on Linux 2.4.13 (spinlock.h / spinlock_t)
+
+New in version 1.16
+===================
+* More informative captions for 'sources' and 'sourcestats' commands in chronyc
+  (use 'sources -v' and 'sourcestats -v' to get them).
+* Correct behaviour for Solaris versions>=2.6 (dosynctodr not required on these
+  versions.)
+* Remove some compiler warnings (Solaris)
+* If last line of keys file doesn't have end-of-line, don't truncate final
+  character of that key.
+* Change timestamp format used in logfiles to make it fully numeric (to aid
+  importing data into spreadsheets etc)
+* Minor documentation updates and improvements.
+
+New in version 1.15
+===================
+* Add contributed change to 'configure' to support Solaris 2.8 on x86
+* Workaround for assertion failure that arises if two received packets occur
+  close together. (Still need to find out why this happens at all.)
+* Hopefully fix problem where fast slewing was incompatible with machines
+  that have a large background drift rate (=> tick value went out of range
+  for adjtimex() on Linux.)
+* Fix rtc_linux.c compile problems with 2.4.x kernel include files.
+* Include support for RTC device not being at /dev/rtc (new rtcdevice directive
+  in configuration file).
+* Include support for restricting network interfaces for commands (new
+  bindcmdaddress directive in configuration file)
+* Fix potential linking fault in pktlength.c (use of CROAK macro replaced by
+  normal assert).
+* Add some material on bug reporting + contributing to the chrony.texi file
+* Made the chrony.texi file "Vim6-friendly" (removed xrefs on @node lines,
+  added folding markers to chapters + sections.)
+* Switched over to GPL for the licence
+
+New in version 1.14
+===================
+* Fix compilation for certain other Linux distributions (including Mandrake
+  7.1)
+
+New in version 1.13
+===================
+* Fixed compilation problems on Redhat/SuSE installations with recent 2.2.x
+  kernels.
+* Minor tidy-ups and documentation enhancements.
+* Add support for Linux 2.4 kernels
+
+New in version 1.12
+===================
+
+* Trial fix for long-standing bug in Linux RTC estimator when system time is
+  slewed.
+* Fix bug in chronyc if -h is specified without a hostname
+* Fixes to logging various error conditions when operating in daemon mode.
+* More stuff under contrib/
+* Changes to README file (e.g. about the new chrony-users mailing list)
+
+New in version 1.11a
+====================
+
+* Minor changes to contact details
+* Minor changes to installation details (chrony subdirectory under doc/)
+
+New in version 1.11
+===================
+
+* Improve robustness of installation procedure
+* Tidy up documenation and contact details
+* Distribute manual as .txt rather than as .ps
+* Add -n option to chronyc to work with numeric IP addresses rather than
+  names.
+* Add material in contrib subdirectory
+* Improve robustness of handling drift file and RTC coefficients file
+* Improve robustness of regression algorithm
+
+New in version 1.1
+==================
+
+Bug fixes
+---------
+
+* Made linear regression more resistant to rounding errors (old one
+  occasionally generated negative variances which made everything go
+  haywire).  Trap infinite or 'not-a-number' values being used to
+  alter system clock to increase robustness further.
+
+Other changes/Enhancements
+--------------------------
+
+* Support for Linux 2.1 and 2.2 kernels
+
+* New command 'makestep' in chronyc to immediately jump the system
+  time to match the NTP estimated time (Linux only) - a response to
+  systems booting an hour wrong after summertime/wintertime changes,
+  due to RTCs running on local time.  Needs extending to Sun driver
+  files too.
+
+* New directives 'logchange' and 'mailonchange' to log to syslog or
+  email to a specific address respectively if chronyd detects a clock
+  offset exceeding a defined threshold.
+
+* Added capability to log all client/peer NTP accesses and command
+  accesses (can be turned off with conf file directive 'noclientlog').
+  Added 'clients' command to chronyc to display this data.
+
+* Improved manual mode to use robust regression rather than 2 point
+  fit.
+
+* Added 'manual list' and 'manual delete' commands to chronyc to
+  allow display of entered timestamps and discretionary deletion of
+  outliers.
+
+* If host goes unsynchronised the dummy IP address 0.0.0.0 is detected
+  to avoid attempting a reverse name lookup (to stop dial on demand IP
+  links from being started)
+
+* Changed chronyc/chronyd protocol so messages are now all variable
+  length.  Saves on network bandwidth particularly for large replies
+  from chronyd to chronyc (to support the clients command).
+
+* Added bindaddress directive to configuration file, to give
+  additional control over limiting which hosts can access the local
+  server.
+
+* Groundwork done for a port to Windows NT to compile with Cygwin
+  toolkit.  chronyc works (to monitor another host).  sys_winnt.c
+  needs finishing to use NT clock control API.  Program structure
+  needs adapting to use Windows NT service functions, so it can be
+  started at boot time.  Hopefully a Windows NT / Cygwin guru with
+  some spare time can take this port over :-)
+
+New in version 1.02
+===================
+
+Bug fixes
+---------
+
+* Fix error messages in chronyc if daemon is not reachable.
+
+* Fix config file problem for 'allow all' and 'deny all' without a
+  trailing machine address.
+
+* Remove fatal failed assertion if command socket cannot be read from
+  in daemon.
+
+* Rewrote timezone handling for Linux real time clock, following
+  various reported problems related to daylight saving.
+
+Other changes/Enhancements
+--------------------------
+
+* Configure script recognizes BSD/386 and uses SunOS 4.1 driver for
+  it.
+
+* Log files now print date as day-month-year rather than as a day
+  number.  Milliseconds removed from timestamps of logged data.
+  Banners included in file to give meanings of columns.
+
+* Only do 1 initial step (followed by a trimming slew) when
+  initialising from RTC on Linux (previously did 2 steps).
+
+New in version 1.01
+===================
+
+Bug fixes
+---------
+
+* Handle timezone of RTC correctly with respect to daylight saving
+  time
+
+* Syntax check the chronyc 'local' command properly
+
+* Fixed assertion failed fault in median finder (used by RTC
+  regression fitting)
+
+Other changes/Enhancements
+--------------------------
+
+* Log selection of new NTP reference source to syslog.
+
+* Don't zero-pad IP address fields
+
+* Add new command to chronyc to allow logfiles to be cycled.
+
+* Extend allow/deny directive syntax in configuration file to so
+  directive can apply to all hosts on the Internet.
+
+* Tidy up printout of timestamps to make it clear they are in UTC
+
+* Make 'configure' check the processor type as well as the operating
+  system.
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..4846ec8
--- /dev/null
+++ b/README
@@ -0,0 +1,243 @@
+This is the README for chrony.
+
+What is chrony?
+===============
+Chrony is a pair of programs for maintaining the accuracy of computer
+clocks.
+
+chronyd is a (background) daemon program that can be started at boot
+time.  This does most of the work.
+
+chronyc is a command-line interface program which can be used to
+monitor chronyd's performance and to change various operating
+parateters whilst it is running.
+
+chronyd's main function is to obtain measurements of the true (UTC)
+time from one of several sources, and correct the system clock
+accordingly.  It also works out the rate at which the system clock
+gains or loses time and uses this information to keep it accurate
+between measurements from the reference.
+
+The reference time can be derived from either Network Time Protocol
+(NTP) servers (preferred), or wristwatch-and-keyboard (via chronyc).
+The main source of information about the Network Time Protocol is
+http://www.eecis.udel.edu/~ntp.
+
+It is designed so that it can work on computers which only have
+intermittent access to reference sources, for example computers which
+use a dial-up account to access the Internet.  Of course, it will work
+on computers with permanent connections too.
+
+In addition, the Linux 2.0.x (for x >= 32), 2.2.x and 2.3.x versions
+can monitor the system's real time clock performance, so the system
+can maintain accurate time even across reboots.
+
+Typical accuracies available between 2 machines are
+
+On an ethernet LAN : 100-200 microseconds, often much better
+On a V32bis dial-up modem connection : 10's of milliseconds (from one
+session to the next)
+
+chronyd can also operate as an RFC1305-compatible NTP server and peer.
+
+
+What will chrony run on?
+========================
+
+Chrony can be successfully built and run on
+
+1. Linux v1.2.13, v2.0.x, 2.1.x (partially), 2.2.x, 2.3.x, 2.4.x (i386).
+Real time clock support is limited to 2.0.32 onwards and to 2.2, 2.3 and
+2.4 series only.  PowerPC is also known to be supported.
+
+2. Solaris 2.5/2.5.1/2.6/2.7/2.8 (various platforms) 
+
+3. SunOS 4.1.4 (Sparc 2 and Sparc 20)
+
+4. BSD/386 v1.1 has been reported to work using the SunOS 4.1 driver.
+
+5. NetBSD.
+
+Any other system will require a porting exercise.  You would need to
+start from one of the existing system-specific drivers and look into
+the quirks of certain system calls and the kernel on your target
+system.  (This is described in the manual).
+
+How do I set it up?
+===================
+
+The file INSTALL gives instructions.  On supported systems the
+compilation process should be automatic.
+
+You will need an ANSI C compiler -- gcc is recommended.  Versions
+2.7.2/2.7.2.2 are known to work.
+
+The manual (in texinfo and text formats) describes how to set the
+software up for the less straightforward cases.
+
+What documentation is there?
+============================
+
+A manual is supplied in Texinfo format (chrony.texi) and
+ready-formatted plain text (chrony.txt) in the distribution.
+
+There is also information available on the chrony web pages, accessible
+through the URL 
+
+    http://chrony.sunsite.dk/
+
+What can chrony not do?
+=======================
+
+Compared to the `reference' RFC1305 implementation xntpd, chronyd does
+not support hardware reference clocks, leap seconds or broadcast
+modes.
+
+Where are new versions announced?
+=================================
+
+There is a low volume mailing list where new versions and other
+important news relating to chrony is announced.  You can join this list
+by sending mail to
+
+chrony-announce-subscribe@sunsite.dk
+
+These messages will be copied to chrony-users (see below).  I also try
+to announce new versions on Freshmeat (http://freshmeat.net/).
+
+I don't reliably announce via news any more - I don't tend to keep up
+with news as I haven't enough time.
+
+How can I get support for chrony?
+and where can I discuss new features, possible bugs etc?
+========================================================
+
+There are 3 mailing lists relating to chrony.  chrony-announce was
+mentioned above.  chrony-users is a users' discussion list, e.g. for
+general questions and answers about using chrony.  chrony-dev is a more
+technical list, e.g. for discussing how new features should be
+implemented, exchange of information between developers etc.  To
+subscribe to either of these lists, send an empty message to
+
+chrony-users-subscribe@sunsite.dk
+or
+chrony-dev-subscribe@sunsite.dk
+
+as applicable.
+
+Note that due to family commitments (a 3 year-old and a 1 year-old), I
+no longer have the time to give to supporting chrony that I once had.
+Therefore, the chrony-users list should be your main route for support,
+rather than mailing me directly.  Even if it's me that responds to your
+question on the list, at least *ALL* subscribers then benefit from
+seeing the discussion, rather than me taking up lots of time on
+supporting people on a one-to-one basis.  If you do mail me directly,
+don't be surprised if I cc: the response to the mailing list.
+
+But how can I contact the author if I need to?
+==============================================
+
+You can email me at <rc@rc0.org.uk>.  If that fails, you could try to
+find me through one of the mailing lists.  It would be nice if:
+
+- you include the word 'chrony' in the subject line (so my mail reader
+can sort my mail by topic)
+
+- you don't send complete log files, encoded binaries etc, without
+editing such material down to just the relevant bits - a few tens of
+lines at most.  (My dial-up connection handles large messages rather
+slowly ...).
+
+Acknowledgements
+================
+
+The following people have provided patches and other major contributions
+to the program :
+
+Andrew Bishop <amb@gedanken.demon.co.uk>
+    Fixes for bugs in logging when in daemon mode
+    Fixes for compiler warnings
+    Robustness improvements for drift file
+    Improve installation (directory checking etc)
+    Entries in contrib directory
+    Improvements to 'sources' and 'sourcestats' output from chronyc
+    Improvements to documentation
+    Investigation of required dosynctodr behaviour for various Solaris
+      versions.
+
+Stephan I. Boettcher <stephan@nevis1.columbia.edu>
+    Entries in contrib directory
+
+Erik Bryer <ebryer@spots.ab.ca>
+    Entries in contrib directory
+
+Paul Elliott <pelliott@io.com>
+    DNSchrony (in contrib directory), a tool for handling NTP servers
+    with variable IP addresses.
+
+Mike Fleetwood <mike@rockover.demon.co.uk>
+    Fixes for compiler warnings
+
+Alexander Gretencord <arutha@gmx.de>
+    Changes to installation directory system to make it easier for
+    package builders.
+
+Walter Haidinger <walter.haidinger@gmx.at>
+    Providing me with login access to a Linux installation where v1.12
+    wouldn't compile, so I could develop the fixes for v1.13.  Also, for
+    providing the disc space so I can keep an independent backup of the
+    sources.
+
+Juergen Hannken-Illjes <hannken@eis.cs.tu-bs.de>
+    Port to NetBSD
+
+John Hasler <john@dhh.gt.org>
+    Changes to support 64 bit machines (i.e. those where
+    sizeof(unsigned long) > 4)
+
+Liam Hatton <me@liamhatton.com>
+    Advice on configuring for Linux on PPC
+
+Jachym Holecek <jakym@volny.cz>
+    Patch to make Linux real time clock work with devfs
+
+Jim Knoble <jmknoble@pobox.com>
+    Fixes for compiler warnings
+
+Antti Jrvinen <costello@iki.fi>
+    Advice on configuring for BSD/386
+
+Victor Moroz <vim@prv.adlum.ru>
+    Patch to support Linux with HZ!=100
+
+Kalle Olavi Niemitalo  <tosi@stekt.oulu.fi>
+    acquisitionport support
+
+Frank Otto <sandwichmacher@web.de>
+    Handling arbitrary HZ values
+
+Andreas Piesk <apiesk@virbus.de>
+    Patch to make chronyc use the readline library if available
+
+Wolfgang Weisselberg <weissel@netcologne.de>
+    Entries in contrib directory
+
+Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+    Many robustness and security improvements
+    
+Ulrich Windl <ulrich.windl@rz.uni-regensburg.de> for the
+    Providing me with information about the Linux 2.2 kernel
+    functionality compared to 2.0.
+
+Doug Woodward <dougw@whistler.com>
+    Advice on configuring for Solaris 2.8 on x86
+
+Many other people have contributed bug reports and suggestions.  I'm
+sorry I can't identify all of you individually.
+
+Version control information
+===========================
+
+$Header: /cvs/src/chrony/README,v 1.27 2003/07/01 20:56:23 richard Exp $
+
+vim:tw=72
diff --git a/acquire.c b/acquire.c
new file mode 100644 (file)
index 0000000..26e8708
--- /dev/null
+++ b/acquire.c
@@ -0,0 +1,688 @@
+/*
+  $Header: /cvs/src/chrony/acquire.c,v 1.23 2003/03/27 23:45:47 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Processing to perform the equivalent of what ntpdate does.  That is,
+  make a rapid-fire set of measurements to a designated set of
+  sources, and step or slew the local clock to bring it into line with
+  the result.
+
+  This is kept completely separate of the main chronyd processing, by
+  using a separate socket for sending/receiving the measurement
+  packets.  That way, ntp_core.c can be kept completely independent of
+  this functionality.
+  
+  A few of the finer points of how to construct valid RFC1305 packets
+  and validate responses for this case have been cribbed from the
+  ntpdate source.
+
+  */
+
+#include "sysincl.h"
+
+#include "acquire.h"
+#include "memory.h"
+#include "sched.h"
+#include "local.h"
+#include "logging.h"
+#include "ntp.h"
+#include "util.h"
+#include "main.h"
+#include "conf.h"
+
+/* ================================================== */
+
+/* Interval between firing off the first sample to successive sources */
+#define INTER_SOURCE_START (0.2)
+
+#define MAX_SAMPLES 8
+
+#define MAX_DEAD_PROBES 4
+#define N_GOOD_SAMPLES 4
+
+#define RETRANSMISSION_TIMEOUT (1.0)
+
+typedef struct {  unsigned long ip_addr;
+  int sanity;                   /* Flag indicating whether source
+                                   looks sane or not */
+  int n_dead_probes;            /* Number of probes sent to the server
+                                   since a good one */
+  int n_samples;                /* Number of samples accumulated */
+  int n_total_samples;          /* Total number of samples received
+                                   including useless ones */
+  double offsets[MAX_SAMPLES];  /* In seconds, positive means local
+                                   clock is fast of reference */
+  double root_distances[MAX_SAMPLES]; /* in seconds */
+  double inter_lo;              /* Low end of estimated range of offset */
+  double inter_hi;              /* High end of estimated range of offset */
+
+  NTP_int64 last_tx;            /* Transmit timestamp in last packet
+                                   transmitted to source. */
+
+  int timer_running;
+  SCH_TimeoutID timeout_id;
+} SourceRecord;
+
+static SourceRecord *sources;
+static int n_sources;
+static int n_started_sources;
+static int n_completed_sources;
+
+static int init_slew_threshold = -1;
+
+static int sock_fd = -1;
+
+/* ================================================== */
+
+static void (*saved_after_hook)(void *) = NULL;
+static void *saved_after_hook_anything = NULL;
+
+/* ================================================== */
+
+typedef struct {
+  double offset;
+  enum {LO, HIGH} type;
+  int index;
+} Endpoint;
+
+typedef struct {
+  double lo;
+  double hi;
+} Interval;
+
+/* ================================================== */
+
+static void read_from_socket(void *anything);
+static void transmit_timeout(void *x);
+static void wind_up_acquisition(void);
+static void start_source_timeout_handler(void *not_used);
+
+/* ================================================== */
+
+static SCH_TimeoutID source_start_timeout_id;
+
+/* ================================================== */
+
+void
+ACQ_Initialise(void)
+{
+  return;
+}
+
+
+/* ================================================== */
+
+void
+ACQ_Finalise(void)
+{
+  return;
+}
+
+/* ================================================== */
+
+static void
+initialise_io(void)
+{
+  unsigned short port_number = CNF_GetAcquisitionPort();
+
+  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+
+  if (sock_fd < 0) {
+    LOG_FATAL(LOGF_Acquire, "Could not open socket : %s", strerror(errno));
+  }
+
+  if (port_number == 0) {
+    /* Don't bother binding this socket - we're not fussed what port
+       number it gets */
+  } else {
+    struct sockaddr_in my_addr;
+    my_addr.sin_family = AF_INET;
+    my_addr.sin_port = htons(port_number);
+    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    if (bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
+      LOG(LOGS_ERR, LOGF_Acquire, "Could not bind socket : %s\n", strerror(errno));
+      /* but keep running */
+    }
+  }
+
+  SCH_AddInputFileHandler(sock_fd, read_from_socket, NULL);
+
+}
+
+/* ================================================== */
+
+static void
+finalise_io(void)
+{
+  if (sock_fd >= 0) {
+    SCH_RemoveInputFileHandler(sock_fd);
+    close(sock_fd);
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+probe_source(SourceRecord *src)
+{
+  NTP_Packet pkt;
+  int version = 3;
+  NTP_Mode my_mode = MODE_CLIENT;
+  struct timeval cooked;
+  double local_time_err;
+  struct sockaddr_in his_addr;
+
+#if 0
+  printf("Sending probe to %08lx sent=%d samples=%d\n", src->ip_addr, src->n_probes_sent, src->n_samples);
+#endif
+
+  pkt.lvm = (((LEAP_Unsynchronised << 6) & 0xc0) |
+             ((version << 3) & 0x38) |
+             ((my_mode) & 0x7));
+
+  pkt.stratum = 0;
+  pkt.poll = 4;
+  pkt.precision = -6; /* as ntpdate */
+  pkt.root_delay = double_to_int32(1.0); /* 1 second */
+  pkt.root_dispersion = double_to_int32(1.0); /* likewise */
+  pkt.reference_id = 0UL;
+  pkt.reference_ts.hi = 0; /* Set to 0 */
+  pkt.reference_ts.lo = 0; /* Set to 0 */
+  pkt.originate_ts.hi = 0; /* Set to 0 */
+  pkt.originate_ts.lo = 0; /* Set to 0 */
+  pkt.receive_ts.hi = 0;   /* Set to 0 */
+  pkt.receive_ts.lo = 0;   /* Set to 0 */
+
+  /* And do transmission */
+  his_addr.sin_addr.s_addr = htonl(src->ip_addr);
+  his_addr.sin_port = htons(123); /* Fixed for now */
+  his_addr.sin_family = AF_INET;
+
+  LCL_ReadCookedTime(&cooked, &local_time_err);
+  UTI_TimevalToInt64(&cooked, &pkt.transmit_ts);
+
+  if (sendto(sock_fd, (void *) &pkt, NTP_NORMAL_PACKET_SIZE,
+             0,
+             (struct sockaddr *) &his_addr, sizeof(his_addr)) < 0) {
+    LOG(LOGS_WARN, LOGF_Acquire, "Could not send to %s : %s",
+        UTI_IPToDottedQuad(src->ip_addr),
+        strerror(errno));
+  }
+
+  src->last_tx = pkt.transmit_ts;
+
+  ++(src->n_dead_probes);
+  src->timer_running = 1;
+  src->timeout_id = SCH_AddTimeoutByDelay(RETRANSMISSION_TIMEOUT, transmit_timeout, (void *) src);
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+transmit_timeout(void *x)
+{
+  SourceRecord *src = (SourceRecord *) x;
+
+  src->timer_running = 0;
+
+#if 0
+  printf("Timeout expired for server %08lx\n", src->ip_addr);
+#endif
+
+  if (src->n_dead_probes < MAX_DEAD_PROBES) {
+    probe_source(src);
+  } else {
+    /* Source has croaked or is taking too long to respond */
+    ++n_completed_sources;
+    if (n_completed_sources == n_sources) {
+      wind_up_acquisition();
+    }
+  }
+}
+
+/* ================================================== */
+
+#define MAX_STRATUM 15
+
+static void
+process_receive(NTP_Packet *msg, SourceRecord *src, struct timeval *now)
+{
+
+  unsigned long lvm;
+  int leap, version, mode;
+  double root_delay, root_dispersion;
+  double total_root_delay, total_root_dispersion, total_root_distance;
+
+  struct timeval local_orig, local_average, remote_rx, remote_tx, remote_average;
+  double remote_interval, local_interval;
+  double delta, theta, epsilon;
+  int n;
+  
+  /* Most of the checks are from ntpdate */
+
+  /* Need to do something about authentication */
+
+  lvm = msg->lvm;
+  leap = (lvm >> 6) & 0x3;
+  version = (lvm >> 3) & 0x7;
+  mode = lvm & 0x7;
+
+  if ((leap == LEAP_Unsynchronised) ||
+      (version != 3) ||
+      (mode != MODE_SERVER && mode != MODE_PASSIVE)) {
+    return;
+  }
+
+  if (msg->stratum > MAX_STRATUM) {
+    return;
+  }
+
+  /* Check whether server is responding to our last request */
+  if ((msg->originate_ts.hi != src->last_tx.hi) ||
+      (msg->originate_ts.lo != src->last_tx.lo)) {
+    return;
+  }
+
+  /* Check that the server is sane */
+  if (((msg->originate_ts.hi == 0) && (msg->originate_ts.lo == 0)) ||
+      ((msg->receive_ts.hi == 0) && (msg->receive_ts.lo) == 0)) {
+    return;
+  }
+
+  root_delay = int32_to_double(msg->root_delay);
+  root_dispersion = int32_to_double(msg->root_dispersion);
+
+  UTI_Int64ToTimeval(&src->last_tx, &local_orig);
+  UTI_Int64ToTimeval(&msg->receive_ts, &remote_rx);
+  UTI_Int64ToTimeval(&msg->transmit_ts, &remote_tx);
+  UTI_AverageDiffTimevals(&remote_rx, &remote_tx, &remote_average, &remote_interval);
+  UTI_AverageDiffTimevals(&local_orig, now, &local_average, &local_interval);
+
+  delta = local_interval - remote_interval;
+
+  /* Defined as positive if we are fast.  Note this sign convention is
+     opposite to that used in ntp_core.c */
+
+  UTI_DiffTimevalsToDouble(&theta, &local_average, &remote_average);
+  
+  /* Could work out epsilon - leave till later */
+  epsilon = 0.0;
+
+  total_root_delay = fabs(delta) + root_delay;
+  total_root_dispersion = epsilon + root_dispersion;
+  total_root_distance = 0.5 * fabs(total_root_delay) + total_root_dispersion;
+
+  n = src->n_samples;
+#if 0
+  printf("Sample %d theta=%.6f delta=%.6f root_del=%.6f root_disp=%.6f root_dist=%.6f\n",
+         n, theta, delta, total_root_delay, total_root_dispersion, total_root_distance);
+#endif
+  src->offsets[n] = theta;
+  src->root_distances[n] = total_root_distance;
+  ++(src->n_samples);
+
+}
+
+/* ================================================== */
+
+static void
+read_from_socket(void *anything)
+{
+  int status;
+  ReceiveBuffer msg;
+  struct sockaddr_in his_addr;
+  int his_addr_len;
+  int flags;
+  int message_length;
+  unsigned long remote_ip;
+  int i, ok;
+  struct timeval now;
+  double local_time_err;
+  SourceRecord *src;
+
+  flags = 0;
+  message_length = sizeof(msg);
+  his_addr_len = sizeof(his_addr);
+
+  /* Get timestamp */
+  LCL_ReadCookedTime(&now, &local_time_err);
+
+  status = recvfrom (sock_fd, (char *)&msg, message_length, flags,
+                     (struct sockaddr *) &his_addr, &his_addr_len);
+
+  if (status < 0) {
+    LOG(LOGS_WARN, LOGF_Acquire, "Error reading from socket, %s", strerror(errno));
+    return;
+  }
+  
+  remote_ip = ntohl(his_addr.sin_addr.s_addr);
+
+#if 0
+  printf("Got message from %08lx\n", remote_ip);
+#endif
+  
+  /* Find matching host */
+  ok = 0;
+  for (i=0; i<n_sources; i++) {
+    if (remote_ip == sources[i].ip_addr) {
+      ok = 1;
+      break;
+    }
+  }
+
+  if (ok) {
+
+    src = sources + i;
+    ++src->n_total_samples;
+
+    src->n_dead_probes = 0; /* reset this when we actually receive something */
+
+    /* If we got into this function, we know the retransmission timeout has not
+       expired for the source */
+    if (src->timer_running) {
+      SCH_RemoveTimeout(src->timeout_id);
+      src->timer_running = 0;
+    }
+
+    process_receive(&msg.ntp_pkt, src, &now);
+
+    /* Check if server done and requeue timeout */
+    if ((src->n_samples >= N_GOOD_SAMPLES) ||
+        (src->n_total_samples >= MAX_SAMPLES)) {
+      ++n_completed_sources;
+#if 0
+      printf("Source %08lx completed\n", src->ip_addr);
+#endif
+      if (n_completed_sources == n_sources) {
+        wind_up_acquisition();
+      }
+    } else {
+      
+      /* Send the next probe */
+      probe_source(src);
+
+    }
+  }
+
+}
+
+/* ================================================== */
+
+static void
+start_next_source(void)
+{
+  probe_source(sources + n_started_sources);
+#if 0
+  printf("Trying to start source %08lx\n", sources[n_started_sources].ip_addr);
+#endif
+  n_started_sources++;
+  
+  if (n_started_sources < n_sources) {
+    source_start_timeout_id = SCH_AddTimeoutByDelay(INTER_SOURCE_START, start_source_timeout_handler, NULL);
+  }
+}
+
+/* ================================================== */
+
+static int
+endpoint_compare(const void *a, const void *b)
+{
+  const Endpoint *aa = (const Endpoint *) a;
+  const Endpoint *bb = (const Endpoint *) b;
+
+  if (aa->offset < bb->offset) {
+    return -1;
+  } else if (aa->offset > bb->offset) {
+    return +1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static void
+process_measurements(void)
+{
+
+  SourceRecord *s;
+  Endpoint *eps;
+  int i, j;
+  int n_sane_sources;
+  double lo, hi;
+  double inter_lo, inter_hi;
+
+  int depth;
+  int best_depth;
+  int n_at_best_depth;
+  Interval *intervals;
+  double estimated_offset;
+  int index1, index2;
+
+  n_sane_sources = 0;
+
+  /* First, get a consistent interval for each source.  Those for
+     which this is not possible are considered to be insane. */
+
+  for (i=0; i<n_sources; i++) {
+    s = sources + i;
+    /* If we got no measurements, the source is insane */
+    if (s->n_samples == 0) {
+      s->sanity = 0;
+    } else {
+      s->sanity = 1; /* so far ... */
+      lo = s->offsets[0] - s->root_distances[0];
+      hi = s->offsets[0] + s->root_distances[0];
+      inter_lo = lo;
+      inter_hi = hi;
+      for (j=1; j<s->n_samples; j++) {
+        lo = s->offsets[j] - s->root_distances[j];
+        hi = s->offsets[j] + s->root_distances[j];
+        if ((inter_hi <= lo) || (inter_lo >= hi)) {
+          /* Oh dear, we won't get an interval for this source */
+          s->sanity = 0;
+          break;
+        } else {
+          inter_lo = (lo < inter_lo) ? inter_lo : lo;
+          inter_hi = (hi > inter_hi) ? inter_hi : hi;
+        }
+      }
+      if (s->sanity) {
+        s->inter_lo = inter_lo;
+        s->inter_hi = inter_hi;
+      }
+    }
+
+    if (s->sanity) {
+      ++n_sane_sources;
+    }
+
+  }
+
+  /* Now build the endpoint list, similar to the RFC1305 clock
+     selection algorithm. */
+  eps = MallocArray(Endpoint, 2*n_sane_sources);
+  intervals = MallocArray(Interval, n_sane_sources);
+
+  j = 0;
+  for (i=0; i<n_sources; i++) {
+    s = sources + i;
+    if (s->sanity) {
+      eps[j].offset = s->inter_lo;
+      eps[j].type = LO;
+      eps[j].index = i;
+      eps[j+1].offset = s->inter_hi;
+      eps[j+1].type = HIGH;
+      eps[j+1].index = i;
+      j += 2;
+    }
+  }
+
+  qsort(eps, 2*n_sane_sources, sizeof(Endpoint), endpoint_compare);
+
+  /* Now do depth searching algorithm */
+  n_at_best_depth = best_depth = depth = 0;
+  for (i=0; i<2*n_sane_sources; i++) {
+
+#if 0
+    fprintf(stderr, "Endpoint type %s source index %d [ip=%08lx] offset=%.6f\n",
+            (eps[i].type == LO) ? "LO" : "HIGH",
+            eps[i].index,
+            sources[eps[i].index].ip_addr,
+            eps[i].offset);
+#endif
+
+    switch (eps[i].type) {
+      case LO:
+        depth++;
+        if (depth > best_depth) {
+          best_depth = depth;
+          n_at_best_depth = 0;
+          intervals[0].lo = eps[i].offset;
+        } else if (depth == best_depth) {
+          intervals[n_at_best_depth].lo = eps[i].offset;
+        } else {
+          /* Nothing to do */
+        }
+
+        break;
+
+      case HIGH:
+        if (depth == best_depth) {
+          intervals[n_at_best_depth].hi = eps[i].offset;
+          n_at_best_depth++;
+        }
+
+        depth--;
+
+        break;
+
+    }
+  }
+
+  if (best_depth > 0) {
+    if ((n_at_best_depth % 2) == 1) {
+      index1 = (n_at_best_depth - 1) / 2;
+      estimated_offset = 0.5 * (intervals[index1].lo + intervals[index1].hi);
+    } else {
+      index2 = (n_at_best_depth / 2);
+      index1 = index2 - 1;
+      estimated_offset = 0.5 * (intervals[index1].lo + intervals[index2].hi);
+    }
+
+
+    /* Apply a step change to the system clock.  As per sign
+       convention in local.c and its children, a positive offset means
+       the system clock is fast of the reference, i.e. it needs to be
+       stepped backwards. */
+
+    if (fabs(estimated_offset) > (double) init_slew_threshold) {
+      LOG(LOGS_INFO, LOGF_Acquire, "System's initial offset : %.6f seconds %s of true (step)",
+          fabs(estimated_offset),
+          (estimated_offset >= 0) ? "fast" : "slow");
+      LCL_ApplyStepOffset(estimated_offset);
+    } else {
+      LOG(LOGS_INFO, LOGF_Acquire, "System's initial offset : %.6f seconds %s of true (slew)",
+          fabs(estimated_offset),
+          (estimated_offset >= 0) ? "fast" : "slow");
+      LCL_AccumulateOffset(estimated_offset);
+    }
+
+  } else {
+    LOG(LOGS_WARN, LOGF_Acquire, "No intersecting endpoints found");
+  }  
+
+  Free(intervals);
+  Free(eps);
+        
+}
+
+/* ================================================== */
+
+static void
+wind_up_acquisition(void)
+{
+
+  /* Now process measurements */
+  process_measurements();
+
+  Free(sources);
+
+  finalise_io();
+
+  if (saved_after_hook) {
+    (saved_after_hook)(saved_after_hook_anything);
+  }
+
+}
+
+/* ================================================== */
+
+static void
+start_source_timeout_handler(void *not_used)
+{
+
+  start_next_source();
+}
+
+/* ================================================== */
+
+void
+ACQ_StartAcquisition(int n, unsigned long *ip_addrs, int threshold, void (*after_hook)(void *), void *anything)
+{
+
+  int i;
+
+  saved_after_hook = after_hook;
+  saved_after_hook_anything = anything;
+
+  init_slew_threshold = threshold;
+
+  n_started_sources = 0;
+  n_completed_sources = 0;
+  n_sources = n;
+  sources = MallocArray(SourceRecord, n);
+
+  for (i=0; i<n; i++) {
+    sources[i].ip_addr = ip_addrs[i];
+    sources[i].n_samples = 0;
+    sources[i].n_total_samples = 0;
+    sources[i].n_dead_probes = 0;
+  }
+
+  initialise_io();
+
+  /* Start sampling first source */
+  start_next_source();
+
+  return;
+}
+
+/* ================================================== */
diff --git a/acquire.h b/acquire.h
new file mode 100644 (file)
index 0000000..e1e00ae
--- /dev/null
+++ b/acquire.h
@@ -0,0 +1,47 @@
+/*
+  $Header: /cvs/src/chrony/acquire.h,v 1.9 2002/02/28 23:27:07 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for acquisition module
+  */
+
+#ifndef GOT_ACQUIRE_H
+#define GOT_ACQUIRE_H
+
+typedef struct ACQ_SourceRecord *ACQ_Source;
+
+extern void ACQ_Initialise(void);
+
+extern void ACQ_Finalise(void);
+
+extern void ACQ_StartAcquisition(int n, unsigned long *ip_addrs, int init_slew_threshold,
+                                 void (*after_hook)(void *), void *anything);
+
+extern void ACQ_AccumulateSample(ACQ_Source acq_source, double offset, double root_distance);
+
+extern void ACQ_MissedSample(ACQ_Source acq_source);
+
+#endif /* GOT_ACQUIRE_H */
diff --git a/addressing.h b/addressing.h
new file mode 100644 (file)
index 0000000..aa20ed9
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+  $Header: /cvs/src/chrony/addressing.h,v 1.7 2002/02/28 23:27:08 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Types used for addressing sources etc
+  */
+
+#ifndef GOT_ADDRESSING_H
+#define GOT_ADDRESSING_H
+
+/* This type is used to represent an IPv4 address and port
+   number.  Both parts are in HOST order, NOT network order. */
+typedef struct {
+  unsigned long ip_addr;
+  unsigned short port;
+} NTP_Remote_Address;
+
+#if 0
+unsigned long NTP_IP_Address;
+#endif
+
+#endif /* GOT_ADDRESSING_H */
+
diff --git a/addrfilt.c b/addrfilt.c
new file mode 100644 (file)
index 0000000..b3c6a59
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+  $Header: /cvs/src/chrony/addrfilt.c,v 1.8 2002/02/28 23:27:08 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This module provides a set of routines for checking IP addresses
+  against a set of rules and deciding whether they are allowed or
+  disallowed.
+
+  */
+
+#include "sysincl.h"
+
+#include "addrfilt.h"
+#include "memory.h"
+
+/* Define the number of bits which are stripped off per level of
+   indirection in the tables */
+#define NBITS 4
+
+/* Define the table size */
+#define TABLE_SIZE (1UL<<NBITS)
+
+struct _TableNode;
+
+typedef struct _TableNode ExtendedTable[TABLE_SIZE];
+
+typedef enum {DENY, ALLOW, AS_PARENT} State;
+
+typedef struct _TableNode {
+  State state;
+  ExtendedTable *extended;
+} TableNode;
+
+struct ADF_AuthTableInst {
+  TableNode base;
+};
+
+/* ================================================== */
+
+inline static unsigned long
+get_subnet(unsigned long addr)
+{
+  return (addr >> (32-NBITS)) & ((1UL<<NBITS) - 1);
+}
+
+/* ================================================== */
+
+inline static unsigned long
+get_residual(unsigned long addr)
+{
+  return (addr << NBITS);
+}
+
+/* ================================================== */
+
+ADF_AuthTable
+ADF_CreateTable(void)
+{
+  ADF_AuthTable result;
+  result = MallocNew(struct ADF_AuthTableInst);
+
+  /* Default is that nothing is allowed */
+  result->base.state = DENY;
+  result->base.extended = NULL;
+
+  return result;
+}
+
+/* ================================================== */
+/* This function deletes all definitions of child nodes, in effect
+   pruning a whole subnet definition back to a single parent
+   record. */
+static void
+close_node(TableNode *node)
+{
+  int i;
+  TableNode *child_node;
+
+  if (node->extended != NULL) {
+    for (i=0; i<TABLE_SIZE; i++) {
+      child_node = &((*(node->extended))[i]);
+      close_node(child_node);
+    }
+    Free(node->extended);
+    node->extended = NULL;
+  }
+
+  return;
+}
+
+
+/* ================================================== */
+/* Allocate the extension field in a node, and set all the children's
+   states to default to that of the node being extended */
+
+static void
+open_node(TableNode *node)
+{
+  int i;
+  TableNode *child_node;
+
+  if (node->extended == NULL) {
+
+    node->extended = MallocNew(ExtendedTable);
+
+    for (i=0; i<TABLE_SIZE; i++) {
+      child_node = &((*(node->extended))[i]);
+      child_node->state = AS_PARENT;
+      child_node->extended = NULL;
+    }
+  }
+  return;
+}
+
+/* ================================================== */
+
+static ADF_Status
+set_subnet(TableNode *start_node,
+           unsigned long ip,
+           int subnet_bits,
+           State new_state,
+           int delete_children)
+{
+  int bits_to_go;
+  unsigned long residual;
+  unsigned long subnet;
+  TableNode *node;
+
+  bits_to_go = subnet_bits;
+  residual = ip;
+  node = start_node;
+
+  if ((subnet_bits < 0) ||
+      (subnet_bits > 32)) {
+
+    return ADF_BADSUBNET;
+
+  } else {
+
+    if ((bits_to_go & (NBITS-1)) == 0) {
+    
+      while (bits_to_go > 0) {
+        subnet = get_subnet(residual);
+        residual = get_residual(residual);
+        if (!(node->extended)) {
+          open_node(node);
+        }
+        node = &((*(node->extended))[subnet]);
+        bits_to_go -= NBITS;
+      }
+
+      if (delete_children) {
+        close_node(node);
+      }
+      node->state = new_state;
+
+    } else { /* Have to set multiple entries */
+      int N, i, j;
+      TableNode *this_node;
+
+      while (bits_to_go >= NBITS) {
+        subnet = get_subnet(residual);
+        residual = get_residual(residual);
+        if (!(node->extended)) {
+          open_node(node);
+        }
+        node = &((*(node->extended))[subnet]);
+        bits_to_go -= NBITS;
+      }
+
+      /* How many subnet entries to set : 1->8, 2->4, 3->2 */
+      N = 1 << (NBITS-bits_to_go);
+      subnet = get_subnet(residual);
+      if (!(node->extended)) {
+        open_node(node);
+      }
+      
+      for (i=subnet, j=0; j<N; i++, j++) {
+        this_node = &((*(node->extended))[i]);
+        if (delete_children) {
+          close_node(this_node);
+        }
+        this_node->state = new_state;
+      }
+    }
+    
+    return ADF_SUCCESS;
+  }
+  
+}
+
+/* ================================================== */
+
+ADF_Status
+ADF_Allow(ADF_AuthTable table,
+          unsigned long ip,
+          int subnet_bits)
+{
+  return set_subnet(&(table->base), ip, subnet_bits, ALLOW, 0);
+}
+
+/* ================================================== */
+
+
+ADF_Status
+ADF_AllowAll(ADF_AuthTable table,
+             unsigned long ip,
+             int subnet_bits)
+{
+  return set_subnet(&(table->base), ip, subnet_bits, ALLOW, 1);
+}
+
+/* ================================================== */
+
+ADF_Status
+ADF_Deny(ADF_AuthTable table,
+         unsigned long ip,
+         int subnet_bits)
+{
+  return set_subnet(&(table->base), ip, subnet_bits, DENY, 0);
+}
+
+/* ================================================== */
+
+ADF_Status
+ADF_DenyAll(ADF_AuthTable table,
+            unsigned long ip,
+            int subnet_bits)
+{
+  return set_subnet(&(table->base), ip, subnet_bits, DENY, 1);
+}
+
+/* ================================================== */
+
+void
+ADF_DestroyTable(ADF_AuthTable table)
+{
+  close_node(&(table->base));
+  Free(table);
+}
+
+/* ================================================== */
+
+static int
+check_ip_in_node(TableNode *start_node, unsigned long ip)
+{
+  unsigned long residual, subnet;
+  int result = 0;
+  int finished = 0;
+  TableNode *node;
+  State state=DENY;
+
+  node = start_node;
+  residual = ip;
+
+  do {
+    if (node->state != AS_PARENT) {
+      state = node->state;
+    }
+    if (node->extended) {
+      subnet = get_subnet(residual);
+      residual = get_residual(residual);
+      node = &((*(node->extended))[subnet]);
+    } else {
+      /* Make decision on this node */
+      finished = 1;
+    }
+  } while (!finished);
+
+  switch (state) {
+    case ALLOW:
+      result = 1;
+      break;
+    case DENY:
+      result = 0;
+      break;
+    case AS_PARENT:
+      assert(0);
+      break;
+  }
+
+  return result;
+}
+
+
+/* ================================================== */
+
+int
+ADF_IsAllowed(ADF_AuthTable table,
+              unsigned long ip)
+{
+
+  return check_ip_in_node(&(table->base), ip);
+
+}
+
+/* ================================================== */
+
+#if defined TEST
+
+static void print_node(TableNode *node, unsigned long addr, int shift, int subnet_bits)
+{
+  unsigned long new_addr;
+  int i;
+  TableNode *sub_node;
+
+  for (i=0; i<subnet_bits; i++) putchar(' ');
+
+  printf("%d.%d.%d.%d/%d : %s\n",
+         ((addr >> 24) & 255),
+         ((addr >> 16) & 255),
+         ((addr >>  8) & 255),
+         ((addr      ) & 255),
+         subnet_bits,
+         (node->state == ALLOW) ? "allow" :
+         (node->state == DENY)  ? "deny" : "as parent");
+  if (node->extended) {
+    for (i=0; i<16; i++) {
+      sub_node = &((*(node->extended))[i]);
+      new_addr = addr | ((unsigned long) i << shift);
+      print_node(sub_node, new_addr, shift - 4, subnet_bits + 4);
+    }
+  }
+  return;
+}
+
+
+static void print_table(ADF_AuthTable table)
+{
+  unsigned long addr = 0;
+  int shift = 28;
+  int subnet_bits = 0;
+
+  print_node(&table->base, addr, shift, subnet_bits);
+  return;
+}
+
+/* ================================================== */
+
+int main (int argc, char **argv)
+{
+  ADF_AuthTable table;
+  table = ADF_CreateTable();
+
+  ADF_Allow(table, 0x7e800000, 9);
+  ADF_Deny(table, 0x7ecc0000, 14);
+  /* ADF_Deny(table, 0x7f000001, 32); */
+  /* ADF_Allow(table, 0x7f000000, 8); */
+
+  print_table(table);
+
+  ADF_DestroyTable(table);
+  return 0;
+}
+
+
+
+#endif /* defined TEST */
+
+
+
+
diff --git a/addrfilt.h b/addrfilt.h
new file mode 100644 (file)
index 0000000..f96f0fb
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+  $Header: /cvs/src/chrony/addrfilt.h,v 1.6 2002/02/28 23:27:08 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Module for providing an authorisation filter on IP addresses
+  */
+
+#ifndef GOT_ADDRFILT_H
+#define GOT_ADDRFILT_H
+
+typedef struct ADF_AuthTableInst *ADF_AuthTable;
+
+typedef enum {
+  ADF_SUCCESS,
+  ADF_BADSUBNET
+} ADF_Status;
+  
+
+/* Create a new table.  The default rule is deny for everything */
+extern ADF_AuthTable ADF_CreateTable(void);
+
+/* Allow anything in the supplied subnet, EXCEPT for any more specific
+   subnets that are already defined */
+extern ADF_Status ADF_Allow(ADF_AuthTable table,
+                            unsigned long ip,
+                            int subnet_bits);
+
+/* Allow anything in the supplied subnet, overwriting existing
+   definitions for any more specific subnets */
+extern ADF_Status ADF_AllowAll(ADF_AuthTable table,
+                               unsigned long ip,
+                               int subnet_bits);
+
+/* Deny anything in the supplied subnet, EXCEPT for any more specific
+   subnets that are already defined */
+extern ADF_Status ADF_Deny(ADF_AuthTable table,
+                           unsigned long ip,
+                           int subnet_bits);
+
+/* Deny anything in the supplied subnet, overwriting existing
+   definitions for any more specific subnets */
+extern ADF_Status ADF_DenyAll(ADF_AuthTable table,
+                              unsigned long ip,
+                              int subnet_bits);
+
+/* Clear up the table */
+extern void ADF_DestroyTable(ADF_AuthTable table);
+
+/* Check whether a given IP address is allowed by the rules in 
+   the table */
+extern int ADF_IsAllowed(ADF_AuthTable table,
+                         unsigned long ip);
+
+#endif /* GOT_ADDRFILT_H */
diff --git a/broadcast.c b/broadcast.c
new file mode 100644 (file)
index 0000000..be217e7
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+  $Header: /cvs/src/chrony/broadcast.c,v 1.3 2002/02/28 23:27:08 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Deal with broadcast server functions.
+  */
+
+#include "sysincl.h"
+#include "memory.h"
+
+#include "addressing.h"
+#include "broadcast.h"
+#include "sched.h"
+#include "ntp.h"
+#include "local.h"
+#include "reference.h"
+#include "util.h"
+#include "ntp_io.h"
+
+typedef struct {
+  NTP_Remote_Address addr;
+  int interval;
+} Destination;
+static Destination *destinations = 0;
+static int n_destinations = 0;
+static int max_destinations = 0;
+
+void
+BRD_Initialise(void)
+{
+  return; /* Nothing to do */
+}
+
+/* ================================================== */
+
+void
+BRD_Finalise(void)
+{
+  return; /* Nothing to do */
+}
+
+/* ================================================== */
+/* This is a cut-down version of what transmit_packet in ntp_core.c does */
+
+static void
+timeout_handler(void *arbitrary)
+{
+  Destination *d = (Destination *) arbitrary;
+  NTP_Packet message;
+  /* Parameters read from reference module */
+  int version;
+  int leap;
+  int are_we_synchronised, our_stratum;
+  NTP_Leap leap_status;
+  unsigned long our_ref_id;
+  struct timeval our_ref_time;
+  double our_root_delay, our_root_dispersion;
+  double local_time_err;
+  struct timeval local_transmit;
+
+  version = 3;
+
+  LCL_ReadCookedTime(&local_transmit, &local_time_err);
+  REF_GetReferenceParams(&local_transmit,
+                         &are_we_synchronised, &leap_status,
+                         &our_stratum,
+                         &our_ref_id, &our_ref_time,
+                         &our_root_delay, &our_root_dispersion);
+
+
+  if (are_we_synchronised) {
+    leap = (int) leap_status;
+  } else {
+    leap = 3;
+  }
+
+  message.lvm = ((leap << 6) &0xc0) | ((version << 3) & 0x38) | (MODE_BROADCAST & 0x07);
+  message.stratum = our_stratum;
+  message.poll = 6; /* FIXME: what should this be? */
+  message.precision = LCL_GetSysPrecisionAsLog();
+
+  /* If we're sending a client mode packet and we aren't synchronized yet, 
+     we might have to set up artificial values for some of these parameters */
+  message.root_delay = double_to_int32(our_root_delay);
+  message.root_dispersion = double_to_int32(our_root_dispersion);
+
+  message.reference_id = htonl((NTP_int32) our_ref_id);
+
+  /* Now fill in timestamps */
+  UTI_TimevalToInt64(&our_ref_time, &message.reference_ts);
+  message.originate_ts.hi = 0UL;
+  message.originate_ts.lo = 0UL;
+  message.receive_ts.hi = 0UL;
+  message.receive_ts.lo = 0UL;
+
+  LCL_ReadCookedTime(&local_transmit, &local_time_err);
+  UTI_TimevalToInt64(&local_transmit, &message.transmit_ts);
+  NIO_SendNormalPacket(&message, &d->addr);
+
+  /* Requeue timeout.  Don't care if interval drifts gradually, so just do it
+   * at the end. */
+  SCH_AddTimeoutInClass((double) d->interval, 1.0,
+                        SCH_NtpBroadcastClass,
+                        timeout_handler, (void *) d);
+
+
+}
+
+/* ================================================== */
+
+void 
+BRD_AddDestination(unsigned long addr, unsigned short port, int interval)
+{
+  if (max_destinations == n_destinations) {
+    /* Expand array */
+    max_destinations += 8;
+    if (destinations) {
+      destinations = ReallocArray(Destination, max_destinations, destinations);
+    } else {
+      destinations = MallocArray(Destination, max_destinations);
+    }
+  }
+
+  destinations[n_destinations].addr.ip_addr = addr;
+  destinations[n_destinations].addr.port = port;
+  destinations[n_destinations].interval = interval;
+
+  SCH_AddTimeoutInClass((double) interval, 1.0,
+                        SCH_NtpBroadcastClass,
+                        timeout_handler, (void *)(destinations + n_destinations));
+  
+  ++n_destinations;
+
+}
+
+
diff --git a/broadcast.h b/broadcast.h
new file mode 100644 (file)
index 0000000..01013bc
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+  $Header: /cvs/src/chrony/broadcast.h,v 1.2 2002/02/28 23:27:08 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Deal with broadcast server functions.
+  */
+
+#ifndef GOT_BROADCAST_H
+#define GOT_BROADCAST_H
+
+extern void BRD_Initialise(void);
+extern void BRD_Finalise(void);
+extern void BRD_AddDestination(unsigned long addr, unsigned short port, int interval);
+
+#endif /* GOT_BROADCAST_H */
+
diff --git a/build_kit b/build_kit
new file mode 100755 (executable)
index 0000000..3067318
--- /dev/null
+++ b/build_kit
@@ -0,0 +1,24 @@
+#!/usr/bin/env perl
+# $Header: /cvs/src/chrony/build_kit,v 1.13 2003/01/12 23:50:54 richard Exp $
+# Perl script for building a release
+
+chmod 0755, "configure";
+
+# Construct chrony.spec file
+$version = $ARGV[0] || die "No version on command line";
+open (IN, "<chrony.spec.sample");
+open (OUT, ">chrony.spec");
+while (<IN>) {
+  s/\@\@VERSION\@\@/$version/;
+  print OUT;
+}
+close (IN);
+close (OUT);
+
+unlink "chrony.spec.sample";
+
+# Requires the makeinfo from texinfo v4
+system("makeinfo --no-headers --number-sections -o chrony.txt chrony.texi");
+unlink("build_kit");
+unlink("LICINS");
+
diff --git a/candm.h b/candm.h
new file mode 100644 (file)
index 0000000..e411fc3
--- /dev/null
+++ b/candm.h
@@ -0,0 +1,594 @@
+/*
+  $Header: /cvs/src/chrony/candm.h,v 1.39 2003/04/01 20:54:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Definitions for the network protocol used for command and monitoring
+  of the timeserver.
+
+  */
+
+#ifndef GOT_CANDM_H
+#define GOT_CANDM_H
+
+#include "sysincl.h"
+
+/* This is the default port to use for CANDM, if no alternative is
+   defined */
+#define DEFAULT_CANDM_PORT 323
+
+/* Request codes */
+#define REQ_NULL 0
+#define REQ_ONLINE 1
+#define REQ_OFFLINE 2
+#define REQ_BURST 3
+#define REQ_MODIFY_MINPOLL 4
+#define REQ_MODIFY_MAXPOLL 5
+#define REQ_DUMP 6
+#define REQ_MODIFY_MAXDELAY 7
+#define REQ_MODIFY_MAXDELAYRATIO 8
+#define REQ_MODIFY_MAXUPDATESKEW 9
+#define REQ_LOGON 10
+#define REQ_SETTIME 11
+#define REQ_LOCAL 12
+#define REQ_MANUAL 13
+#define REQ_N_SOURCES 14
+#define REQ_SOURCE_DATA 15
+#define REQ_REKEY 16
+#define REQ_ALLOW 17
+#define REQ_ALLOWALL 18
+#define REQ_DENY 19
+#define REQ_DENYALL 20
+#define REQ_CMDALLOW 21
+#define REQ_CMDALLOWALL 22
+#define REQ_CMDDENY 23
+#define REQ_CMDDENYALL 24
+#define REQ_ACCHECK 25
+#define REQ_CMDACCHECK 26
+#define REQ_ADD_SERVER 27
+#define REQ_ADD_PEER 28
+#define REQ_DEL_SOURCE 29
+#define REQ_WRITERTC 30
+#define REQ_DFREQ 31
+#define REQ_DOFFSET 32
+#define REQ_TRACKING 33
+#define REQ_SOURCESTATS 34
+#define REQ_RTCREPORT 35
+#define REQ_TRIMRTC 36
+#define REQ_CYCLELOGS 37
+#define REQ_SUBNETS_ACCESSED 38
+#define REQ_CLIENT_ACCESSES 39
+#define REQ_CLIENT_ACCESSES_BY_INDEX 40
+#define REQ_MANUAL_LIST 41
+#define REQ_MANUAL_DELETE 42
+#define REQ_MAKESTEP 43
+#define REQ_ACTIVITY 44
+#define N_REQUEST_TYPES 45
+
+/* Special utoken value used to log on with first exchange being the
+   password.  (This time value has long since gone by) */
+#define SPECIAL_UTOKEN 0x10101010
+
+/* The EOR (end of record) fields are used by the offsetof operator in
+   pktlength.c, to get the number of bytes that ought to be
+   transmitted for each packet type. */
+
+typedef struct {
+  uint32_t mask;
+  uint32_t address;
+  int32_t EOR;
+} REQ_Online;
+
+typedef struct {
+  uint32_t mask;
+  uint32_t address;
+  int32_t EOR;
+} REQ_Offline;
+
+typedef struct {
+  uint32_t mask;
+  uint32_t address;
+  int32_t n_good_samples;
+  int32_t n_total_samples;
+  int32_t EOR;
+} REQ_Burst;
+
+typedef struct {
+  uint32_t address;
+  int32_t new_minpoll;
+  int32_t EOR;
+} REQ_Modify_Minpoll;
+
+typedef struct {
+  uint32_t address;
+  int32_t new_maxpoll;
+  int32_t EOR;
+} REQ_Modify_Maxpoll;
+
+typedef struct {
+  int32_t pad;
+  int32_t EOR;
+} REQ_Dump;
+
+typedef struct {
+  uint32_t address;
+  int32_t new_max_delay;
+  int32_t EOR;
+} REQ_Modify_Maxdelay;
+
+typedef struct {
+  uint32_t address;
+  int32_t new_max_delay_ratio;
+  int32_t EOR;
+} REQ_Modify_Maxdelayratio;
+
+typedef struct {
+  int32_t new_max_update_skew;
+  int32_t EOR;
+} REQ_Modify_Maxupdateskew;
+
+typedef struct {
+  struct timeval ts;
+  int32_t EOR;
+} REQ_Logon;
+
+typedef struct {
+  struct timeval ts;
+  int32_t EOR;
+} REQ_Settime;
+
+typedef struct {
+  int32_t on_off;
+  int32_t stratum;
+  int32_t EOR;
+} REQ_Local;
+
+typedef struct {
+  int32_t option;
+  int32_t EOR;
+} REQ_Manual;
+
+typedef struct {
+  int32_t EOR;
+} REQ_N_Sources;
+
+typedef struct {
+  int32_t index;
+  int32_t EOR;
+} REQ_Source_Data;
+
+typedef struct {
+  int32_t EOR;
+} REQ_Rekey;
+
+typedef struct {
+  uint32_t ip;
+  int32_t subnet_bits;
+  int32_t EOR;
+} REQ_Allow_Deny;
+
+typedef struct {
+  uint32_t ip;
+  int32_t EOR;
+} REQ_Ac_Check;
+
+typedef struct {
+  uint32_t ip_addr;
+  uint32_t port;
+  int32_t minpoll;
+  int32_t maxpoll;
+  int32_t presend_minpoll;
+  int32_t online;
+  int32_t auto_offline;
+  uint32_t authkey;
+  int32_t max_delay;
+  int32_t max_delay_ratio;
+  int32_t EOR;
+} REQ_NTP_Source;
+
+typedef struct {
+  uint32_t ip_addr;
+  int32_t EOR;
+} REQ_Del_Source;
+
+typedef struct {
+  int32_t EOR;
+} REQ_WriteRtc;
+
+typedef struct {
+  int32_t dfreq;
+  int32_t EOR;
+} REQ_Dfreq;
+
+typedef struct {
+  int32_t sec;
+  int32_t usec;
+  int32_t EOR;
+} REQ_Doffset;
+
+typedef struct {
+  int32_t EOR;
+} REQ_Tracking;
+
+typedef struct {
+  uint32_t index;
+  int32_t EOR;
+} REQ_Sourcestats;
+
+typedef struct {
+  int32_t EOR;
+} REQ_RTCReport;
+
+typedef struct {
+  int32_t EOR;
+} REQ_TrimRTC;
+
+typedef struct {
+  int32_t EOR;
+} REQ_CycleLogs;
+
+typedef struct {
+  uint32_t ip;
+  uint32_t bits_specd;
+} REQ_SubnetsAccessed_Subnet;
+
+#define MAX_SUBNETS_ACCESSED 8
+
+typedef struct {
+  uint32_t n_subnets;
+  REQ_SubnetsAccessed_Subnet subnets[MAX_SUBNETS_ACCESSED];
+} REQ_SubnetsAccessed;
+
+/* This is based on the response size rather than the
+   request size */
+#define MAX_CLIENT_ACCESSES 16
+
+typedef struct {
+  uint32_t n_clients;
+  uint32_t client_ips[MAX_CLIENT_ACCESSES];
+} REQ_ClientAccesses;  
+
+typedef struct {
+  uint32_t first_index;
+  uint32_t n_indices;
+  int32_t EOR;
+} REQ_ClientAccessesByIndex;
+
+typedef struct {
+  int32_t EOR;
+} REQ_ManualList;
+
+typedef struct {
+  int32_t index;
+  int32_t EOR;
+} REQ_ManualDelete;
+
+typedef struct {
+  int32_t EOR;
+} REQ_MakeStep;
+
+typedef struct {
+  int32_t EOR;
+} REQ_Activity;
+
+/* ================================================== */
+
+#define PKT_TYPE_CMD_REQUEST 1
+#define PKT_TYPE_CMD_REPLY 2
+
+/* This version number needs to be incremented whenever the packet
+   size and/or the format of any of the existing messages is changed.
+   Other changes, e.g. new command types, should be handled cleanly by
+   client.c and cmdmon.c anyway, so the version can stay the same.
+   
+   Version 1 : original version with fixed size packets
+
+   Version 2 : both command and reply packet sizes made capable of
+   being variable length.
+
+   Version 3 : NTP_Source message lengthened (auto_offline)
+
+ */
+
+#define PROTO_VERSION_NUMBER 3
+
+/* ================================================== */
+
+typedef struct {
+  uint8_t version; /* Protocol version */
+  uint8_t pkt_type; /* What sort of packet this is */
+  uint8_t res1;
+  uint8_t res2;
+  uint16_t command; /* Which command is being issued */
+  uint16_t attempt; /* How many resends the client has done
+                             (count up from zero for same sequence
+                             number) */
+  uint32_t sequence; /* Client's sequence number */
+  uint32_t utoken; /* Unique token per incarnation of daemon */
+  uint32_t token; /* Command token (to prevent replay attack) */
+  uint32_t auth[4]; /* MD5 authentication of the packet */
+
+  union {
+    REQ_Online online;
+    REQ_Offline offline;
+    REQ_Burst burst;
+    REQ_Modify_Minpoll modify_minpoll;
+    REQ_Modify_Maxpoll modify_maxpoll;
+    REQ_Dump dump;
+    REQ_Modify_Maxdelay modify_maxdelay;
+    REQ_Modify_Maxdelayratio modify_maxdelayratio;
+    REQ_Modify_Maxupdateskew modify_maxupdateskew;
+    REQ_Logon logon;
+    REQ_Settime settime;
+    REQ_Local local;
+    REQ_Manual manual;
+    REQ_N_Sources n_sources;
+    REQ_Source_Data source_data;
+    REQ_Rekey rekey;
+    REQ_Allow_Deny allow_deny;
+    REQ_Ac_Check ac_check;
+    REQ_NTP_Source ntp_source;
+    REQ_Del_Source del_source;
+    REQ_WriteRtc writertc;
+    REQ_Dfreq dfreq;
+    REQ_Doffset doffset;
+    REQ_Tracking tracking;
+    REQ_Sourcestats sourcestats;
+    REQ_RTCReport rtcreport;
+    REQ_TrimRTC trimrtc;
+    REQ_CycleLogs cyclelogs;
+    REQ_SubnetsAccessed subnets_accessed;
+    REQ_ClientAccesses client_accesses;
+    REQ_ClientAccessesByIndex client_accesses_by_index;
+    REQ_ManualList manual_list;
+    REQ_ManualDelete manual_delete;
+    REQ_MakeStep make_step;
+    REQ_Activity activity;
+  } data; /* Command specific parameters */
+
+} CMD_Request;
+
+/* ================================================== */
+/* Authority codes for command types */
+
+#define PERMIT_OPEN 0
+#define PERMIT_LOCAL 1
+#define PERMIT_AUTH 2
+
+/* ================================================== */
+/* These conversion utilities are used to convert between the internal
+   and the 'wire' representation of real quantities */
+
+#define WIRE2REAL(x) ((double) ((int32_t) ntohl(x)) / 65536.0)
+#define REAL2WIRE(x) (htonl((int32_t)(0.5 + 65536.0 * (x))))
+
+/* ================================================== */
+
+/* Reply codes */
+#define RPY_NULL 1
+#define RPY_N_SOURCES 2
+#define RPY_SOURCE_DATA 3
+#define RPY_MANUAL_TIMESTAMP 4
+#define RPY_TRACKING 5
+#define RPY_SOURCESTATS 6
+#define RPY_RTC 7
+#define RPY_SUBNETS_ACCESSED 8
+#define RPY_CLIENT_ACCESSES 9
+#define RPY_CLIENT_ACCESSES_BY_INDEX 10
+#define RPY_MANUAL_LIST 11
+#define RPY_ACTIVITY 12
+#define N_REPLY_TYPES 13
+
+/* Status codes */
+#define STT_SUCCESS 0
+#define STT_FAILED 1
+#define STT_UNAUTH 2
+#define STT_INVALID 3
+#define STT_NOSUCHSOURCE 4
+#define STT_INVALIDTS 5
+#define STT_NOTENABLED 6
+#define STT_BADSUBNET 7
+#define STT_ACCESSALLOWED 8
+#define STT_ACCESSDENIED 9
+#define STT_NOHOSTACCESS 10
+#define STT_SOURCEALREADYKNOWN 11
+#define STT_TOOMANYSOURCES 12
+#define STT_NORTC 13
+#define STT_BADRTCFILE 14
+#define STT_INACTIVE 15
+#define STT_BADSAMPLE 16
+
+typedef struct {
+  int32_t EOR;
+} RPY_Null;
+
+typedef struct {
+  uint32_t n_sources;
+  int32_t EOR;
+} RPY_N_Sources;
+
+#define RPY_SD_MD_CLIENT 0
+#define RPY_SD_MD_PEER   1
+#define RPY_SD_MD_REF    2
+
+#define RPY_SD_ST_SYNC 0
+#define RPY_SD_ST_UNREACH 1
+#define RPY_SD_ST_FALSETICKER 2
+#define RPY_SD_ST_JITTERY 3
+#define RPY_SD_ST_OTHER 4
+
+typedef struct {
+  uint32_t ip_addr;
+  uint16_t poll;
+  uint16_t stratum;
+  uint16_t state;
+  uint16_t mode;
+  uint32_t  since_sample;
+  int32_t orig_latest_meas;
+  int32_t latest_meas;
+  uint32_t latest_meas_err;
+  int32_t est_offset;
+  uint32_t est_offset_err;
+  int32_t resid_freq;
+  uint32_t resid_skew;
+  int32_t EOR;
+} RPY_Source_Data;
+
+typedef struct {
+  uint32_t ref_id;
+  uint32_t stratum;
+  uint32_t ref_time_s;
+  uint32_t ref_time_us;
+  uint32_t current_correction_s;
+  uint32_t current_correction_us;
+  int32_t freq_ppm;
+  int32_t resid_freq_ppm;
+  int32_t skew_ppm;
+  int32_t root_delay;
+  int32_t root_dispersion;
+  int32_t EOR;
+} RPY_Tracking;
+
+typedef struct {
+  uint32_t ip_addr;
+  uint32_t n_samples;
+  uint32_t n_runs;
+  uint32_t span_seconds;
+  uint32_t sd_us;
+  int32_t resid_freq_ppm;
+  int32_t skew_ppm;
+  int32_t EOR;
+} RPY_Sourcestats;
+
+typedef struct {
+  uint32_t ref_time;
+  uint16_t n_samples;
+  uint16_t n_runs;
+  uint32_t span_seconds;
+  int32_t rtc_seconds_fast;
+  int32_t rtc_gain_rate_ppm;
+  int32_t EOR;
+} RPY_Rtc;
+
+typedef struct {
+  uint32_t centiseconds;
+  int32_t dfreq_ppm;
+  int32_t new_afreq_ppm;
+  int32_t EOR;
+} RPY_ManualTimestamp;
+
+typedef struct {
+  uint32_t ip;
+  uint32_t bits_specd;
+  uint32_t bitmap[8];
+} RPY_SubnetsAccessed_Subnet;
+
+typedef struct {
+  uint32_t n_subnets;
+  RPY_SubnetsAccessed_Subnet subnets[MAX_SUBNETS_ACCESSED];
+} RPY_SubnetsAccessed;
+
+typedef struct {
+  uint32_t ip;
+  uint32_t client_hits;
+  uint32_t peer_hits;
+  uint32_t cmd_hits_auth;
+  uint32_t cmd_hits_normal;
+  uint32_t cmd_hits_bad;
+  uint32_t last_ntp_hit_ago;
+  uint32_t last_cmd_hit_ago;
+} RPY_ClientAccesses_Client;
+
+typedef struct {
+  uint32_t n_clients;
+  RPY_ClientAccesses_Client clients[MAX_CLIENT_ACCESSES];
+} RPY_ClientAccesses;
+
+typedef struct {
+  uint32_t n_indices;      /* how many indices there are in the server's table */
+  uint32_t next_index;     /* the index 1 beyond those processed on this call */
+  uint32_t n_clients;      /* the number of valid entries in the following array */
+  RPY_ClientAccesses_Client clients[MAX_CLIENT_ACCESSES];
+} RPY_ClientAccessesByIndex;
+
+#define MAX_MANUAL_LIST_SAMPLES 32
+
+typedef struct {
+  uint32_t when;
+  int32_t slewed_offset;
+  int32_t orig_offset;
+  int32_t residual;
+} RPY_ManualListSample;
+
+typedef struct {
+  uint32_t n_samples;
+  RPY_ManualListSample samples[MAX_MANUAL_LIST_SAMPLES];
+} RPY_ManualList;
+
+typedef struct {
+  int32_t online;
+  int32_t offline;
+  int32_t burst_online;
+  int32_t burst_offline;
+  int32_t EOR;
+} RPY_Activity;
+
+typedef struct {
+  uint8_t version;
+  uint8_t pkt_type;
+  uint8_t res1;
+  uint8_t res2;
+  uint16_t command; /* Which command is being replied to */
+  uint16_t reply; /* Which format of reply this is */
+  uint16_t status; /* Status of command processing */
+  uint16_t number; /* Which packet this is in reply sequence */
+  uint16_t total; /* Number of replies to expect in this sequence */
+  uint16_t pad1; /* Get up to 4 byte alignment */
+  uint32_t sequence; /* Echo of client's sequence number */
+  uint32_t utoken; /* Unique token per incarnation of daemon */
+  uint32_t token; /* New command token (only if command was successfully
+                          authenticated) */
+  uint32_t auth[4]; /* MD5 authentication of the packet */
+
+  union {
+    RPY_Null null;
+    RPY_N_Sources n_sources;
+    RPY_Source_Data source_data;
+    RPY_ManualTimestamp manual_timestamp;
+    RPY_Tracking tracking;
+    RPY_Sourcestats sourcestats;
+    RPY_Rtc rtc;
+    RPY_SubnetsAccessed subnets_accessed;
+    RPY_ClientAccesses client_accesses;
+    RPY_ClientAccessesByIndex client_accesses_by_index;
+    RPY_ManualList manual_list;
+    RPY_Activity activity;
+  } data; /* Reply specific parameters */
+
+} CMD_Reply;
+
+/* ================================================== */
+
+#endif /* GOT_CANDM_H */
diff --git a/chrony.1 b/chrony.1
new file mode 100644 (file)
index 0000000..81e6ee2
--- /dev/null
+++ b/chrony.1
@@ -0,0 +1,65 @@
+.TH CHRONY 1 "August 10, 2001" chrony "User's Manual"
+.SH NAME
+chrony \- programs for keeping computer clocks accurate
+
+.SH SYNOPSIS
+\fBchronyc\fR [\fIOPTIONS\fR]
+
+\fBchronyd\fR [\fIOPTIONS\fR]
+
+.SH DESCRIPTION
+\fBchrony\fR is a pair of programs for keeping computer clocks accurate.
+\fIchronyd\fR is a background (daemon) program and \fIchronyc\fR is a
+command-line interface to it. Time reference sources for chronyd can be
+RFC1305 NTP servers, human (via keyboard and \fIchronyc\fR), or the computer's
+real-time clock at boot time (Linux only). chronyd can determine the rate at
+which the computer gains or loses time and compensate for it while no external
+reference is present. Its use of NTP servers can be switched on and off
+(through \fIchronyc\fR) to support computers with dial-up/intermittent access
+to the Internet, and it can also act as an RFC1305-compatible NTP server.
+
+.SH USAGE
+\fIchronyc\fR is a command-line interface program which can be used to
+monitor \fIchronyd\fR's performance and to change various operating
+parateters whilst it is running.
+
+\fIchronyd\fR's main function is to obtain measurements of the true (UTC)
+time from one of several sources, and correct the system clock
+accordingly.  It also works out the rate at which the system clock
+gains or loses time and uses this information to keep it accurate
+between measurements from the reference.
+
+The reference time can be derived from either Network Time Protocol
+(NTP) servers (preferred), or wristwatch-and-keyboard (via \fIchronyc\fR).
+The main source of information about the Network Time Protocol is
+\fIhttp://www.eecis.udel.edu/~ntp\fR.
+
+It is designed so that it can work on computers which only have
+intermittent access to reference sources, for example computers which
+use a dial-up account to access the Internet.  Of course, it will work
+on computers with permanent connections too.
+
+In addition, for Linux 2.0.x (for x >= 32) or 2.2 onwards, chronyd can monitor
+the system's real time clock performance, so the system can maintain accurate
+time even across reboots.
+
+Typical accuracies available between 2 machines are
+
+On an ethernet LAN : 100-200 microseconds, often much better
+On a V32bis dial-up modem connection : 10's of milliseconds (from one
+session to the next)
+
+\fIchronyd\fR can also operate as an RFC1305-compatible NTP server and peer.
+
+.SH "SEE ALSO"
+.BR chronyc(1),
+.BR chrony(1)
+
+.I http://chrony.sunsite.dk/
+
+.SH AUTHOR
+Richard Curnow <rc@rc0.org.uk>
+
+This man-page was written by Jan Schaumann <jschauma@netmeister.org> as part
+of "The Missing Man Pages Project".  Please see
+\fIhttp://www.netmeister.org/misc/m2p2/index.html\fR for details.
diff --git a/chrony.conf.5 b/chrony.conf.5
new file mode 100644 (file)
index 0000000..968662e
--- /dev/null
@@ -0,0 +1,49 @@
+.TH chrony.conf 5 "August 10, 2001" chrony "Configuration Files"
+.SH NAME
+chrony.conf \- chronyd configuration file
+
+.SH SYNOPSIS
+.B /etc/chrony.conf
+
+.SH DESCRIPTION
+\fIchrony\fR is a pair of programs for maintaining the accuracy of computer
+clocks. \fIchronyd\fR is a background daemon program that can be started at
+boot time.
+
+Assuming that you have found some servers, you need to set up a
+configuration file to run \fIchrony\fR.  The (compiled-in) default location
+for this file is \fB/etc/chrony.conf\fR.  Assuming that your ntp servers
+are called `a.b.c' and `d.e.f', your \fBchrony.conf\fR file could contain
+as a minimum
+
+     server a.b.c
+     server d.e.f
+     server g.h.i
+
+However, you will probably want to include some of the other directives
+described in detail in the documentation supplied with the distribution
+(\fIchrony.txt\fR and \fIchrony.texi\fR). The following directives will be
+particularly useful : `driftfile', `commandkey', `keyfile'.  The smallest
+useful configuration file would look something like
+
+     server a.b.c
+     server d.e.f
+     server g.h.i
+     keyfile /etc/chrony.keys
+     commandkey 1
+     driftfile /etc/chrony.drift
+
+
+.SH "SEE ALSO"
+.BR chrony(1),
+.BR chronyc(1),
+.BR chronyd(1)
+
+.I http://chrony.sunsite.dk/
+
+.SH AUTHOR
+Richard Curnow <rc@rc0.org.uk>
+
+This man-page was written by Jan Schaumann <jschauma@netmeister.org> as part of "The Missing
+Man Pages Project".  Please see \fIhttp://www.netmeister.org/misc/m2p2/index.html\fR
+for details.
diff --git a/chrony.lsm b/chrony.lsm
new file mode 100644 (file)
index 0000000..1e65785
--- /dev/null
@@ -0,0 +1,29 @@
+Begin3
+Title:          chrony
+Version:        1.18
+Entered-date:   01APR02
+Description:    A pair of programs for keeping computer clocks accurate.
+                chronyd is a background (daemon) program and chronyc is a
+                command-line interface to it.  Time reference sources for
+                chronyd can be RFC1305 NTP servers, human (via keyboard and
+                chronyc), and the computer's real-time clock at boot time
+                (Linux only).  chronyd can determine the rate at which the
+                computer gains or loses time and compensate for it whilst no
+                external reference is present.  chronyd's use of NTP servers
+                can be switched on and off (through chronyc) to support
+                computers with dial-up/intermittent access to the
+                Internet. chronyd can also act as an RFC1305-compatible NTP
+                server.
+Keywords:       time NTP RFC1305 RTC adjtime
+Author:         rc@rc0.org.uk (Richard Curnow)
+Maintained-by:  rc@rc0.org.uk (Richard Curnow)
+Primary-site:   sunsite.unc.edu /pub/Linux/system/admin/time
+                295k chrony-1.18.tar.gz
+                2k chrony.lsm
+Platforms:      Linux 2.0/2.1/2.2/2.3/2.4 (x86, powerpc)
+                Solaris 2.5/6/7/8, SunOS 4.1.4. (Sparc)
+                BSDI/386.
+               NetBSD
+                Solaris 2.8 (x86)
+Copying-policy: GPL
+End
diff --git a/chrony.spec.sample b/chrony.spec.sample
new file mode 100644 (file)
index 0000000..361617a
--- /dev/null
@@ -0,0 +1,52 @@
+Summary: An NTP client/server
+Name: chrony
+Version: @@VERSION@@
+Release: 1
+Source: chrony-%{version}.tar.gz
+Copyright: GPL
+Group: Applications/Utilities
+Packager: Richard P. Curnow <rc@rc0.org.uk>
+BuildRoot: %{_tmppath}/%{name}-%{version}-root-%(id -u -n)
+Requires: info
+
+%description
+A pair of programs for keeping computer clocks accurate.  chronyd is a
+background (daemon) program and chronyc is a command-line interface to it.
+Time reference sources for chronyd can be RFC1305 NTP servers, human (via
+keyboard and chronyc), and the computer's real-time clock at boot time (Linux
+only).  chronyd can determine the rate at which the computer gains or loses
+time and compensate for it whilst no external reference is present.  chronyd's
+use of NTP servers can be switched on and off (through chronyc) to support
+computers with dial-up/intermittent access to the Internet. chronyd can also
+act as an RFC1305-compatible NTP server.
+
+%prep
+%setup
+
+%build
+./configure --prefix=%{_prefix}
+make CC=gcc CFLAGS=-O2 prefix=%{_prefix}
+make chrony.txt prefix=%{_prefix}
+make chrony.info prefix=%{_prefix}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+cd $RPM_BUILD_DIR/chrony-%{version}
+make install DESTDIR=$RPM_BUILD_ROOT prefix=%{_prefix}
+mkdir -p $RPM_BUILD_ROOT%{_infodir}
+cp chrony.info* $RPM_BUILD_ROOT%{_infodir}
+
+%files
+%{_sbindir}/chronyd
+%{_bindir}/chronyc
+%{_infodir}/chrony.info*
+%{_mandir}/man1/chrony.1.gz
+%{_mandir}/man1/chronyc.1.gz
+%{_mandir}/man5/chrony.conf.5.gz
+%{_mandir}/man8/chronyd.8.gz
+%doc README
+%doc chrony.txt
+%doc COPYING
+%doc examples/chrony.conf.example
+%doc examples/chrony.keys.example
+
diff --git a/chrony.texi b/chrony.texi
new file mode 100644 (file)
index 0000000..4642bb7
--- /dev/null
@@ -0,0 +1,3979 @@
+\input texinfo
+@c {{{ Main header stuff
+@afourwide
+@paragraphindent 0
+@setfilename chrony.info
+@settitle User guide for the chrony suite
+@c @setchapternewpage off
+
+@ifinfo
+@dircategory Net Utilities
+@direntry
+* chrony: (chrony).                    How to use chronyd and chronyc
+* chronyd: (chrony)Starting chronyd.   Reference for chronyd
+* chronyc: (chrony)Running chronyc.    Reference for chronyc
+@end direntry
+@end ifinfo
+
+@titlepage
+@sp 10
+@title The chrony suite
+@subtitle This manual describes how to use
+@subtitle the programs chronyd and chronyc
+@author Richard P. Curnow
+@page
+@vskip 0pt plus 1filll
+Copyright @copyright{} 1997-1999 Richard P. Curnow
+@end titlepage
+@c }}}
+@c {{{ Top node
+@node Top
+@top
+@menu
+* Introduction::                What the chrony suite does
+* Installation::                How to compile and install the software
+* Typical scenarios::           How to configure the software for some common cases
+* Usage reference::             Reference manual
+* Porting guide::               Hints to help with porting the software
+* GPL::                         The GNU General Public License
+@end menu
+@c }}}
+@c {{{ Ch:Introduction
+@c {{{ Chapter top
+@node Introduction
+@chapter Introduction
+@menu
+* Overview::                    What the programs do
+* Acknowledgements::            Credit where credit is due
+* Availability::                Where to get the software
+* Other time synchronisation packages::  Comparision with other software
+* Distribution and warranty::   There is no warranty
+* Bug reporting::               How to report bugs and make suggestions
+* Contributing::                Areas where contributions are particularly welcome
+@end menu
+@c }}}
+@c {{{ S:Overview
+@node Overview
+@section Overview
+Chrony is a software package for maintaining the accuracy of computer
+system clocks.  It consists of a pair of programs :
+
+@itemize @bullet
+@item @code{chronyd}.  This is a daemon which runs in background on the
+system.  It obtains measurements (e.g. via the network) of the system's
+offset relative to other systems, and adjusts the system time
+accordingly.  For isolated systems, the user can periodically enter the
+correct time by hand (using @code{chronyc}).  In either case,
+@code{chronyd} determines the rate at which the computer gains or loses
+time, and compensates for this.
+
+@code{chronyd} can also act as an NTP server, and provide a time-of-day service
+to other computers.  A typical set-up is to run @code{chronyd} on a gateway
+computer that has a dial-up link to the Internet, and use it to serve time to
+computers on a private LAN sitting behind the gateway.  The IP addresses that
+can act as clients of @code{chronyd} can be tightly controlled.  The default is
+no client access.
+
+@item @code{chronyc}.  This is a command-line driven control and
+monitoring program.  An administrator can use this to fine-tune various
+parameters within the daemon, add or delete servers etc whilst the
+daemon is running.
+
+The IP addresses from which @code{chronyc} clients may connect can be tightly
+controlled.  The default is just the computer that @code{chronyd} itself is
+running on.
+@end itemize
+@c }}}
+@c {{{ S:Acknowledgments
+@node Acknowledgements
+@section Acknowledgements
+
+The @code{chrony} suite makes use of the algorithm known as @emph{RSA
+Data Security, Inc. MD5 Message-Digest Algorithm} for authenticating
+messages between different machines on the network.
+
+In writing the @code{chronyd} program, extensive use has been made of
+RFC1305, written by David Mills.  I have occasionally referred to the
+@code{xntp} suite's source code to check details of the protocol that
+the RFC did not make absolutely clear.  The core algorithms in
+@code{chronyd} are all completely distinct from @code{xntp}, however.
+@c }}}
+@c {{{ S:Availability
+@node Availability
+@section Availability
+@menu
+* Getting the software::        Where can I get the software from?
+* Platforms::                   Which platforms will it run on?
+@end menu
+
+
+@node Getting the software
+@subsection Getting the software
+Links on @uref{http://chrony.sunsite.dk/download.php, the
+chrony home page} describe how to obtain the software.
+
+
+@node Platforms
+@subsection Platforms
+Although most of the program is portable between
+Unix-like systems, there are parts that have to be tailored to each
+specific vendor's system.  These are the parts that interface with the
+operating system's facilities for adjusting the system clock;
+different operating systems may provide different function calls to
+achieve this, and even where the same function is used it may have
+different quirks in its behaviour.
+
+The software is known to work in the following environments:
+@itemize @bullet
+@item Linux/i386 and Linux/ppc.  The software is known to work on Linux 2.0.x,
+2.2.x and 2.4.x.  Prior to 2.0.31, the real time clock can't be used.
+
+@item NetBSD
+@item BSD/386
+
+@item Solaris 2.3/2.5/2.5.1/2.6/2.7/2.8 on Sparc (Sparc 20, Ultrasparc) and
+i386
+
+@item SunOS 4.1.4 on Sparc 2 and Sparc20.
+@end itemize
+
+Closely related systems may work too, but they have not been tested.
+
+Porting the software to other system (particularly to those supporting
+an @code{adjtime} system call) should not be difficult, however it
+requires access to such systems to test out the driver.
+@c }}}
+@c {{{ S:Other programs
+@node Other time synchronisation packages
+@section Relationship to other software packages
+@menu
+* Comparison with xntpd::       
+* Comparison with timed::       
+@end menu
+
+@node Comparison with xntpd
+@subsection xntpd
+The `reference' implementation of the Network Time Protocol is the
+program @code{xntpd}, available via
+@uref{http://www.eecis.udel.edu/~ntp, The NTP home page}.
+
+@code{xntpd} is designed to support all the operating modes defined by
+RFC1305, and has driver support for a large number of reference clocks
+(such as GPS receivers) that can be connected directly to a computer,
+thereby providing a so-called 'stratum 1' server.
+
+Things @code{chronyd} can do that @code{xntpd} can't:
+
+@itemize @bullet
+@item
+@code{chronyd} can perform usefully in an environment where access to
+the time reference is intermittent.  @code{chronyd} estimates
+@emph{both} the current time offset @emph{and} the rate at which the
+computer's clock gains or loses time, and can use that rate estimate to
+trim the clock after the reference disappears.  @code{xntpd} corrects
+any time offset by speeding up and slowing down the computer clock, and
+so could be left with a significant rate error if the reference
+disappears whilst it is trying to correct a big offset.
+
+@item
+@code{chronyd} provides support for isolated networks whether the only
+method of time correction is manual entry (e.g. by the administrator
+looking at a clock).  @code{chronyd} can look at the errors corrected at
+different updates to work out the rate at which the computer gains or
+loses time, and use this estimate to trim the computer clock
+subsequently.
+
+@item
+@code{chronyd} provides support to work out the gain or loss rate of the
+`real-time clock', i.e. the clock that maintains the time when the
+computer is turned off.  It can use this data when the system boots to
+set the system time from a corrected version of the real-time clock.
+These real-time clock facilities are only available on certain releases
+of Linux, so far.
+
+@item
+The @code{xntpd} program is supported by other programs to carry out
+certain functions.  @code{ntpdate} is used to provide an initial
+correction to the system clock based on a `one-shot' sampling of other
+NTP servers.  @code{tickadj} is used to adjust certain operating system
+parameters to make @code{xntpd} work better.  All this functionality is
+integrated into @code{chronyd}.
+@end itemize
+
+Things @code{xntpd} can do that @code{chronyd} can't:
+
+@itemize @bullet
+@item
+@code{xntpd} supports a range of different hardware reference clocks
+(GPS, atomic etc) that can be connected to a computer to provide a
+`stratum-1' server.  @code{chronyd} does not support any such hardware
+@emph{yet}; I don't have access to any to do any development work.
+However, the software architecture should allow such equipment to be
+interfaced at a later date.
+
+@item
+@code{xntpd} supports effectively all of RFC1305, including broadcast /
+multicast clients, leap seconds, and extra encryption schemes for
+authenticating data packets.
+
+@item
+@code{xntpd} has been ported to more types of computer / operating
+system (so far).
+
+@item
+xntpd is designed to work solely with integer arithmetic (i.e. does not
+require floating point support from its host).
+@end itemize
+
+@node Comparison with timed
+@subsection timed
+@code{timed} is a program that is part of the BSD networking suite.  It
+uses broadcast packets to find all machines running the daemon within a
+subnet.  The machines elect a master which periodically measures the
+system clock offsets of the other computers using ICMP timestamps.
+Corrections are sent to each member as a result of this process.
+
+Problems that may arise with @code{timed} are :
+
+@itemize @bullet
+@item
+Because it uses broadcasts, it is not possible to isolate its
+functionality to a particular group of computers; there is a risk of
+upsetting other computers on the same network (e.g. where a whole
+company is on the same subnet but different departments are independent
+from the point of view of administering their computers.)
+@item
+The update period appears to be 10 minutes.  Computers can build up
+significant offsets relative to each other in that time.  If a
+computer can estimate its rate of drift it can keep itself closer to
+the other computers between updates by adjusting its clock every few
+seconds.  @code{timed} does not seem to do this.
+@item
+@code{timed} does not have any integrated capability for feeding
+real-time into its estimates, or for estimating the average rate of time
+loss/gain of the machines relative to real-time (unless one of the
+computers in the group has access to an external reference and is always
+appointed as the `master').
+@end itemize
+
+@code{timed} does have the benefit over @code{chronyd} that for isolated
+networks of computers, they will track the `majority vote' time.  For
+such isolated networks, @code{chronyd} requires one computer to be the
+`master' with the others slaved to it.  If the master has a particular
+defective clock, the whole set of computers will tend to slip relative
+to real time (but they @emph{will} stay accurate relative to one
+another).
+@c }}}
+@c {{{ S:Rights + warranty
+@node Distribution and warranty
+@section Distribution rights and (lack of) warranty
+
+Chrony may be distributed in accordance with the GNU General Public License
+version 2, reproduced in @xref{GPL}.
+
+@c }}}
+@c {{{ S:Bug reporting + suggestions
+@node Bug reporting
+@section Bug reporting and suggestions
+
+If you think you've found a bug in chrony, or have a suggestion, please let me
+know.  My primary current email address is @email{rc@@rc0.org.uk}.  If that
+fails, you could try finding me through one of the chrony mailing lists, or by
+looking up my name on a search engine.
+
+I can't promise a timescale to fix a bug; it depends a lot on the how complex
+the bug is to track down, as I have a lot of other calls on my time : 2 young
+children, my job, and indeed other free/open source software projects.
+However, I do intend to look into problems when time allows.
+
+Another source of information to try is the chrony users mailing list.  You can
+join this by sending an empty message to
+@email{chrony-users-subscribe@@sunsite.dk}.  Only subscribers can post to
+the list.
+
+When you are reporting a bug, please send me all the information you can.
+Unfortunately, chrony has proven to be one of those programs where it is very
+difficult to reproduce bugs in a different environment.  So I may have to
+interact with you quite a lot to obtain enough extra logging and tracing to
+pin-point the problem in some cases.  Please be patient and plan for this!
+
+Of course, if you can debug the problem yourself and send me a source code
+patch to fix it, I will be very grateful!
+
+@c }}}
+@c {{{ S:Contributions
+@node Contributing
+@section Contributions
+
+Although chrony is now a fairly mature and established project, there are still
+areas that could be improved.  If you can program in C and have some expertise
+in these areas, you might be able to fill the gaps.
+
+Particular areas I know need addressing are :
+
+@enumerate
+@item Porting to other Unices
+
+This involves creating equivalents of sys_solaris.c, sys_linux.c etc for the
+new system.  Note, the Linux driver has been reported as working on a range of
+different architectures (Alpha, Sparc, MIPS as well as x86 of course).
+
+@item Porting to Windows NT
+
+I did a small amount of work on this under Cygwin.  Only the sorting out of the
+include files has really been achieved so far.  The two main areas still to
+address are
+
+@enumerate
+@item The system clock driver.
+@item How to make chronyd into an NT service (i.e. what to replace fork(),
+setsid() etc with so that chronyd can be automatically started in the system
+bootstrap.
+@end enumerate
+
+@item Hardware clock support
+
+@item Automation of the trimrtc and writertc mechanisms
+
+Currently, the RTC trimming mechanism is a manual operation, because there has
+to be a reasonable guarantee that the system will stay up for a reasonable
+length of time afterwards.  (If it is shut down too soon, a poor
+characterisation of the RTC drift rate will be stored on disc, giving a bad
+system clock error when the system is next booted.)
+
+To make chrony more automated for the non-expert user, it would be useful if
+this problem could be avoided so that trimrtc could be done automatically (e.g.
+in a crontab, or as part of the ip-up or ip-down scripts.)
+
+@end enumerate
+@c }}}
+@c }}}
+@c {{{ Ch:Installation
+@node Installation
+@chapter Installation
+
+@c {{{ main introduction text
+The software is distributed as source code which has to be compiled.
+The source code is supplied in the form of a gzipped tar file, which
+unpacks to a subdirectory identifying the name and version of the
+program.
+
+After unpacking the source code, change directory into it, and type
+
+@example
+./configure
+@end example
+
+This is a shell script that automatically determines the system type.
+There is a single optional parameter, @code{--prefix} which indicates
+the directory tree where the software should be installed.  For example,
+
+@example
+./configure --prefix=/opt/free
+@end example
+
+will install the @code{chronyd} daemon into /opt/free/sbin and the
+chronyc control program into /opt/free/bin.  The default value for the
+prefix is /usr/local.
+
+The configure script assumes you want to use gcc as your compiler.
+If you want to use a different compiler, you can configure this way:
+
+@example
+CC=cc CFLAGS=-O ./configure --prefix=/opt/free
+@end example
+
+for Bourne-family shells, or
+    
+@example
+setenv CC cc
+setenv CFLAGS -O
+./configure --prefix=/opt/free
+@end example
+
+for C-family shells.
+
+If the software cannot (yet) be built on your system, an error message
+will be shown.  Otherwise, the files @file{options.h} and
+@file{Makefile} will be generated.
+
+By default, chronyc will be built to make use of the readline library.  If you
+don't want this, specify the --disable-readline flag to configure.  If you have
+readline and/or ncurses installed in a non-standard location, please refer to
+@pxref{readline support} for information.
+
+Now type
+
+@example
+make
+@end example
+
+to build the programs.
+
+If you want to build the manual in plain text, HTML and info versions, type
+
+@example
+make docs
+@end example
+
+Once the programs have been successfully compiled, they need to be
+installed in their target locations.  This step normally needs to be
+performed by the superuser, and requires the following command to be
+entered.
+
+@example
+make install
+@end example
+
+This will install the binaries, plain text manual and manpages.
+
+To install the HTML and info versions of the manual as well, enter the command
+
+@example
+make install-docs
+@end example
+
+If you want chrony to appear in the top level info directory listing, you need
+to run the @command{install-info} command manually after this step.
+@command{install-info} takes 2 arguments.  The first is the path to the
+@file{chrony.info} file you have just installed.  This will be the argument you
+gave to --prefix when you configured (@file{/usr/local} by default), with
+@file{/info/chrony.info} on the end.  The second argument is the location of
+the file called @file{dir}.  This will typically be @file{/usr/info/dir}.  So
+the typical command line would be
+
+@example
+install-info /usr/local/info/chrony.info /usr/info/dir
+@end example
+    
+Now that the software is successfully installed, the next step is to
+set up a configuration file.  The contents of this depend on the
+network environment in which the computer operates.  Typical scenarios
+are described in the following section of the document.
+@c }}}
+@menu
+* readline support::            If readline or ncurses in in a non-standard place
+* package builders::            Extra options useful to package builders
+@end menu
+@c {{{ readline support
+@node readline support
+@section Support for the readline library
+By default, chronyc is built to make use of the readline library.  This allows
+you to use the cursor keys to replay and edit old commands.  If you don't want
+to use readline (in which case chronyc will use a minimal command line
+interface), invoke configure like this:
+
+@example
+./configure --disable-readline other-options...
+@end example
+
+If you have readline and/or ncurses installed in locations that aren't normally searched by the compiler and linker, you need extra options if you want readline to be used:
+
+@table @samp
+@item --with-readline-includes=directory_name
+This defines the name of the directory above the one where @file{readline.h}
+is.  @file{readline.h} is assumed to be in a @file{readline} subdirectory of
+the named directory.
+
+@item --with-readline-library=directory_name
+This defines the directory containing the @file{libreadline.a} or
+@file{libreadline.so} file.
+
+@item --with-ncurses-library=directory_name
+This defines the directory containing the @file{libncurses.a} or
+@file{libncurses.so} file.
+@end table
+
+@c }}}
+@c {{{
+@node package builders
+@section Extra options for package builders
+The configure and make procedures have some extra options that may be useful if
+you are building a distribution package for chrony.
+
+The --infodir=DIR option to configure specifies a different install directory
+for the info files.  This overrides the @file{info} subdirectory of the
+argument to the --prefix option.  For example, you might use
+
+@example
+./configure --prefix=/usr --infodir=/usr/share/info
+@end example
+
+The --mandir=DIR option to configure specifies a different install directory
+for the man pages.  This overrides the @file{man} subdirectory of the
+argument to the --prefix option.
+
+@example
+./configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man
+@end example
+
+to set both options together.
+
+The final option is the DESTDIR option to the make command.  For example, you
+could use the commands
+
+@example
+./configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man
+make all docs
+make install DESTDIR=./tmp
+cd tmp
+tar cvf - . | gzip -9 > chrony.tar.gz
+@end example
+
+to build a package.  When untarred within the root directory, this will install
+the files to the intended final locations.
+
+@c }}}
+
+@c }}}
+@c {{{ Ch:Typical operating scenarios
+@c {{{ Chapter top
+@node Typical scenarios
+@chapter Typical operating scenarios
+@menu
+* Computers on the net::        Your computer is permanently on the Internet (or on
+                                a private network with NTP servers)
+* Infrequent connection::       You connect to the Internet sometimes (e.g. via a modem)
+* Isolated networks::           You have an isolated network with no reference clocks
+* Dial-up home PCs::            Additional considerations if you turn your computer off
+                                when it's not in use.
+* Configuration options overview::  Overview of some configuration options.
+@end menu
+@c }}}
+@c {{{ S:Permanent connection
+@node Computers on the net
+@section Computers connected to the internet
+In this section we discuss how to configure chrony for computers that
+have permanent connections to the internet (or to any network
+containing true NTP servers which ultimately derive their time from a
+reference clock).
+
+To operate in this mode, you will need to know the names of the NTP
+server machines you wish to use.  You may be able to find names of
+suitable servers by one of the following methods:
+
+@itemize @bullet
+@item Your institution may already operate servers on its network.
+Contact your system administrator to find out.
+
+@item Your ISP probably has one or more NTP servers available for its
+customers.
+
+@item Somewhere under the NTP homepage there is a list of public
+stratum 1 and stratum 2 servers.  You should find one or more servers
+that are near to you --- check that their access policy allows you to
+use their facilities.
+@end itemize
+
+Assuming that you have found some servers, you need to set up a
+configuration file to run chrony.  The (compiled-in) default location
+for this file is @file{/etc/chrony.conf}.  Assuming that your ntp
+servers are called @code{a.b.c} and @code{d.e.f}, your
+@file{chrony.conf} file could contain as a minimum
+
+@example
+server a.b.c
+server d.e.f
+server g.h.i
+@end example
+
+However, you will probably want to include some of the other directives
+described later.  The following directives will be particularly useful :
+@code{driftfile}, @code{commandkey}, @code{keyfile}.  The smallest
+useful configuration file would look something like
+
+@example
+server a.b.c
+server d.e.f
+server g.h.i
+keyfile /etc/chrony.keys
+commandkey 1
+driftfile /etc/chrony.drift
+@end example
+@c }}}
+@c {{{ S:Infrequent connection
+@node Infrequent connection
+@section Infrequent connection to true NTP servers
+In this section we discuss how to configure chrony for computers that
+have occasional connections to the internet.
+
+@menu
+* Configuration for infrequent connections::  How to set up the @code{/etc/chrony} file
+* Advising chronyd of internet availability::  How to tell chronyd when the link is available
+@end menu
+
+@node Configuration for infrequent connections
+@subsection Setting up the configuration file for infrequent connections
+As in the previous section, you will need access to NTP servers on the
+internet.  The same remarks apply for how to find them.
+
+In this case, you will need some additional configuration to tell
+@code{chronyd} when the connection to the internet goes up and down.
+This saves the program from continuously trying to poll the servers when
+they are inaccessible.
+
+Again, assuming that your ntp servers are called @code{a.b.c} and
+@code{d.e.f}, your @file{chrony.conf} file would need to contain
+something like
+
+@example
+server a.b.c
+server d.e.f
+server g.h.i
+@end example
+
+However, the following issues need to be addressed:
+
+@enumerate 1
+@item
+Your computer probably doesn't have DNS access whilst offline to turn
+the machine names into IP addresses.
+@item
+Your computer will keep trying to contact the servers to obtain
+timestamps, even whilst offline.  If you operate a dial-on-demand
+system, things are even worse, because the link to the internet will
+keep getting established.
+@end enumerate
+
+For this reason, it would be better to specify this part of your
+configuration file in the following way:
+
+@example
+server 1.2.3.4 offline
+server 5.6.7.8 offline
+server 9.10.11.12 offline
+@end example
+
+Because numeric IP addresses have been used, the first problem is
+overcome.  The @code{offline} keyword indicates that the servers start
+in an offline state, and that they should not be contacted until @code{chronyd}
+receives notification that the link to the internet is present.
+
+An alternative is to use the names of the NTP servers, and put entries for them
+into your @file{/etc/hosts} file.  This will be OK as long as @samp{files}
+comes before @samp{dns} in the @samp{hosts} line of the
+@file{/etc/nsswitch.conf} file.
+
+In order to notify @code{chronyd} of the presence of the link, you will need to
+be able to log in to it with the program chronyc.  To do this, @code{chronyd}
+needs to be configured with an administrator password.  To set up an
+administrator password, you can create a file @file{/etc/chrony.keys}
+containing a single line
+
+@example
+1 xyzzy
+@end example
+
+and add the following line to @file{/etc/chrony.conf} (the order of the
+lines does not matter)
+
+@example
+commandkey 1
+@end example
+
+The smallest useful configuration file would look something like
+
+@example
+server 1.2.3.4 offline
+server 5.6.7.8 offline
+server 9.10.11.12 offline
+keyfile /etc/chrony.keys
+commandkey 1
+driftfile /etc/chrony.drift
+@end example
+
+The next section describes how to tell @code{chronyd} when the internet link
+goes up and down.
+
+@node Advising chronyd of internet availability
+@subsection How to tell chronyd when the internet link is available.
+To use this option, you will need to configure a command key in
+@code{chronyd's} configuration file @file{/etc/chrony.conf}, as described in
+the previous section.
+
+To tell @code{chronyd} when to start and finish sampling the servers, the
+@code{online} and @code{offline} commands of chronyc need to be used.
+To give an example of their use, we assume that @code{pppd} is the
+program being used to connect to the internet, and that chronyc has been
+installed at its default location @file{/usr/local/bin/chronyc}.  We
+also assume that the command key has been set up as described in the
+previous section.
+
+In the file @file{/etc/ppp/ip-up} we add the command sequence
+
+@example
+/usr/local/bin/chronyc <<EOF
+password xyzzy
+online
+EOF
+@end example
+
+and in the file @file{/etc/ppp/ip-down} we add the sequence
+
+@example
+/usr/local/bin/chronyc <<EOF 
+password xyzzy
+offline
+EOF
+@end example
+
+@code{chronyd's} polling of the servers will now only occur whilst the
+machine is actually connected to the Internet.
+@c }}}
+@c {{{ S:Isolated networks
+@node Isolated networks
+@section Isolated networks
+In this section we discuss how to configure chrony for computers that
+never have network conectivity to any computer which ultimately derives
+its time from a reference clock.
+
+In this situation, one computer is selected to be the master timeserver.
+The other computers are either direct clients of the master, or clients
+of clients.
+
+The rate value in the master's drift file needs to be set to the average
+rate at which the master gains or loses time.  @code{chronyd} includes
+support for this, in the form of the @code{manual} directive in the
+configuration file and the @code{settime} command in the @code{chronyc}
+program.
+
+If the master is rebooted, @code{chronyd} can re-read the drift rate
+from the drift file.  However, the master has no accurate estimate of
+the current time.  To get around this, the system can be configured so
+that the master can initially set itself to a `majority-vote' of
+selected clients' times; this allows the clients to `flywheel' the
+master across its outage.
+
+A typical configuration file for the master (called @code{master}) might
+be (assuming the clients are in the 192.168.165.x subnet and that the
+master's address is 192.168.169.170)
+
+@example
+driftfile /etc/chrony.drift
+commandkey 25
+keyfile /etc/chrony.keys
+initstepslew 10 client1 client3 client6
+local stratum 8
+manual
+allow 192.168.165
+@end example
+
+For the clients that have to resynchronise the master when it restarts,
+the configuration file might be
+
+@example
+server master
+driftfile /etc/chrony.drift
+logdir /var/log/chrony
+log measurements statistics tracking
+keyfile /etc/chrony.keys
+commandkey 24
+local stratum 10
+initstepslew 20 master
+allow 192.168.169.170
+@end example
+
+The rest of the clients would be the same, except that the @code{local}
+and @code{allow} directives are not required.
+@c }}}
+@c {{{ S:Dial-up home PCs
+@node Dial-up home PCs
+@section The home PC with a dial-up connection
+
+@menu
+* Dial-up overview::            General discussion of how the software operates in this mode
+* Dial-up configuration::       Typical configuration files
+@end menu
+
+@node Dial-up overview
+@subsection Assumptions/how the software works
+This section considers the home computer which has a dial-up connection.
+It assumes that Linux is run exclusively on the computer.  Dual-boot
+systems may work; it depends what (if anything) the other system does to
+the system's real-time clock.
+
+Much of the configuration for this case is discussed earlier
+(@pxref{Infrequent connection}).  This section addresses specifically
+the case of a computer which is turned off between 'sessions'.
+
+In this case, @code{chronyd} relies on the computer's real-time clock
+(RTC) to maintain the time between the periods when it is powered up.
+The arrangement is shown in the figure below.
+
+@example
+@group
+            trim if required                          PSTN
+      +---------------------------+               +----------+
+      |                           |               |          |
+      v                           |               |          |
++---------+                    +-------+       +-----+     +---+
+| System's|  measure error/    |chronyd|       |modem|     |ISP|
+|real-time|------------------->|       |-------|     |     |   |
+|  clock  |   drift rate       +-------+       +-----+     +---+
++---------+                       ^                          |
+      |                           |                          |
+      +---------------------------+                  --o-----o---
+         set time at boot up                           |
+                                                  +----------+
+                                                  |NTP server|
+                                                  +----------+
+@end group
+@end example
+
+When the computer is connected to the Internet (via the modem),
+@code{chronyd} has access to external NTP servers which it makes
+measurements from.  These measurements are saved, and straight-line fits
+are performed on them to provide an estimate of the computer's time
+error and rate of gaining/losing time.
+
+When the computer is taken offline from the Internet, the best estimate
+of the gain/loss rate is used to free-run the computer until it next
+goes online.
+
+Whilst the computer is running, @code{chronyd} makes measurements of the
+real-time clock (RTC) (via the @file{/dev/rtc} interface, which must be
+compiled into the kernel).  An estimate is made of the RTC error at a
+particular RTC second, and the rate at which the RTC gains or loses time
+relative to true time.
+
+The RTC is fully supported in 2.2 and 2.4 kernels.
+
+For kernels in the 2.0 series prior to 2.0.32, the kernel was set up to
+trim the RTC every 11 minutes.  This would be disasterous for
+@code{chronyd} -- there is no reliable way of synchronising with this
+trimming. For this reason, @code{chronyd} only supports the RTC in 2.0
+kernels from v2.0.32 onwards.
+
+When the computer is powered down, the measurement histories for all the
+NTP servers are saved to files (if the @code{dumponexit} directive is
+specified in the configuration file), and the RTC tracking information
+is also saved to a file (if the @code{rtcfile} directive has been
+specified).  These pieces of information are also saved if the
+@code{dump} and @code{writertc} commands respectively are issued through
+@code{chronyc}.
+
+When the computer is rebooted, @code{chronyd} reads the current RTC time
+and the RTC information saved at the last shutdown.  This information is
+used to set the system clock to the best estimate of what its time would
+have been now, had it been left running continuously.  The measurement
+histories for the servers are then reloaded.
+
+The next time the computer goes online, the previous sessions'
+measurements can contribute to the line-fitting process, which gives a
+much better estimate of the computer's gain/loss rate.
+
+One problem with saving the measurements and RTC data when the machine
+is shut down is what happens if there is a power failure; the most
+recent data will not be saved.  Although @code{chronyd} is robust enough
+to cope with this, some performance may be lost.  (The main danger
+arises if the RTC has been changed during the session, with the
+@code{trimrtc} command in @code{chronyc}.  Because of this,
+@code{trimrtc} will make sure that a meaningful RTC file is saved out
+after the change is completed).
+
+The easiest protection against power failure is to put the @code{dump}
+and @code{writertc} commands in the same place as the @code{offline}
+command is issued to take @code{chronyd} offline; because @code{chronyd}
+free-runs between online sessions, no parameters will change
+significantly between going offline from the Internet and any power
+failure.
+
+A final point regards home computers which are left running for extended
+periods and where it is desired to spin down the hard disc when it is
+not in use (e.g. when not accessed for 15 minutes).  @code{chronyd} has
+been planned so it supports such operation; this is the reason why the
+RTC tracking parameters are not saved to disc after every update, but
+only when the user requests such a write, or during the shutdown
+sequence.  The only other facility that will generate periodic writes to
+the disc is the @code{log rtc} facility in the configuration file; this
+option should not be used if you want your disc to spin down.
+
+@node Dial-up configuration
+@subsection Typical configuration files.
+
+To illustrate how a dial-up home computer might be configured, example
+configuration files are shown in this section.
+
+For the @file{/etc/chrony.conf} file, the following can be used as an
+example.  @emph{NOTE : The @code{server} directives are only applicable
+to customers of Demon Internet; users of other ISPs will need to use
+their own ISP's NTP servers or public NTP servers.}
+
+@example
+server 158.152.1.65 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+server 158.152.1.76 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+server 194.159.253.2 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+logdir /var/log/chrony
+log statistics measurements tracking
+driftfile /etc/chrony.drift
+keyfile /etc/chrony.keys
+commandkey 25
+maxupdateskew 100.0
+dumponexit
+dumpdir /var/log/chrony
+rtcfile /etc/chrony.rtc
+@end example
+
+With Freeserve as the ISP, I use the following server lines :
+
+@example
+server 194.152.64.68 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+server 194.152.64.35 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+server 194.152.64.34 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+@end example
+
+I use @code{pppd} for connecting to my ISP.  This runs two scripts
+@file{/etc/ppp/ip-up} and @file{/etc/ppp/ip-down} when the link goes
+online and offline respectively.
+
+The relevant part of the @file{/etc/ppp/ip-up} file is (with a dummy
+password)
+
+@example
+/usr/local/bin/chronyc <<EOF
+password xxxxxxxx
+online
+EOF
+@end example
+
+and the relevant part of the @file{/etc/ppp/ip-down} script is
+
+@example
+/usr/local/bin/chronyc <<EOF
+password xxxxxxxx
+offline
+dump
+writertc
+EOF
+@end example
+
+(Because they have to contain the administrator password, it would be
+desirable to make the files readable only by root on a multiuser
+machine).
+
+To start @code{chronyd} during the boot sequence, I have the following
+in @file{/etc/rc.d/rc.local} (this is a Slackware system)
+
+@example
+if [ -f /usr/local/sbin/chronyd -a -f /etc/chrony.conf ]; then
+  /usr/local/sbin/chronyd -r -s
+  echo "Start chronyd"
+fi
+@end example
+
+The placement of this command may be important on some systems.  In
+particular, @code{chronyd} may need to be started several seconds (about
+10 as a minimum) before any software that depends on the system clock
+not jumping or moving backwards, depending on the directives in
+@code{chronyd's} configuration file.
+
+For the system shutdown, @code{chronyd} should receive a SIGTERM several
+seconds before the final SIGKILL; the SIGTERM causes the measurement
+histories and RTC information to be saved out.  There should be no need
+to add anything to the shutdown sequence, unless (as my system had)
+there is no pause between the SIGTERM and SIGKILL being delivered to the
+remaining processes.  So if you find something like
+
+@example
+killall5 -15 
+killall5 -9
+@end example
+
+in your @code{/etc/rc.d/rc.0} script, you will need to insert a sleep, e.g.
+
+@example
+killall5 -15 
+sleep 5
+killall5 -9
+@end example
+
+Otherwise, @code{chronyd} will not always save information on shutdown,
+which could be a problem if you don't use @code{dump} and
+@code{writertc} when you go offline.
+@c }}}
+@c {{{ S:Other config options
+@node Configuration options overview
+@section Other important configuration options
+The most common option to include in the configuration file is the
+@code{driftfile} option.  One of the major tasks of @code{chronyd} is to
+work out how fast or how slow the system clock runs relative to real
+time - e.g. in terms of seconds gained or lost per day.  Measurements
+over a long period are usually required to refine this estimate to an
+acceptable degree of accuracy.  Therefore, it would be bad if
+@code{chronyd} had to work the value out each time it is restarted,
+because the system clock would not run so accurately whilst the
+determination is taking place.
+
+To avoid this problem, @code{chronyd} allows the gain or loss rate to be
+stored in a file, which can be read back in when the program is
+restarted.  This file is called the drift file, and might typically be
+stored in @file{/etc/chrony.drift}.  By specifying an option like the
+following
+
+@example
+driftfile /etc/chrony.drift
+@end example
+
+in the configuration file (@file{/etc/chrony.conf}), the drift file
+facility will be activated.
+@c }}}
+@c }}}
+@c {{{ Ch:Usage reference
+@node Usage reference
+@chapter Usage reference
+
+@c {{{ Chapter top
+@menu
+* Starting chronyd::            Command line options for the daemon
+* Configuration file::          Format of the configuration file
+* Running chronyc::             The run-time configuration program
+@end menu
+@c }}}
+@c {{{ S:Starting chronyd
+@node Starting chronyd
+@section Starting chronyd
+If @code{chronyd} has been installed to its default location
+@file{/usr/local/sbin/chronyd}, starting it is simply a matter of
+entering the command
+
+@example
+/usr/local/sbin/chronyd
+@end example
+
+Information messages and warnings will be logged to syslog.
+
+The command line options supported are as follows:
+
+@table @code
+@item -d
+When run in this mode, the program will not detach itself from the
+terminal, and all messages will be sent to the terminal instead of to
+syslog.
+@item -f <conf-file>
+This option can be used to specify an alternate location for the
+configuration file (default @file{/etc/chrony.conf}).
+@item -r
+This option will reload sample histories for each of the servers being
+used.  These histories are created by using the @code{dump} command in
+@code{chronyc}, or by setting the @code{dumponexit} directive in the
+configuration file.  This option is useful if you want to stop and
+restart @code{chronyd} briefly for any reason, e.g. to install a new
+version.  However, it only makes sense on systems where the kernel can
+maintain clock compensation whilst not under @code{chronyd's} control.
+The only version where this happens so far is Linux.  On systems where
+this is not the case, e.g. Solaris and SunOS the option should not be
+used.
+@item -s
+This option will set the system clock from the computer's real-time
+clock.  This is analogous to supplying the `-s' flag to the
+@file{/sbin/clock} program during the Linux boot sequence.
+
+Support for real-time clocks is limited at present - the criteria are
+described in the section on the @code{rtcfile} directive (@pxref{rtcfile
+directive}).
+
+If @code{chronyd} cannot support the real time clock on your computer,
+this option cannot be used and a warning message will be logged to the
+syslog.
+
+If used in conjunction with the `-r' flag, @code{chronyd} will attempt
+to preserve the old samples after setting the system clock from the real
+time clock.  This can be used to allow @code{chronyd} to perform long
+term averaging of the gain or loss rate across system reboots, and is
+useful for dial-up systems that are shut down when not in use.  For this
+to work well, it relies on @code{chronyd} having been able to determine
+accurate statistics for the difference between the real time clock and
+system clock last time the computer was on.
+
+@item -v
+This option displays @code{chronyd's} version number to the terminal and
+exits.
+@end table
+
+On systems that support an @file{/etc/rc.local} file for starting
+programs at boot time, @code{chronyd} can be started from there.
+
+On systems with a System V style initialisation (e.g. Solaris), a
+suitable start/stop script might be as shown below.  This might be
+placed in the file @file{/etc/rc2.d/S83chrony}.
+
+@example
+@group
+#!/bin/sh
+# This file should have uid root, gid sys and chmod 744
+#
+
+killproc() @{            # kill the named process(es)
+        pid=`/usr/bin/ps -e |
+             /usr/bin/grep -w $1 |
+             /usr/bin/sed -e 's/^  *//' -e 's/ .*//'`
+        [ "$pid" != "" ] && kill $pid
+@}
+
+case "$1" in
+
+'start')
+   if [ -f /opt/free/sbin/chronyd -a -f /etc/chrony.conf ]; then
+     /opt/free/sbin/chronyd
+   fi
+   ;;
+'stop')
+   killproc chronyd
+   ;;
+*)
+   echo "Usage: /etc/rc2.d/S83chrony @{ start | stop @}"
+   ;;
+esac
+@end group
+@end example
+
+(In both cases, you may want to bear in mind that @code{chronyd} can
+step the time when it starts.  There may be other programs started at
+boot time that could be upset by this, so you may need to consider the
+ordering carefully.  However, @code{chronyd} will need to start after
+daemons providing services that it may require, e.g. the domain name
+service.)
+@c }}}
+@c {{{ S:chronyd configuration file
+@node Configuration file
+@section The chronyd configuration file
+@c {{{ section top
+The configuration file is normally called @file{/etc/chrony.conf}; in
+fact, this is the compiled-in default. However, other locations can be
+specified with a command line option.
+
+Each command in the configuration file is placed on a separate line.
+The following sections describe each of the commands in turn.  The
+directives can occur in any order in the file.
+
+@menu
+* comments in config file::     How to write a comment
+* acquisitionport directive::   Set port to use for initial time probes
+* allow directive::             Give access to NTP clients
+* bindaddress directive::       Limit the network interface that is used for NTP
+* bindcmdaddress directive::    Limit the network interface that is used for commands
+* broadcast directive::         Make chronyd act as an NTP broadcast server
+* cmdallow directive::          Give control access to chronyc on other computers
+* cmddeny directive::           Deny control access to chronyc on other computers
+* commandkey directive::        Set runtime command key
+* cmdport directive::           Set port to use for runtime commanding
+* deny directive::              Deny access to NTP clients
+* driftfile directive::         Specify location of file containing drift data
+* dumpdir directive::           Specify directory for dumping measurements
+* dumponexit directive::        Dump measurements when daemon exits
+* initstepslew directive::      Trim the system clock on boot-up.
+* keyfile directive::           Specify location of file containing keys
+* linux_hz directive::          Define a non-standard value of the kernel HZ constant
+* linux_freq_scale directive::  Define a non-standard value to compensate the kernel frequency bias
+* local directive::             Allow unsynchronised machine to act as server
+* log directive::               Make daemon log certain sets of information
+* logchange directive::         Generate syslog messages if large offsets occur
+* logdir directive::            Specify directory for logging
+* mailonchange directive::      Send email if a clock correction above a threshold occurs
+* manual directive::            Allow manual entry using chronyc's settime cmd.
+* maxupdateskew directive::     Stop bad estimates upsetting machine clock
+* noclientlog directive::       Prevent chronyd from gathering data about clients
+* peer directive::              Specify an NTP peer
+* pidfile directive::           Specify the file where chronyd's pid is written
+* port directive::              Set port to use for NTP packets
+* rtcdevice directive::         Specify name of enhanced RTC device (if not /dev/rtc)
+* rtcfile directive::           Specify the file where real-time clock data is stored
+* rtconutc directive::          Specify that the real time clock keeps UTC not local time
+* server directive::            Specify an NTP server
+@end menu
+@c }}}
+@c {{{ comments in config file
+@node comments in config file
+@subsection Comments in the configuration file
+The configuration file may contain comment lines.  A comment line is any line
+that starts with zero or more spaces followed by any one of the following
+characters:
+@itemize
+@item !
+@item ;
+@item #
+@item %
+@end itemize
+Any line with this format will be ignored.
+@c }}}
+@c {{{ acquisitionport directive
+@node acquisitionport directive
+@subsection acquisitionport
+@code{chronyd} uses a separate client-side port for the rapid-fire
+measurements requested with the @code{initstepslew} directive
+(@pxref{initstepslew directive}).  Normally, that port is chosen
+arbitrarily by the operating system.  However, you can use
+@code{acquisitionport} to explicitly specify a port.  This may be useful
+for getting through firewalls.
+
+Do not make acquisition and regular NTP service (@pxref{port directive})
+use the same port.
+
+An example of the @code{acquisitionport} command is
+
+@example
+acquisitionport 1123
+@end example
+
+This would change the port used for rapid queries to udp/1123.  You
+could then persuade the firewall administrator to let that port through.
+@c }}}
+@c {{{ allow
+@node allow directive
+@subsection allow
+The @code{allow} command is used to designate a particular subnet from
+which NTP clients are allowed to access the computer as an NTP server.
+
+The default is that no clients are allowed access, i.e. @code{chronyd}
+operates purely as an NTP client.  If the @code{allow} directive is
+used, @code{chronyd} will be both a client of its servers, and a server
+to other clients.
+
+Examples of use of the command are as follows:
+
+@example
+allow foo.bar.com
+allow 1.2
+allow 3.4.5
+allow 6.7.8/22
+allow 6.7.8.9/22
+allow
+@end example
+
+The first command allows the named node to be an NTP client of this computer.
+The second command allows any node with an IP address of the form 1.2.x.y (with
+x and y arbitrary) to be an NTP client of this computer.  Likewise, the third
+command allows any node with an IP address of the form 3.4.5.x to have client
+NTP access.  The fourth and fifth forms allow access from any node with an IP
+address of the form 6.7.8.x, 6.7.9.x, 6.7.10.x or 6.7.11.x (with x arbitrary),
+i.e. the value 22 is the number of bits defining the specified subnet.  (In the
+fifth form, the final byte is ignored).  The sixth form allows access by any
+node on the entire Internet.
+
+A second form of the directive, @code{allow all}, has a greater effect,
+depending on the ordering of directives in the configuration file.  To
+illustrate the effect, consider the two examples
+
+@example
+allow 1.2.3.4
+deny 1.2.3
+allow 1.2
+@end example
+
+and 
+
+@example
+allow 1.2.3.4
+deny 1.2.3
+allow all 1.2
+@end example
+
+In the first example, the effect is the same regardles of what order the
+three directives are given in.  So the 1.2.x.y subnet is allowed access,
+except for the 1.2.3.x subnet, which is denied access, however the host
+1.2.3.4 is allowed access.
+
+In the second example, the @code{allow all 1.2} directives overrides the
+effect of @emph{any} previous directive relating to a subnet within the
+specified subnet.  Within a configuration file this capability is
+probably rather moot; however, it is of greater use for reconfiguration
+at run-time via @code{chronyc} (@pxref{allow all command}).
+
+Note, if the @code{initstepslew} directive (@pxref{initstepslew
+directive}) is used in the configuration file, each of the computers
+listed in that directive must allow client access by this computer for
+it to work.
+@c }}}
+@c {{{ bindaddress
+@node bindaddress directive
+@subsection bindaddress
+The bindaddress allows you to restrict the network interface to which
+chronyd will listen for NTP packets.  This provides an additional level of
+access restriction above that available through the 'deny' mechanism.
+
+Suppose you have a local ethernet with addresses in the 192.168.1.0
+subnet together with a dial-up connection.  The ethernet interface's IP
+address is 192.168.1.1.  Suppose (for some reason) you want to block all
+access through the dialup connection (note, this will even block replies
+from servers on the dialup side, so you will not be able to synchronise
+to an external source).  You could add the line
+
+@example
+bindaddress 192.168.1.1
+@end example
+
+to the configuration file.
+
+This directive affects NTP (UDP port 123) packets.  If no @code{bindcmdaddress}
+directive is present, the address supplied by @code{bindaddress} will be used
+to control binding of the command socket (UDP port 323) as well.
+
+The @code{bindaddress} directive has been found to cause problems when used on
+computers that need to pass NTP traffic over multiple network interfaces (e.g.
+firewalls).  It is, therefore, not particularly useful.  Use of the
+@code{allow} and @code{deny} directives together with a network firewall is
+more likely to be successful.
+
+@c }}}
+@c {{{ bindcmdaddress
+@node bindcmdaddress directive
+@subsection bindcmdaddress
+The bindcmdaddress allows you to restrict the network interface to which
+chronyd will listen for command packets (issued by chronyc).
+
+Suppose you have a local ethernet with addresses in the 192.168.1.0 subnet
+together with a dial-up connection.  The ethernet interface's IP address is
+192.168.1.1.  Suppose you want to block all access through the dialup
+connection.  You could add the line
+
+@example
+bindcmdaddress 192.168.1.1
+@end example
+
+to the configuration file.
+
+The @code{bindcmdaddress} directive has been found to cause problems when used
+on computers that need to pass command traffic over multiple network
+interfaces.  It is, therefore, not particularly useful.  Use of the
+@code{cmdallow} and @code{cmddeny} directives together with a network firewall
+is more likely to be successful.
+
+@c }}}
+@c {{{ broadcast directive
+@node broadcast directive
+@subsection broadcast
+The @code{broadcast} directive is used to declare a broadcast address to which
+chronyd should send packets in NTP broadcast mode (i.e. make chronyd act as a
+broadcast server).  Broadcast clients on that subnet will be able to
+synchronise.
+
+The syntax is as follows
+
+@example
+broadcast 30 192.168.1.255
+broadcast 60 192.168.2.255 12123
+@end example
+
+In the first example, the destination port defaults to 123/udp (the normal NTP
+port).  In the second example, the destionation port is specified as 12123.
+The first parameter in each case (30 or 60 respectively) is the interval in
+seconds between broadcast packets being sent.  The second parameter in each
+case is the broadcast address to send the packet to.  This should correspond to
+the broadcast address of one of the network interfaces on the computer where
+chronyd is running.
+
+You can have more than 1 @code{broadcast} directive if you have more than 1
+network interface onto which you wish to send NTP broadcast packets.
+
+Chronyd itself cannot currently act as a broadcast client; it must always be
+configured as a point-to-point client by defining specific NTP servers and
+peers.  This broadcast server feature is intended for providing a time source
+to other NTP software (e.g. various MS Windows clients).
+
+If xntpd is used as the broadcast client, it will try to use a point-to-point
+client/server NTP access to measure the round-trip delay.  Thus, the broadcast
+subnet should also be the subject of an @code{allow} directive (@pxref{allow
+directive}).
+@c }}}
+@c {{{ cmdallow
+@node cmdallow directive
+@subsection cmdallow
+
+This is similar to the @code{allow} directive (@pxref{allow directive}), except
+that it allows control access (rather than NTP client access) to a particular
+subnet or host.  (By 'control access' is meant that chronyc can be run on those
+hosts and successfully connect to chronyd on this computer.)
+
+The syntax is identical to the @code{allow} directive.
+
+There is also a @code{cmdallow all} directive with similar behaviour to the
+@code{allow all} directive (but applying to control access in this case, of
+course).
+@c }}}
+@c {{{ cmddeny
+@node cmddeny directive
+@subsection cmddeny
+
+This is similar to the @code{cmdallow} directive (@pxref{cmdallow directive}),
+except that it denies control access to a particular subnet or host,
+rather than allowing it.
+
+The syntax is identical.
+
+There is also a @code{cmddeny all} directive with similar behaviour to the
+@code{cmdallow all} directive.
+@c }}}
+@c {{{ commandkey
+@node commandkey directive
+@subsection commandkey
+The commandkey command is used to set the key number used for
+authenticating user commands via the chronyc program at run time.
+This allows certain actions of the chronyc program to be restricted to
+administrators.
+
+An example of the commandkey command is
+
+@example
+commandkey 20
+@end example
+
+In the key file (see the keyfile command) there should be a line of
+the form
+
+@example
+20 foobar
+@end example
+
+When running the chronyc program to perform run-time configuration,
+the command
+
+@example
+password foobar
+@end example
+
+must be entered before any commands affecting the operation of the
+daemon can be entered.
+@c }}}
+@c {{{ cmdport
+@node cmdport directive
+@subsection cmdport
+
+The @code{cmdport} directive allows the port that is used for run-time
+command and monitoring (via the program @code{chronyc}) to be altered
+from its default (323/udp).
+
+An example shows the syntax
+
+@example
+cmdport 257
+@end example
+
+This would make @code{chronyd} use 257/udp as its command port.
+(@code{chronyc} would need to be run with the @code{-p 257} switch to
+inter-operate correctly).
+@c }}}
+@c {{{ deny
+@node deny directive
+@subsection deny
+
+This is similar to the @code{allow} directive (@pxref{allow directive}),
+except that it denies NTP client access to a particular subnet or host,
+rather than allowing it.
+
+The syntax is identical.
+
+There is also a @code{deny all} directive with similar behaviour to the
+@code{allow all} directive.
+@c }}}
+@c {{{ driftfile
+@node driftfile directive
+@subsection driftfile
+One of the main activities of the @code{chronyd} program is to work out
+the rate at which the system clock gains or loses time relative to real
+time.
+
+Whenever @code{chronyd} computes a new value of the gain/loss rate, it
+is desirable to record it somewhere.  This allows @code{chronyd} to
+begin compensating the system clock at that rate whenever it is
+restarted, even before it has had a chance to obtain an equally good
+estimate of the rate during the new run.  (This process may take many
+minutes, at least).
+
+The driftfile command allows a file to be specified into which
+@code{chronyd} can store the rate information.  Two parameters are
+recorded in the file.  The first is the rate at which the system clock
+gains or loses time, expressed in parts per million, with gains
+positive.  Therefore, a value of 100.0 indicates that when the system
+clock has advanced by a second, it has gained 100 microseconds on
+reality (so the true time has only advanced by 999900 microseconds).
+The second is an estimate of the error bound around the first value in
+which the true rate actually lies.
+
+An example of the driftfile command is
+
+@example
+driftfile /etc/chrony.drift
+@end example
+@c }}}
+@c {{{ dumpdir
+@node dumpdir directive
+@subsection dumpdir
+To compute the rate of gain or loss of time, @code{chronyd} has to store
+a measurement history for each of the time sources it uses.
+
+Certain systems (so far only Linux) have operating system support for
+setting the rate of gain or loss to compensate for known errors.  (On
+other systems, @code{chronyd} must simulate such a capability by
+periodically slewing the system clock forwards or backwards by a
+suitable amount to compensate for the error built up since the previous
+slew).
+
+For such systems, it is possible to save the measurement history across
+restarts of @code{chronyd} (assuming no changes are made to the system
+clock behaviour whilst it is not running).  If this capability is to be
+used (via the dumponexit command in the configuration file, or the dump
+command in chronyc), the dumpdir command should be used to define the
+directory where the measurement histories are saved.
+
+An example of the command is
+
+@example
+dumpdir /var/log/chrony
+@end example
+
+A source whose IP address is 1.2.3.4 would have its measurement
+history saved in the file @file{/var/log/chrony/1.2.3.4.dat}.
+@c }}}
+@c {{{ dumponexit
+@node dumponexit directive
+@subsection dumponexit
+If this command is present, it indicates that @code{chronyd} should save
+the measurement history for each of its time sources recorded whenever
+the program exits.  (See the dumpdir command above).
+@c }}}
+@c {{{ initstepslew
+@node initstepslew directive
+@subsection initstepslew
+In normal operation, @code{chronyd} always slews the time when it needs to
+adjust the system clock.  For example, to correct a system clock which
+is 1 second slow, @code{chronyd} slightly increases the amount by which the
+system clock is advanced on each clock interrupt, until the error is
+removed.  (Actually, this is done by calling the @code{adjtime()} or
+similar system function which does it for us.)  Note that at no time
+does time run backwards with this method.
+
+On most Unix systems it is not desirable to step the system clock,
+because many programs rely on time advancing monotonically forwards.
+
+When the @code{chronyd} daemon is initially started, it is possible that the
+system clock is considerably in error.  Attempting to correct such an
+error by slewing may not be sensible, since it may take several hours
+to correct the error by this means.
+
+The purpose of the @code{initstepslew} directive is to allow @code{chronyd} to
+make a rapid measurement of the system clock error at boot time, and to
+correct the system clock by stepping before normal operation begins.
+Since this would normally be performed only at an appropriate point in
+the system boot sequence, no other software should be adversely affected
+by the step.
+
+If the correction required is less than a specified threshold, a slew is
+used instead.  This makes it easier to restart @code{chronyd} whilst the
+system is in normal operation.
+
+The @code{initstepslew} directive takes a threshold and a list of NTP
+servers as arguments.  A maximum of 8 will be used.  Each of the servers
+is rapidly polled several times, and a majority voting mechanism used to
+find the most likely range of system clock error that is present.  A
+step (or slew) is applied to the system clock to correct this error.
+@code{chronyd} then enters its normal operating mode (where only slews are
+used).
+
+An example of use of the command is
+
+@example
+initstepslew 30 foo.bar.com baz.quz.com
+@end example
+
+where 2 NTP servers are used to make the measurement.  The @code{30}
+indicates that if the system's error is found to be 30 seconds or less,
+a slew will be used to correct it; if the error is above 30 seconds, a
+step will be used.
+
+The @code{initstepslew} directive can also be used in an isolated LAN
+environment, where the clocks are set manually.  The most stable
+computer is chosen as the master, and the other computers are slaved to
+it.  If each of the slaves is configured with the local option (see
+below), the master can be set up with an @code{initstepslew} directive
+which references some or all of the slaves.  Then, if the master machine
+has to be rebooted, the slaves can be relied on to 'flywheel' the time
+for the master.
+@c }}}
+@c {{{ keyfile
+@node keyfile directive
+@subsection keyfile
+This command is used to specify the location of the file containing
+ID/key pairs for the following 2 uses:
+
+@itemize @bullet 
+@item Authentication of NTP packets.
+@item Authentication of administrator commands entered via chronyc.
+@end itemize
+
+The format of the command is shown in the example below
+
+@example
+keyfile /etc/chrony.keys
+@end example
+
+The argument is simply the name of the file containing the ID/key
+pairs.  The format of the file is shown below
+
+@example
+10 tulip
+11 hyacinth
+20 crocus
+25 iris
+ ...
+@end example
+
+Each line consists of an ID and a password.  The ID can be any
+unsigned integer in the range 0 through 2**32-1.  The password can be
+any string of characters not containing a space.
+
+For NTP use, the MD5 authentication scheme is always used.  This must be
+borne in mind if @code{chronyd} is to inter-operate in authenticated
+mode with @code{xntpd} running on other computers.
+
+The ID for the chronyc authentication key is specified with the
+commandkey command (see earlier).
+@c }}}
+@c {{{ local
+@node local directive
+@subsection local
+The local keyword is used to allow @code{chronyd} to appear synchronised
+to real time (from the viewpoint of clients polling it), even if it has
+no current synchronisation source.
+
+This option is normally used on computers in an isolated network,
+where several computers are required to synchronise to one other, this
+being the "master" which is kept vaguely in line with real time by
+manual input.
+
+An example of the command is 
+
+@example
+local stratum 10
+@end example
+
+The value 10 may be substituted with other values in the range 1
+through 15.  Stratum 1 indicates a computer that has a true real-time
+reference directly connected to it (e.g. GPS, atomic clock etc)
+&ndash; such computers are expected to be very close to real time.
+Stratum 2 computers are those which have a stratum 1 server; stratum 3
+computers have a stratum 2 server and so on.
+
+A large value of 10 indicates that the clock is so many hops away from
+a reference clock that its time is fairly unreliable.  Put another
+way, if the computer ever has access to another computer which is
+ultimately synchronised to a reference clock, it will almost certainly
+be at a stratum less than 10.  Therefore, the choice of a high value
+like 10 for the local command prevents the machine's own time from
+ever being confused with real time, were it ever to leak out to
+clients that have visibility of real servers.
+@c }}}
+@c {{{ linux_hz
+@node linux_hz directive
+@subsection linux_hz
+(This option only applies to Linux).
+
+By default, chronyd will find the value of @code{HZ} from a kernel header file
+at compile time.  @code{HZ} is the nominal number of timer interrupts per
+second.  If you're running chronyd on the system where it was built, the value
+it has should be right, and you don't need to worry about this option.
+
+This option is provided for people who move a pre-built chronyd onto a system
+where the value of HZ in the kernel headers has been changed from the default
+value.
+
+An example of the command is
+
+@example
+linux_hz 100
+@end example
+@c }}}
+@c {{{ linux_freq_scale
+@node linux_freq_scale directive
+@subsection linux_freq_scale
+(This option only applies to Linux).
+
+By default, chronyd will find the value of @code{HZ} and @code{SHIFT_HZ} from
+kernel header files at compile time.  An internal value called
+@code{freq_scale} is calculated from this.  By default it is (1<<SHIFT_HZ)/HZ,
+except for the case HZ=100, when special case code is used which leads to the
+value 128/128.125.  If you're running chronyd on the system where it was built,
+the value it has should be right, and you don't need to worry about this
+option.
+
+This option is provided for people who move a pre-built chronyd onto a system
+where the method by which the kernel computes the reciprocal of this value has been changed or where the HZ and SHIFT_HZ constants differ from those on the system where chronyd was built.
+
+An example of the command is
+
+@example
+linux_freq_scale 0.99902439
+@end example
+@c }}}
+@c {{{ log
+@node log directive
+@subsection log
+@c {{{ section top
+The log command indicates that certain information is to be logged.
+
+@table @code
+@item measurements
+This option logs the raw NTP measurements and related information to a
+file called measurements.log.
+
+@item statistics
+This option logs information about the regression processing to a file
+called statistics.log.
+
+@item tracking
+This option logs changes to the estimate of the system's gain or loss
+rate, and any slews made, to a file called tracking.log.
+
+@item rtc
+This option logs information about the system's real-time clock.
+@end table
+
+The files are written to the directory specified by the logdir
+command.
+
+An example of the command is
+
+@example
+log measurements statistics tracking
+@end example
+
+@menu
+* measurements log::            The format of the measurements log
+* statistics log::              The format of the statistics log
+* tracking log::                The format of the tracking log
+* RTC log::                     The format of the RTC log
+@end menu
+@c }}}
+@c {{{ measurements.log
+@node measurements log
+@subsubsection Measurements log file format
+
+An example line (which actually appears as a single line in the file)
+from the measurements log file is shown below.
+
+@example
+1998-07-22 05:40:50 158.152.1.76    N  8 1111 11 1111 10 10  1 \
+   -4.966e-03  2.296e-01  1.577e-05  1.615e-01  7.446e-03
+@end example
+
+The columns are as follows (the quantities in square brackets are the
+values from the example line above) :
+
+@enumerate 1
+@item
+Date [1998-07-22]
+@item
+Hour:Minute:Second [05:40:50].  Note that the date/time pair is
+expressed in UTC, not the local time zone.
+@item
+IP address of server/peer from which measurement comes [158.152.1.76]
+@item
+Leap status (@code{N} means normal, @code{-} means that the last minute
+of today has 61 seconds, @code{+} means that the last minute of the day
+has 59 seconds, @code{?} means the remote computer is not currently
+synchronised.) [N]
+@item
+Stratum of remote computer. [2]
+@item
+RFC1305 tests 1 through 4 (1=pass, 0=fail) [1111]
+@item
+Tests for maximum delay and maximum delay ratio, against user defined
+parameters (1=pass, 0=fail) [11]
+@item
+RFC1305 tests 5 through 8 (1=pass, 0=fail) [1111]
+@item
+Local poll [10]
+@item
+Remote poll [10]
+@item
+`Score' (an internal score within each polling level used to decide when
+to increase or decrease the polling level.  This is adjusted based on
+changes to the variance of the measurements obtained from the source). [1]
+@item
+The estimated local clock error (`theta' in RFC1305).  Positive indicates that the local clock is slow. [-4.966e-03].
+@item
+The peer delay (`delta' in RFC1305). [2.296e-01]
+@item
+The peer dispersion (`epsilon' in RFC1305). [1.577e-05]
+@item
+The root delay (`Delta' in RFC1305). [1.615e-01]
+@item
+The root dispersion (`E' in RFC1305). [7.446e-03]
+@end enumerate
+
+A banner is periodically written to the log file to indicate the
+meanings of the columns.
+@c }}}
+@c {{{ statistics.log
+@node statistics log
+@subsubsection Statistics log file format
+
+An example line (which actually appears as a single line in the file)
+from the measurements log file is shown below.
+
+@example
+1998-07-22 05:40:50 158.152.1.76     6.261e-03 -3.247e-03 \
+     2.220e-03  1.874e-06  1.080e-06 7.8e-02  16   0   8
+@end example
+
+The columns are as follows (the quantities in square brackets are the
+values from the example line above) :
+
+@enumerate 1
+@item
+Date [1998-07-22]
+@item
+Hour:Minute:Second [05:40:50].  Note that the date/time pair is
+expressed in UTC, not the local time zone.
+@item
+IP address of server/peer from which measurement comes [158.152.1.76]
+@item
+The estimated standard deviation of the measurements from the source (in
+seconds). [6.261e-03]
+@item
+The estimated offset of the source (in seconds, positive means the local
+clock is estimated to be fast, in this case). [-3.247e-03]
+@item
+The estimated standard deviation of the offset estimate (in
+seconds). [2.220e-03]
+@item
+The estimated rate at which the local clock is gaining or losing time
+relative to the source (in seconds per second, positive means the local
+clock is gaining).  This is relative to the compensation currently being
+applied to the local clock, @emph{not} to the local clock without any
+compensation. [1.874e-06]
+@item
+The estimated error in the rate value (in seconds per
+second). [1.080e-06].
+@item
+The ration of |old_rate - new_rate| / old_rate_error.  Large values
+indicate the statistics are not modelling the source very well. [7.8e-02]
+@item
+The number of measurements currently being used for the regression
+algorithm. [16]
+@item
+The new starting index (the oldest sample has index 0; this is the
+method used to prune old samples when it no longer looks like the
+measurements fit a linear model). [0, i.e. no samples discarded this
+time]
+@item
+The number of runs.  The number of runs of regression residuals with the
+same sign is computed.  If this is too small it indicates that the
+measurements are no longer represented well by a linear model and that
+some older samples need to be discarded.  The number of runs for the
+data that is being retained is tabulated.  Values of approximately half
+the number of samples are expected. [8]
+@end enumerate
+
+A banner is periodically written to the log file to indicate the
+meanings of the columns.
+@c }}}
+@c {{{ tracking.log
+@node tracking log
+@subsubsection Tracking log file format
+
+An example line (which actually appears as a single line in the file)
+from the measurements log file is shown below.
+
+@example
+1998-07-22 05:40:50 158.152.1.76     3    340.529      1.606  1.046e-03
+@end example
+
+The columns are as follows (the quantities in square brackets are the
+values from the example line above) :
+
+@enumerate 1
+@item
+Date [1998-07-22]
+@item
+Hour:Minute:Second [05:40:50].  Note that the date/time pair is
+expressed in UTC, not the local time zone.
+@item
+The IP address of the server/peer to which the local system is
+synchronised. [158.152.1.76]
+@item
+The stratum of the local system. [3]
+@item
+The local system frequency (in ppm, positive means the local system runs
+fast of UTC). [340.529]
+@item
+The error bounds on the frequency (in ppm) [1.606]
+@item
+The estimated local offset at the epoch (which is rapidly corrected by
+slewing the local clock.  (In seconds, positive indicates the local
+system is fast of UTC). [1.046e-3]
+@end enumerate
+
+A banner is periodically written to the log file to indicate the
+meanings of the columns.
+@c }}}
+@c {{{ rtc.log
+@node RTC log
+@subsubsection Real-time clock log file format
+
+An example line (which actually appears as a single line in the file)
+from the measurements log file is shown below.
+
+@example
+1998-07-22 05:40:50     -0.037360 1       -0.037434\
+          -37.948  12   5  120
+@end example
+
+The columns are as follows (the quantities in square brackets are the
+values from the example line above) :
+
+@enumerate 1
+@item
+Date [1998-07-22]
+@item
+Hour:Minute:Second [05:40:50].  Note that the date/time pair is
+expressed in UTC, not the local time zone.
+@item
+The measured offset between the system's real time clock and the system
+(@code{gettimeofday()}) time.  In seconds, positive indicates that the
+RTC is fast of the system time. [-0.037360].
+@item
+Flag indicating whether the regression has produced valid
+coefficients. (1 for yes, 0 for no). [1]
+@item
+Offset at the current time predicted by the regression process.  A large
+difference between this value and the measured offset tends to indicate
+that the measurement is an outlier with a serious measurement
+error. [-0.037434].
+@item
+The rate at which the RTC is losing or gaining time relative to the
+system clock.  In ppm, with positive indicating that the RTC is gaining
+time. [-37.948]
+@item
+The number of measurements used in the regression. [12]
+@item
+The number of runs of regression residuals of the same sign.  Low values
+indicate that a straight line is no longer a good model of the measured
+data and that older measurements should be discarded. [5]
+@item
+The measurement interval used prior to the measurement being made (in
+seconds). [120]
+@end enumerate
+
+A banner is periodically written to the log file to indicate the
+meanings of the columns.
+@c }}}
+@c }}}
+@c {{{ logchange
+@node logchange directive
+@subsection logchange
+This directive forces @code{chronyd} to send a message to syslog if it
+makes a system clock adjustment larger than a threshold value.  An
+example of use is
+
+@example
+logchange 0.5
+@end example
+
+which would cause a syslog message to be generated a system clock error
+of over 0.5 seconds starts to be compensated.
+
+Clock errors detected either via NTP packets or via timestamps entered
+via the @code{settime} command of @code{chronyc} are logged.
+
+This directive assumes that syslog messages are appearing where somebody
+can see them.  This allows that person to see if a large error has
+arisen, e.g. because of a fault, or because of faulty timezone handling,
+for example when summer time (daylight saving) starts or ends.
+@c }}}
+@c {{{ logdir
+@node logdir directive
+@subsection logdir
+This directive allows the directory where log files are written to be
+specified.
+
+An example of the use of this directive is
+
+@example
+logdir /var/log/chrony
+@end example
+@c }}}
+@c {{{ mailonchange
+@node mailonchange directive
+@subsection mailonchange
+This directive defines an email address to which mail should be sent if
+chronyd applies a correction exceeding a particular threshold to the
+system clock.
+
+An example of use of this directive is
+
+@example
+mailonchange root@@localhost 0.5
+@end example
+
+This would send a mail message to root if a change of more than 0.5
+seconds were applied to the system clock.
+@c }}}
+@c {{{ manual
+@node manual directive
+@subsection manual
+The @code{manual} directive enables support at run-time for the
+@code{settime} command in chronyc (@pxref{settime command}).  If no
+@code{manual} directive is included, any attempt to use the
+@code{settime} command in chronyc will be met with an error message.
+
+Note that the @code{settime} command can be enabled at run-time using
+the @code{manual} command in chronyc (@pxref{manual command}).  (The
+idea of the two commands is that the @code{manual} command controls the
+manual clock driver's behaviour, whereas the @code{settime} command
+allows samples of manually entered time to be provided).
+@c }}}
+@c {{{ maxupdateskew
+@node maxupdateskew directive
+@subsection maxupdateskew
+One of @code{chronyd's} tasks is to work out how fast or slow the computer's
+clock runs relative to its reference sources.  In addition, it computes
+an estimate of the error bounds around the estimated value.
+
+If the range of error is too large, it probably indicates that the
+measurements have not settled down yet, and that the estimated gain or
+loss rate is not very reliable.
+
+The @code{maxupdateskew} parameter allows the threshold for determining
+whether an estimate may be so unreliable that it should not be used.
+
+The syntax is
+
+@example
+maxupdateskew <skew-in-ppm>
+@end example
+
+Typical values for <skew-in-ppm> might be 100 for a dial-up connection
+to servers over a phone line, and 5 or 10 for a computer on a LAN.
+
+It should be noted that this is not the only means of protection against
+using unreliable estimates.  At all times, @code{chronyd} keeps track of
+both the estimated gain or loss rate, and the error bound on the
+estimate.  When a new estimate is generated following another
+measurement from one of the sources, a weighted combination algorithm is
+used to update the master estimate.  So if @code{chronyd} has an existing
+highly-reliable master estimate and a new estimate is generated which
+has large error bounds, the existing master estimate will dominate in
+the new master estimate.
+@c }}}
+@c {{{ noclientlog
+@node noclientlog directive
+@subsection noclientlog
+This directive, which takes no arguments, specifies that client accesses
+are not to be logged.  Normally they are logged, allowing statistics to
+be reported using the @code{clients} command in @code{chronyc}.
+@c }}}
+@c {{{ peer
+@node peer directive
+@subsection peer
+The syntax of this directive is identical to that for the @code{server}
+directive (@pxref{server directive}), except that it is used to specify
+an NTP peer rather than an NTP server.
+@c }}}
+@c {{{ pidfile
+@node pidfile directive
+@subsection pidfile
+chronyd always writes its process ID (pid) to a file, and checks this file on startup to see if another chronyd may already be running on the system.  By default, the file used is @code{/var/run/chronyd.pid}.  The @code{pidfile} directive allows the name to be changed, e.g.
+
+@example
+pidfile /var/tmp/chronyd.pid
+@end example
+@c }}}
+@c {{{ port
+@node port directive
+@subsection port
+This option allows you to configure the port used for the NTP service
+on your machine.
+
+The compiled in default is udp/123, the standard NTP port.  It is
+unlikely that you would ever need to change this value.  A possible
+exception would be if you wanted to operate strictly in client-only
+mode and never be available as a server to xntpd clients.
+
+An example of the port command is
+
+@example
+port 11123
+@end example
+
+This would change the NTP port served by chronyd on the computer to
+udp/11123.
+@c }}}
+@c {{{ rtcdevice
+@node rtcdevice directive
+@subsection rtcdevice
+The @code{rtcdevice} directive defines the name of the device file for
+accessing the real time clock.  By default this is @code{/dev/rtc/}, unless the
+directive is used to set a different value.  This applies to Linux systems with
+devfs.  An example of use is
+
+@example
+rtcdevice /dev/misc/rtc
+@end example
+@c }}}
+@c {{{ rtcfile
+@node rtcfile directive
+@subsection rtcfile
+The @code{rtcfile} directive defines the name of the file in which
+@code{chronyd} can save parameters associated with tracking the accuracy
+of the system's real-time clock (RTC).
+
+The syntax is illustrated in the following example
+
+@example
+rtcfile /etc/chrony.rtc
+@end example
+
+@code{chronyd} saves information in this file when it exits and when the
+@code{writertc} command is issued in @code{chronyc}.  The information
+saved is the RTC's error at some epoch, that epoch (in seconds since
+January 1 1970), and the rate at which the RTC gains or loses time.
+
+So far, the support for real-time clocks is limited - their code is even
+more system-specific than the rest of the software.  You can only use
+the real time clock facilities (the @code{rtcfile} directive and the
+@code{-s} command line option to @code{chronyd}) if the following three
+conditions apply:
+
+@enumerate 1
+@item
+You are running Linux version 2.2.x or 2.4.x (for any value of x), or v2.0.x
+with x>=32. 
+
+@item
+You have compiled the kernel with extended real-time clock support
+(i.e. the @file{/dev/rtc} device is capable of doing useful things).
+
+@item
+You don't have other applications that need to make use of
+@file{/dev/rtc} at all.
+
+@end enumerate
+@c }}}
+@c {{{ rtconutc
+@node rtconutc directive
+@subsection rtconutc
+
+@code{chronyd} assumes by default that the real time clock (RTC) keeps
+local time (including any daylight saving changes).  This is convenient
+on PCs running Linux which are dual-booted with DOS or Windows.
+
+NOTE : IF YOU KEEP THE REAL TIME CLOCK ON LOCAL TIME AND YOUR COMPUTER
+IS OFF WHEN DAYLIGHT SAVING (SUMMER TIME) STARTS OR ENDS, THE COMPUTER'S
+SYSTEM TIME WILL BE ONE HOUR IN ERROR WHEN YOU NEXT BOOT AND START
+CHRONYD.
+
+An alternative is for the RTC to keep Universal Coordinated Time (UTC).
+This does not suffer from the 1 hour problem when daylight saving starts
+or ends.
+
+If the @code{rtconutc} directive appears, it means the RTC is required
+to keep UTC.  The directive takes no arguments.  It is equivalent to
+specifying the @code{-u} switch to the Linux @file{/sbin/clock} program.
+@c }}}
+@c {{{ server
+@node server directive
+@subsection server
+The @code{server} directive allows NTP servers to be specified.  The
+client/server relationship is strictly hierarchical : a client may
+synchronise its system time to that of the server, but the server's
+system time will never be influenced by that of a client.
+
+The @code{server} directive is immediately followed by either the name
+of the server, or its IP address in dotted-quad notation.  The server
+command also supports a number of subfields (which may be defined in any
+order):
+
+@table @code
+@item port
+This option allows the UDP port on which the server understands NTP
+requests to be specified.  For normal servers this option should not be
+required (the default is 123, the standard NTP port).
+@item minpoll
+Although @code{chronyd} will trim the rate at which it samples the
+server during normal operation, the user may wish to constrain the
+minimum polling interval.  This is always defined as a power of 2, so
+<tt/minpoll 5/ would mean that the polling interval cannot drop below 32
+seconds.  The default is 6 (64 seconds).
+@item maxpoll
+In a similar way, the user may wish to constrain the maximum polling
+interval.  Again this is specified as a power of 2, so <tt/maxpoll 9/
+indicates that the polling interval must stay at or below 512 seconds.
+The default is 10 (1024 seconds).
+@item maxdelay
+@code{chronyd} uses the network round-trip delay to the server to
+determine how accurate a particular measurement is likely to be.  Long
+round-trip delays indicate that the request, or the response, or both
+were delayed.  If only one of the messages was delayed the measurement
+error is likely to be substantial.
+
+For small variations in round trip delay, @code{chronyd} uses a
+weighting scheme when processing the measurements.  However, beyond a
+certain level of delay the measurements are likely to be so corrupted as
+to be useless.  (This is particularly so on dial-up or other slow links,
+where a long delay probably indicates a highly asymmetric delay caused
+by the response waiting behind a lot of packets related to a download of
+some sort).
+
+If the user knows that round trip delays above a certain level should
+cause the measurement to be ignored, this level can be defined with the
+maxdelay command.  For example, <tt/maxdelay 0.3/ would indicate that
+measurements with a round-trip delay of 0.3 seconds or more should be
+ignored.
+
+@item maxdelayratio
+This option is similar to the maxdelay option above.  @code{chronyd}
+keeps a record of the minimum round-trip delay amongst the previous
+measurements that it has buffered.  If a measurement has a round trip
+delay that is greater than the maxdelayratio times the minimum delay, it
+will be rejected.
+
+@item presend
+If the timing measurements being made by @code{chronyd} are the only
+network data passing between two computers, you may find that some
+measurements are badly skewed due to either the client or the server
+having to do an ARP lookup on the other party prior to transmitting a
+packet.  This is more of a problem with long sampling intervals, which
+may be similar in duration to the lifetime of entries in the ARP caches
+of the machines.
+
+In order to avoid this problem, the @code{presend} option may be used.
+It takes a single integer argument, which is the smallest polling
+interval for which a pair of packets will be exchanged between the
+client and the server prior to the actual measurement being initiated by
+the client.  For example, with the following option included in a
+@code{server} directive :
+
+@example
+presend 9
+@end example
+
+when the polling interval is 512 seconds or more, a UDP echo datagram
+will be sent to the server a short time (currently 4 seconds) before the
+NTP client mode datagram.
+
+@item key
+The NTP protocol supports the inclusion of checksums in the packets, to
+prevent computers having their system time upset by rogue packets being
+sent to them.  The checksums are generated as a function of a password,
+using the MD5 algorithm.
+
+The association between key numbers and passwords is contained in the
+keys file, defined by the keyfile command.
+
+If the key option is present, @code{chronyd} will attempt to use
+authenticated packets when communicating with this server.  The key
+number used will be the single argument to the key option.  The server
+must have the same password for this key number configured, otherwise no
+relationship between the computers will be possible.
+
+@item offline
+If the server will not be reachable when @code{chronyd} is started, the
+offline option may be specified.  @code{chronyd} will not try to poll
+the server until it is enabled to do so (by using the online option of
+@code{chronyc}).
+
+@item auto_offline
+If this option is set, the server will be assumed to have gone offline when 2
+requests have been sent to it without receiving a response.  This option avoids
+the need to run the @code{offline} (@pxref{offline command}) command from
+chrony when disconnecting the dial-up link.  (It will still be necessary to use
+chronyc's @code{online} (@pxref{online command}) command when the link has been
+established, to enable measurements to start.)
+
+@end table
+@c }}}
+@c }}}
+@c {{{ S:Running chronyc
+@node Running chronyc
+@section Running chronyc
+@c {{{ Section top
+Chronyc is the program that can be used to reconfigure options within
+the @code{chronyd} program whilst it is running.  Chronyc can also be
+used to generate status reports about the operation of @code{chronyd}.
+
+@menu
+* Chronyc basic use::           How to run chronyc
+* Chronyc command line options::  Chrony's command line options
+* Security with chronyc::       How chronyd restricts access
+* Chronyc command reference::   All the commands chronyc supports
+@end menu
+@c }}}
+@c {{{ SS:Chronyc basic use
+@node Chronyc basic use
+@subsection Basic use
+The program chronyc is run by entering
+
+@example
+chronyc
+@end example
+
+at the command line.  The prompt @code{chronyc} is displayed whilst
+chronyc is expecting input from the user, when it is being run from a
+terminal.  If chronyc's input or output are redirected from/to a file,
+the prompt is now shown.
+
+When you are finished entering commands, the commands @code{exit} or
+@code{quit} will terminate the program.  (Entering @key{Control-D} will
+also terminate the program.)
+@c }}}
+@c {{{ SS:Command line options
+@node Chronyc command line options
+@subsection Command line options
+Chronyc supports the following command line options.
+
+@table @code
+@item -v
+Displays the version number of chronyc on the terminal, and exists.
+@item -h <host>
+This option allows the user to specify which host running the
+@code{chronyd} program is to be contacted.  This allows for remote
+configuration, without having to telnet or rlogin to the other host
+first.
+
+The default is to contact @code{chronyd} running on the same host as
+that where chronyc is being run.
+@item -p <port>
+This option allows the user to specify the UDP port number which the
+target @code{chronyd} is using for its command & monitoring connections.
+This defaults to the compiled-in default; there would rarely be a need
+to change this.
+@end table
+@c }}}
+@c {{{ SS:Security with chronyc
+@node Security with chronyc
+@subsection Security with chronyc
+Many of the commands available through chronyc have a fair amount of
+power to reconfigure the run-time behaviour of @code{chronyd}.  Consequently,
+@code{chronyc} is quite dangerous for the integrity of the target
+system's clock performance.  Having access to @code{chronyd} via chronyc is
+more or less equivalent to being able to modify @code{chronyd's} configuration
+file (typically @file{/etc/chrony.conf}) and to restart @code{chronyd}.
+
+Chronyc also provides a number of monitoring (as opposed to commanding)
+commands, which will not affect the behaviour of @code{chronyd}.  However, you
+may still want to restrict access to these commands.
+
+In view of this, access to some of the capabilities of chronyc will
+usually be tightly controlled.  There are two mechanisms supported:
+
+@enumerate 1
+@item
+The set of hosts from which @code{chronyd} will accept commands can be
+restricted.  By default, commands will only be accepted from the same
+host that @code{chronyd} is running on.
+@item
+Any command that actually reconfigures some aspect of @code{chronyd's}
+behaviour requires the user of chronyc to know a password.  This
+password is specified in @code{chronyd's} keys file (@pxref{keyfile directive})
+and specified via the commandkey option in its configuration file
+(@pxref{commandkey directive}).
+@end enumerate
+
+Only the following commands can be used @emph{without} providing a
+password:
+
+@itemize @bullet
+@item @code{exit}
+@item @code{help}
+@item @code{password}
+@item @code{quit}
+@item @code{rtcdata}
+@item @code{sources}
+@item @code{sourcestats}
+@item @code{tracking}
+@end itemize
+
+All other commands require a password to have been specified previously,
+because they affect @code{chronyd's} operation.
+@c }}}
+@c {{{ SS:Chronyc command reference
+@node Chronyc command reference
+@subsection Command reference
+@c {{{ Top/menu
+This section describes each of the commands available within the chronyc
+program.  Chronyc offers the user a simple command-line driven
+interface.
+
+@menu
+* accheck command::             Verifying NTP client access
+* activity command::            Check how many NTP servers/peers are online/offline
+* add peer command::            Add a new NTP peer
+* add server command::          Add a new NTP server
+* allow command::               Allowing NTP client access
+* allow all command::           Allowing NTP client access
+* burst command::               Initiating a rapid set of measurements
+* clients command::             Show clients that have accessed the server
+* cmdaccheck command::          Verifying command client access
+* cmdallow command::            Allowing command client access
+* cmdallow all command::        Allowing command client access
+* cmddeny command::             Denying command client access
+* cmddeny all command::         Denying command client access
+* cyclelogs command::           Close and re-open open log files
+* delete command::              Remove an NTP server or peer
+* deny command ::               Denying NTP client access
+* deny all command::            Denying NTP client access
+* dump command::                Dump measurement histories to files
+* exit command::                Exit from chronyc
+* help command::                Generate help summary
+* local command::               Let computer be a server when it is unsynchronised
+* makestep command::            Immediately correct the system clock instead of slewing
+* manual command::              Enable/disable/configure options for settime
+* maxdelay command::            Set max measurement delay for a source
+* maxdelayratio command::       Set max measurement delay for a source as ratio
+* maxpoll command::             Set maximum polling interval for a source
+* maxupdateskew command::       Set safety threshold for clock gain/loss rate
+* minpoll command::             Set minimum polling interval for a source
+* offline command::             Warn that connectivity to a source will be lost
+* online command::              Warn that connectivity to a source has been restored
+* password command::            Provide password needed for most commands
+* quit command::                Exit from chronyc
+* rtcdata command::             Display RTC parameters
+* settime command::             Provide a manual input of the current time
+* sources command::             Display information about the current set of sources
+* sourcestats command::         Display the rate & offset estimation performance of sources
+* tracking command::            Display system clock performance
+* trimrtc command::             Correct the RTC time to the current system time
+* writertc command::            Write the RTC parameters to file.
+@end menu
+@c }}}
+@c {{{ accheck
+@node accheck command
+@subsubsection accheck
+This command allows you to check whether client NTP access is allowed
+from a particular host.
+
+Examples of use, showing a named host and a numeric IP address, are as
+follows:
+
+@example
+accheck a.b.c
+accheck 1.2.3.4
+@end example
+
+This command can be used to examine the effect of a series of
+@code{allow}, @code{allow all}, @code{deny} and @code{deny all} commands
+specified either via chronyc, or in @code{chronyd's} configuration file.
+@c }}}
+@c {{{ activity command
+@node activity command
+@subsubsection activity
+This command reports the number of servers/peers that are online and offline.
+If the auto_offline option is used in specifying some of the servers/peers, the
+@code{activity} command may be useful for detecting when all of them have
+entered the offline state after the PPP link has been disconnected.
+
+The report shows the number of servers/peers in 4 states:
+@itemize
+@item @code{online} : the server/peer is currently online (i.e. assumed by
+chronyd to be reachable)
+@item @code{offline} : the server/peer is currently offline (i.e. assumed by
+chronyd to be unreachable, and no measurements from it will be attempted.)
+@item @code{burst_online} : a burst command has been initiated for the
+server/peer and is being performed; after the burst is complete, the
+server/peer will be returned to the online state.
+@item @code{burst_offline} : a burst command has been initiated for the
+server/peer and is being performed; after the burst is complete, the
+server/peer will be returned to the offline state.
+@end itemize
+@c }}}
+@c {{{ add peer
+@node add peer command
+@subsubsection add peer
+The @code{add peer} command allows a new NTP peer to be added whilst
+@code{chronyd} is running.
+
+Following the words @code{add peer}, the syntax of the following
+parameters and options is identical to that for the @code{peer}
+directive in the configuration file (@pxref{peer directive}).
+
+An example of using this command is shown below.
+
+@example
+add peer foo.bar.com minpoll 6 maxpoll 10 authkey 25
+@end example
+@c }}}
+@c {{{ add server
+@node add server command
+@subsubsection add server
+The @code{add server} command allows a new NTP server to be added whilst
+@code{chronyd} is running.
+
+Following the words @code{add server}, the syntax of the following
+parameters and options is identical to that for the @code{server}
+directive in the configuration file (@pxref{server directive}).
+
+An example of using this command is shown below.
+
+@example
+add server foo.bar.com minpoll 6 maxpoll 10 authkey 25
+@end example
+@c }}}
+@c {{{ allow
+@node allow command
+@subsubsection allow
+The effect of the allow command is identical to the @code{allow} directive in
+the configuration file (@pxref{allow directive}).
+
+The syntax is illustrated in the following examples:
+
+@example
+allow foo.bar.com
+allow 1.2
+allow 3.4.5
+allow 6.7.8/22
+allow 6.7.8.9/22
+allow
+@end example
+
+The effect of each of these examples is the same as that of the @code{allow}
+directive in the configuration file.
+@c }}}
+@c {{{ allow all
+@node allow all command
+@subsubsection allow all
+The effect of the allow command is identical to the @code{allow all}
+directive in the configuration file (@pxref{allow directive}).
+@c }}}
+@c {{{ burst
+@node burst command
+@subsubsection burst
+The @code{burst} command tells @code{chronyd} to make a set of measurements to
+each of its sources over a short duration (rather than the usual
+periodic measurements that it makes).  After such a burst, @code{chronyd} will
+revert to the previous state for each source.  This might be either
+online, if the source was being periodically measured in the normal way,
+or offline, if the source had been indicated as being offline.
+(Switching a source between the online and offline states is described
+in @ref{online command}, @ref{offline command}).
+
+The syntax of the burst command is as follows
+
+@example
+burst <n-good-measurements>/<max-measurements> [<mask>/<masked-address>]
+@end example
+
+The mask and masked-address arguments are optional, in which case
+@code{chronyd} will initiate a burst for all of its currently defined sources.
+
+The arguments have the following meaning and format.
+
+@table @code
+@item n-good-measurements
+This defines the number of good measurements that @code{chronyd} will want to
+obtain from each source.  A measurement is good if it passes certain
+tests, for example, the round trip time to the source must be
+acceptable.  (This allows @code{chronyd} to reject measurements that are likely
+to be bogus.)
+
+@item max-measurements
+This defines the maximum number of measurements that @code{chronyd} will
+attempt to make, even if the required number of good measurements has
+not been obtained.
+
+@item mask
+This is a dotted quad argument (e.g. @code{255.255.255.0}) with which
+the IP address of each of @code{chronyd}'s sources is to be masked.
+
+@item masked-address
+This is a dotted quad argument (e.g. @code{1.2.3.0}).  If the masked IP
+address of a source matches this value then the burst command is applied
+to that source.
+@end table
+
+If no mask or masked address arguments are provided, the default is
+@code{0.0.0.0} and @code{0.0.0.0} respectively, which will match every
+source.
+
+An example of the two-argument form of the command is 
+
+@example
+burst 2/10
+@end example
+
+This will cause @code{chronyd} to attempt to get two good measurements from
+each source, stopping after two have been obtained, but in no event will
+it try more than ten probes to the source.
+
+An example of the four-argument form of the command is
+
+@example
+burst 2/10 255.255.0.0/1.2.0.0
+@end example
+
+In this case, the two out of ten sampling will only be applied to
+sources whose IP addresses are of the form @code{1.2.x.y}, where x and y
+are arbitrary.
+@c }}}
+@c {{{ clients
+@node clients command
+@comment  node-name,  next,  previous,  up
+@subsubsection clients
+This command shows a list of all clients that have accessed the server,
+through either the NTP or command/monitoring ports.  There are no arguments.
+
+An example of the output is
+
+@example
+Hostname                   Client    Peer CmdAuth CmdNorm  CmdBad  LstN  LstC
+=========================  ======  ======  ======  ======  ======  ====  ====
+localhost                       0       0      15       1       0   29y     0
+aardvark.xxx                    4       0       0       0       0    49   29y
+badger.xxx                      4       0       0       0       0     6   29y
+@end example
+
+Each row shows the data for a single host.  Only hosts that have passed
+the host access checks (set with the @code{allow}, @code{deny},
+@code{cmdallow} and @code{cmddeny} commands or configuration file
+directives) are logged.
+
+The columns are as follows:
+
+@enumerate 1
+@item
+The hostname of the client
+@item
+The number of times the client has accessed the server using an NTP
+client mode packet.
+@item
+The number of times the client has accessed the server using an NTP
+symmetric active mode packet.
+@item
+The number of authenticated command packets that have been processed
+from the client (i.e. those following a successful @code{password}
+command).
+@item
+The number of unauthenticated command packets that have been processed
+from the client.
+@item
+The number of bad command packets received from the client (not all
+forms of bad packet are logged).
+@item
+Time since the last NTP packet was received
+@item
+Time since the last command packet was received
+@end enumerate
+
+The last two entries will be shown as the time since 1970 if no packet
+of that type has ever been received.
+@c }}}
+@c {{{ cmdaccheck
+@node cmdaccheck command
+@subsubsection cmdaccheck
+This command is similar to the @code{accheck} command, except that it is
+used to check whether command access is permitted from a named host.
+
+Examples of use are as follows:
+
+@example
+cmdaccheck a.b.c
+cmdaccheck 1.2.3.4
+@end example
+@c }}}
+@c {{{ cmdallow
+@node cmdallow command
+@subsubsection cmdallow
+This is similar to the @code{allow} command, except that it is used to
+allow particular hosts or subnets to use the chronyc program to interact
+with @code{chronyd} on the current host.
+@c }}}
+@c {{{ cmdallow all
+@node cmdallow all command
+@subsubsection cmdallow all
+This is similar to the @code{allow all} command, except that it is used to@c {{{
+allow particular hosts or subnets to use the chronyc program to interact@c }}}
+with @code{chronyd} on the current host.
+@c }}}
+@c {{{ cmddeny
+@node cmddeny command
+@subsubsection cmddeny
+This is similar to the @code{deny} command, except that it is used to
+allow particular hosts or subnets to use the chronyc program to interact
+with @code{chronyd} on the current host.
+@c }}}
+@c {{{ cmddeny all
+@node cmddeny all command
+@subsubsection cmddeny all
+This is similar to the @code{deny all} command, except that it is used
+to allow particular hosts or subnets to use the chronyc program to
+interact with @code{chronyd} on the current host.
+@c }}}
+@c {{{ cyclelogs
+@node cyclelogs command
+@subsubsection cyclelogs
+The @code{cyclelogs} command causes all of @code{chronyd's} open log files to
+be closed and re-opened.  This allows them to be renamed so that they can be
+periodically purged.  An example of how to do this is shown below.
+
+@example
+% mv /var/log/chrony/measurements.log /var/log/chrony/measurements1.log
+% chronyc
+chronyc> password aardvark
+200 OK
+chronyc> cyclelogs
+200 OK
+chronyc> exit
+% ls -l /var/log/chrony
+-rw-r--r--   1 root     root            0 Jun  8 18:17 measurements.log
+-rw-r--r--   1 root     root        12345 Jun  8 18:17 measurements1.log
+% rm -f measurements1.log
+@end example
+@c }}}
+@c {{{ delete
+@node delete command
+@subsubsection delete
+The @code{delete} command allows an NTP server or peer to be removed
+from the current set of sources.
+
+The syntax is illustrated in the examples below.
+
+@example
+delete foo.bar.com
+delete 1.2.3.4
+@end example
+
+There is one parameter, the name or IP address of the server or peer to
+be deleted.
+@c }}}
+@c {{{ deny
+@node deny command 
+@subsubsection deny
+The effect of the allow command is identical to the @code{deny}
+directive in the configuration file (@pxref{deny directive}).
+
+The syntax is illustrated in the following examples:
+
+@example
+deny foo.bar.com
+deny 1.2
+deny 3.4.5
+deny 6.7.8/22
+deny 6.7.8.9/22
+deny
+@end example
+@c }}}
+@c {{{ deny all
+@node deny all command
+@subsubsection deny all
+The effect of the allow command is identical to the @code{deny all}
+directive in the configuration file (@pxref{deny directive}).
+@c }}}
+@c {{{ dump
+@node dump command
+@subsubsection dump
+The @code{dump} command causes @code{chronyd} to write its current history of
+measurements for each of its sources to dump files, either for
+inspection or to support the @code{-r} option when @code{chronyd} is restarted.
+
+The @code{dump} command is somewhat equivalent to the @code{dumponexit}
+directive in the chrony configuration file.  @xref{dumponexit directive}.
+
+To use the @code{dump}, you probably want to configure the name of the
+directory into which the dump files will be written.  This can only be
+done in the configuration file, see @ref{dumpdir directive}.
+@c }}}
+@c {{{ exit
+@node exit command
+@subsubsection exit
+The exit command exits from chronyc and returns the user to the shell
+(same as the quit command).
+@c }}}
+@c {{{ help
+@node help command
+@subsubsection help
+The help command displays a summary of the commands and their arguments.
+@c }}}
+@c {{{ local
+@node local command
+@subsubsection local
+The @code{local} command allows @code{chronyd} to be told that it is to appear
+as a reference source, even if it is not itself properly synchronised to
+an external source.  (This can be used on isolated networks, to allow
+one computer to be a master time server with the other computers slaving
+to it.)  The @code{local} command is somewhat equivalent to the
+@code{local} directive in the configuration file, see @ref{local directive}.
+
+The syntax is as shown in the following examples.
+
+@example
+local stratum 10
+local off
+@end example
+
+The first example enables the local reference mode on the host, and sets
+the stratum at which it should claim to be synchronised.
+
+The second example disables the local reference mode.
+@c }}}
+@c {{{ makestep
+@node makestep command
+@subsubsection makestep
+Normally chronyd will cause the system to gradually correct any time
+offset, by slowing down or speeding up the clock as required.  In
+certain situations, the system clock may be so far adrift that this
+slewing process would take a very long time to correct the system clock.
+
+The @code{makestep} command can be used in this situation.  It cancels
+any remaining correction that was being slewed, and jumps the system
+clock by the equivalent amount, making it correct immediately.
+
+BE WARNED - certain software will be seriously affected by such jumps to
+the system time.  (That is the reason why chronyd uses slewing
+normally.)
+
+The @code{makestep} command is currently only available on the Linux
+version of chrony.
+@c }}}
+@c {{{ manual
+@node manual command
+@subsubsection manual
+The manual command enables and disables use of the @code{settime}
+command (@pxref{settime command}), and is used to modify the behaviour
+of the manual clock driver.
+
+Examples of the command are shown below.
+
+@example
+manual on
+manual off
+manual delete 1
+manual list
+manual reset
+@end example
+
+The @code{on} form of the command enables use of the @code{settime}
+command.
+
+The @code{off} form of the command disables use of the @code{settime}
+command.
+
+The @code{list} form of the command lists all the samples currently
+stored in @code{chronyd}.  The output is illustrated below.
+
+@example
+210 n_samples = 1
+#    Date  Time(UTC)    Slewed   Original   Residual
+====================================================
+ 0 27Jan99 22:09:20       0.00       0.97       0.00
+@end example
+
+The columns as as follows :
+
+@enumerate 1
+@item
+The sample index (used for the @code{manual delete} command)
+@item
+The date and time of the sample
+@item
+The system clock error when the timestamp was entered, adjusted to allow
+for changes made to the system clock since.
+@item
+The system clock error when the timestamp was entered, as it originally
+was (without allowing for changes to the system clock since).
+@item
+The regression residual at this point, in seconds.  This allows
+'outliers' to be easily spotted, so that they can be deleted using the
+@code{manual delete} command.
+@end enumerate
+
+The @code{delete} form of the command deletes a single sample.  The
+parameter is the index of the sample, as shown in the first column of
+the output from @code{manual list}.  Following deletion of the data
+point, the current error and drift rate are re-estimated from the
+remaining data points and the system clock trimmed if necessary.  This
+option is intended to allow 'outliers' to be discarded, i.e. samples
+where the administrator realises he/she has entered a very poor
+timestamp.
+
+The @code{reset} form of the command deletes all samples at once.  The
+system clock is left running as it was before the command was entered.
+@c }}}
+@c {{{ maxdelay
+@node maxdelay command
+@subsubsection maxdelay
+This allows the @code{maxdelay} option for one of the sources to be
+modified, in the same way as specifying the @code{maxdelay} option for
+the @code{server} directive in the configuration file (@pxref{server
+directive}).
+
+The following examples illustrate the syntax
+
+@example
+maxdelay foo.bar.com 0.3
+maxdelay 1.2.3.4 0.0015
+@end example
+
+The first example sets the maximum network delay allowed for a
+measurement to the host @code{foo.bar.com} to 0.3 seconds.  The second
+example sets the maximum network delay for a measurement to the host
+with IP address @code{1.2.3.4} to 1.5 milliseconds.
+
+(Any measurement whose network delay exceeds the specified value is
+discarded.)
+@c }}}
+@c {{{ maxdelayratio
+@node maxdelayratio command
+@subsubsection maxdelayratio
+This allows the @code{maxdelayratio} option for one of the sources to be
+modified, in the same way as specifying the @code{maxdelayratio} option
+for the @code{server} directive in the configuration file (@pxref{server
+directive}).
+
+The following examples illustrate the syntax
+
+@example
+maxdelayratio foo.bar.com 1.5
+maxdelayratio 1.2.3.4 2.0
+@end example
+
+The first example sets the maximum network delay for a measurement to
+the host @code{foo.bar.com} to be 1.5 times the minimum delay found
+amongst the previous measurements that have been retained.  The second
+example sets the maximum network delay for a measurement to the host
+with IP address @code{1.2.3.4} to be double the retained minimum.
+
+As for @code{maxdelay}, any measurement whose network delay is too large
+will be discarded.
+@c }}}
+@c {{{ maxpoll
+@node maxpoll command
+@subsubsection maxpoll
+The @code{maxpoll} command is used to modify the minimum polling
+interval for one of the current set of sources.  It is equivalent to the
+@code{maxpoll} option in the @code{server} directive in the
+configuration file (@pxref{server directive}).
+
+The syntax is as follows
+
+@example
+maxpoll <host> <new-maxpoll>
+@end example
+
+where the host can be specified as either a machine name or dotted-quad
+IP address.  The new minimum poll is specified as a base-2 logarithm of
+the number of seconds between polls (e.g. specify 6 for 64 second
+sampling).
+
+An example is 
+
+@example
+maxpoll foo.bar.com 10
+@end example
+
+which sets the maximum polling interval for the host @code{foo.bar.com}
+to 1024 seconds.
+
+Note that the new maximum polling interval only takes effect after the
+next measurement has been made.
+@c }}}
+@c {{{ maxupdateskew
+@node maxupdateskew command
+@subsubsection maxupdateskew
+This command has the same effect as the @code{maxupdateskew} directive
+in the configuration file, see @ref{maxupdateskew directive}.
+@c }}}
+@c {{{ minpoll
+@node minpoll command
+@subsubsection minpoll
+The @code{minpoll} command is used to modify the minimum polling
+interval for one of the current set of sources.  It is equivalent to the
+@code{minpoll} option in the @code{server} directive in the
+configuration file (@pxref{server directive}).
+
+The syntax is as follows
+
+@example
+minpoll <host> <new-minpoll>
+@end example
+
+where the host can be specified as either a machine name or dotted-quad
+IP address.  The new minimum poll is specified as a base-2 logarithm of
+the number of seconds between polls (e.g. specify 6 for 64 second
+sampling).
+
+An example is 
+
+@example
+minpoll foo.bar.com 5
+@end example
+
+which sets the minimum polling interval for the host @code{foo.bar.com}
+to 32 seconds.
+
+Note that the new minimum polling interval only takes effect after the
+next measurement has been made.
+@c }}}
+@c {{{ offline
+@node offline command
+@subsubsection offline
+The @code{offline} command is used to warn @code{chronyd} that the network
+connection to a particular host or hosts is about to be lost.  It should
+be used on computers with a dial-up or similar connection to their time
+sources, to warn @code{chronyd} that the connection is about to be broken.
+
+An example of how to use @code{offline} in this case is shown in
+@ref{Advising chronyd of internet availability}.
+
+Another case where @code{offline} could be used is where a computer
+serves time to a local group of computers, and has a permanant
+connection to true time servers outside the organisation.  However, the
+external connection is heavily loaded at certain times of the day and
+the measurements obtained are less reliable at those times.  In this
+case, it is probably most useful to determine the gain/loss rate during
+the quiet periods and let the whole network coast through the loaded
+periods.  The @code{offline} and @code{online} commands can be used to
+achieve this.  The situation is shown in the figure below.
+
+@example
+@group
+          +----------+
+          |Ext source|
+          +----------+
+              |
+              |
+              |/| <-- Link with variable
+                |     reliability
+                |
+      +-------------------+
+      |Local master server|
+      +-------------------+
+                |
+  +---+---+-----+-----+----+----+
+  |   |   |     |     |    |    |
+           Local clients
+@end group
+@end example
+
+
+
+If the source to which @code{chronyd} is currently synchronised is indicated
+offline in this way, @code{chronyd} will continue to treat it as the
+synchronisation source.  If the network connection were broken without
+the @code{offline} command being used, @code{chronyd} would assume that the
+source had failed and would attempt to pick another synchronisation
+source.
+
+There are two forms of the @code{offline} command.  The first form is a
+wildcard, meaning all sources.  The second form allows a IP address mask
+and a masked address to be specified.  These forms are illustrated below.
+
+@example
+offline
+offline 255.255.255.0/1.2.3.0
+@end example
+
+The second form means that the @code{offline} command is to be applied
+to any source whose IP address is in the 1.2.3 subnet.  (The host's
+address is logically and-ed with the mask, and if the result matches the
+masked-address the host is processed).
+
+The wildcard form of the address is actually equivalent to
+
+@example
+offline 0.0.0.0/0.0.0.0
+@end example
+@c }}}
+@c {{{ online
+@node online command
+@subsubsection online
+The @code{online} command is opposite in function to the @code{offline}
+command.  It is used to advise @code{chronyd} that network connectivity to a
+particular source or sources has been restored.
+
+The syntax is identical to that of the @code{offline} command, see
+@ref{offline command}.
+@c }}}
+@c {{{ password
+@node password command
+@subsubsection password
+The password command is used to allow chronyc to send privileged
+commands to @code{chronyd}.  The password can either be entered on the command
+line, or can be entered without echoing.  The syntax for entering the
+password on the command line is as follows
+
+@example
+password xyzzy
+@end example
+
+To enter the password without it being echoed, enter
+
+@example
+password
+@end example
+
+The computer will respond with a @samp{Password:} prompt, at which you
+should enter the password and press return.  (Note that the no-echo mode
+is limited to 8 characters on SunOS 4.1 due to limitations in the system
+library.  Other systems do not have this restriction.)
+
+The password is any string of characters not containing whitespace.  It
+has to match @code{chronyd's} currently defined command key (@pxref{commandkey
+directive}).
+@c }}}
+@c {{{ quit
+@node quit command
+@subsubsection quit
+The quit command exits from chronyc and returns the user to the shell
+(same as the exit command).
+@c }}}
+@c {{{ rtcdata
+@node rtcdata command
+@subsubsection rtcdata
+The @code{rtcdata} command displays the current real time clock RTC parameters.
+
+An example output is shown below.
+
+@example
+RTC ref time (GMT) : Sat May 30 07:25:56 1998
+Number of samples  : 10
+Number of runs     : 5
+Sample span period :  549
+RTC is fast by     :    -1.632736 seconds
+RTC gains time at  :  -107.623 ppm
+@end example
+
+The fields have the following meaning
+
+@table @code
+@item RTC ref time (GMT)
+This is the RTC reading the last time its error was measured.
+@item Number of samples
+This is the number of previous measurements being used to determine the
+RTC gain/loss rate.
+@item Number of runs
+This is the number of runs of residuals of the same sign following the
+regression fit for (RTC error) versus (RTC time).  A value which is
+small indicates that the measurements are not well approximated by a
+linear model, and that the algorithm will tend to delete the older
+measurements to improve the fit.
+@item Sample span period
+This is the period that the measurements span (from the oldest to the
+newest).  Without a unit the value is in seconds; suffixes `m' for
+minutes, `h' for hours, `d' for days or `y' for years may be used.
+@item RTC is fast by
+This is the estimate of how many seconds fast the RTC when it thought
+the time was at the reference time (above).  If this value is large, you
+may (or may not) want to use the @code{trimrtc} command to bring the RTC
+into line with the system clock.  (Note, a large error will not affect
+@code{chronyd's} operation, unless it becomes so big as to start causing
+rounding errors.
+@item RTC gains time at
+This is the amount of time gained (positive) or lost (negative) by the
+real time clock for each second that it ticks.  It is measured in parts
+per million.  So if the value shown was +1, suppose the RTC was exactly
+right when it crosses a particular second boundary.  Then it would be 1
+microsecond fast when it crosses its next second boundary.
+@end table
+@c }}}
+@c {{{ settime
+@node settime command
+@subsubsection settime
+The @code{settime} command allows the current time to be entered
+manually, if this option has been configured into @code{chronyd}.  (It may be
+configured either with the @code{manual} directive in the configuration
+file (@pxref{manual directive}), or with the @code{manual} command of
+chronyc (@pxref{manual command}).
+
+It should be noted that the computer's sense of time will only be as
+accurate as the reference you use for providing this input (e.g. your
+watch), as well as how well you can time the press of the return key.
+When inputting time to an isolated network, I have a battery operated
+alarm clock that is synchronised to the Rugby MSF time signal in the UK.
+
+Providing your computer's time zone is set up properly, you will be able
+to enter a local time (rather than UTC).
+
+The response to a successful @code{settime} command indicates the amount
+that the computer's clock was wrong.  It should be apparent from this if
+you have entered the time wrongly, e.g. with the wrong time zone.
+
+The rate of drift of the system clock is estimated by a regression
+process using the entered measurement and all previous measurements
+entered during the present run of @code{chronyd}.  However, the entered
+measurement is used for adjusting the current clock offset (rather than
+the estimated intercept from the regression, which is ignored).
+Contrast what happens with the @code{manual delete} command, where the
+intercept is used to set the current offset (since there is no
+measurement that has just been typed in in that case).
+
+The time is parsed by the public domain @file{getdate} algorithm.
+Consequently, you can only specify time to the nearest second.
+
+Examples of inputs that are valid are shown below.
+
+@example
+settime 16:30
+settime 16:30:05
+settime Nov 21, 1997 16:30:05
+@end example
+
+For a full description of @code{getdate}, get hold of the getdate
+documentation (bundled, for example, with the source for GNU tar).
+@c }}}
+@c {{{ sources
+@node sources command
+@subsubsection sources
+This command displays information about the current time sources that
+@code{chronyd} is accessing.
+
+The optional argument @code{-v} can be specified, meaning @emph{verbose}.  In
+this case, extra caption lines are shown as a reminder of the meanings of the
+columns.
+
+@example
+@group
+210 Number of sources = 3
+MS Name/IP address      Stratum Poll LastRx Last sample
+=======================================================================
+^+ a.b.c                    3     6    47m  -9491us[-6983us] +/-  159ms
+^+ d.e.f                    3     6    47m    +32ms[  +35ms] +/-  274ms
+^* g.h.i                    2     6    47m  +8839us[  +11ms] +/-  214ms
+@end group
+@end example
+
+The columns are as follows:
+
+@table @code
+@item M
+This indicates the mode of the source.  @code{^} means a server,
+@code{=} means a peer and @code{#} indicates a locally connected
+reference clock@footnote{In the current version this will never be
+shown, because @code{chronyd} has no support for reference clocks yet.}.
+
+@item S
+This column indicates the state of the sources.  @code{*} indicates the
+source to which @code{chronyd} is current synchronised.  @code{+} indicates
+other acceptable sources.  @code{?} indicates sources to which
+connectivity has been lost.  @code{x} indicates a clock which @code{chronyd}
+thinks is is a falseticker (i.e. its time is inconsistent with a
+majority of other sources).  @code{~} indicates a source whose time
+appears to have too much variability.  The @code{~} condition is also
+shown at start-up, until at least 3 samples have been gathered from it.
+
+@item Name/IP address
+This shows the name or the IP address of the source.
+
+@item Stratum
+This shows the stratum of the source, as reported in its most recently
+received sample.  Stratum 1 indicates a computer with a locally attached
+reference clock.  A computer that is synchronised to a stratum 1
+computer is at stratum 2.  A computer that is synchronised to a stratum
+2 computer is at stratum 3, and so on.
+
+@item Poll
+This shows the rate at which the source is being polled, as a base-2
+logarithm of the interval in seconds.  Thus, a value of 6 would indicate
+that a measurement is being made every 64 seconds.
+
+@code{chronyd} automatically varies the polling rate in response to prevailing
+conditions.
+
+@item LastRx
+This column shows how long ago the last sample was received from the
+source.  This is normally in seconds.  The letters @code{m}, @code{h},
+@code{d} or @code{y} indicate minutes, hours, days or years.
+
+@item Last sample
+This column shows the offset between the local clock and the source at
+the last measurement.  The number in the square brackets shows the
+actual measured offset.  This may be suffixed by @code{us} (indicating
+microseconds), @code{ms} (indicating milliseconds), or @code{s}
+(indicating seconds).  The number to the left of the square brackets
+shows the original measurement, adjusted to allow for any slews applied
+to the local clock since.  The number following the @code{+/-} indicator
+shows the margin of error in the measurement.
+
+Positive offsets indicate that the local clock is fast of the source.
+
+@end table
+@c }}}
+@c {{{ sourcestats
+@node sourcestats command
+@subsubsection sourcestats
+
+The @code{sourcestats} command displays information about the drift rate
+and offset estimatation process for each of the sources currently being
+examined by @code{chronyd}.
+
+The optional argument @code{-v} can be specified, meaning @emph{verbose}.  In
+this case, extra caption lines are shown as a reminder of the meanings of the
+columns.
+
+An example report is
+
+@example
+@group
+210 Number of sources = 1
+Name/IP Address            NP  NR  Span  Frequency   Freq Skew   Std Dev
+========================================================================
+abc.def.ghi                11   5   46m      -0.001       0.045     25us
+@end group
+@end example
+
+The columns are as follows
+
+@table @code
+@item Name/IP Address
+This is the name or dotted-quad IP address of the NTP server (or peer)
+to which the rest of the line relates.
+
+@item NP
+This is the number of sample points currently being retained for the
+server.  The drift rate and current offset are estimated by performing a
+linear regression through these points.
+
+@item NR
+This is the number of runs of residuals having the same sign following
+the last regression.  If this number starts to become too small relative
+to the number of samples, it indicates that a straight line is no longer
+a good fit to the data.  If the number of runs is too low,
+@code{chronyd} discards older samples and re-runs the regression until
+the number of runs becomes acceptable.
+
+@item Span
+This is the interval between the oldest and newest samples.  If no unit
+is shown the value is in seconds.  In the example, the interval is 46
+minutes.
+
+@item Frequency
+This is the estimated residual frequency for the server, in parts per
+million.  In this case, the computer's clock is estimated to be running
+1 part in 10**9 slow relative to the server.
+
+@item Freq Skew
+This is the estimated error bounds on @code{Freq} (again in parts per
+million).
+
+@item Std Dev
+This is the estimated sample standard deviation.
+
+@end table
+@c }}}
+@c {{{ tracking
+@node tracking command
+@subsubsection tracking
+The @code{tracking} command displays parameters about the system's clock
+performance.  An example of the output is shown below.
+
+@example
+Reference ID    : 1.2.3.4 (a.b.c)
+Stratum         : 3
+Ref time (UTC)  : Sun May 17 06:13:11 1998
+System time     : 0.000000 seconds fast of NTP time
+Frequency       : 331.898 ppm fast
+Residual freq   : 0.004 ppm
+Skew            : 0.154 ppm
+Root delay      : 0.373169 seconds
+Root dispersion : 0.024780 seconds
+@end example
+
+The fields are explained as follows.
+
+@table @code
+@item Reference ID
+This is the IP address, and name if available, of the server to which
+the computer is currently synchronised.  If this is @code{127.127.1.1}
+it means the computer is not synchronised to any external source and
+that you have the `local' mode operating (via the @code{local} command
+in @code{chronyc} (@pxref{local command}), or the @code{local} directive
+in the @file{/etc/chrony.conf} file (@pxref{local directive})).
+
+@item Stratum
+The stratum indicates how many hops away from a computer with an
+attached reference clock we are.  Such a computer is a stratum-1
+computer, so the computer in the example is two hops away
+(i.e. @code{a.b.c} is a stratum-2 and is synchronised from a stratum-1).
+
+@item Ref time
+This is the time (GMT) at which the last measurement from the reference
+source was processed.
+
+@item System time
+In normal operation, @code{chronyd} @emph{never} steps the system clock,
+because any jump in the timescale can have adverse consequences for
+certain application programs.  Instead, any error in the system clock is
+corrected by slightly speeding up or slowing down the system clock until
+the error has been removed, and then returning to the system clock's
+normal speed.  A consequence of this is that there will be a period when
+the system clock (as read by other programs using the
+@code{gettimeofday()} system call, or by the @code{date} command in the
+shell) will be different from @code{chronyd's} estimate of the current
+true time (which it reports to NTP clients when it is operating in
+server mode).  The value reported on this line is the difference due to
+this effect.
+
+On systems such as Solaris and SunOS, @code{chronyd} has no means to
+adjust the fundamental rate of the system clock, so keeps the system
+time correct by periodically making offsets to it as though an error had
+been measured.  The build up of these offsets will be observed in this
+report.  On systems such as Linux where @code{chronyd} can adjust the
+fundamental rate of the system clock, this value will show zero unless a
+very recent measurement has shown the system to be error.
+
+@item Frequency
+The `frequency' is the rate by which the system's clock would be would
+be wrong if @code{chronyd} was not correcting it.  It is expressed in
+ppm (parts per million).  For example, a value of 1ppm would mean that
+when the system's clock thinks it has advanced 1 second, it has actually
+advanced by 1.000001 seconds relative to true time.
+
+As you can see in the example, the clock in the computer I developed
+@code{chrony} on is not a very good one - it gains about 30 seconds per
+day!  This was the reason I started to write @code{chrony} in the first
+place.
+@item Residual freq
+This shows the `residual frequency' for the currently selected reference
+source.  This reflects any difference between what the measurements from
+the reference source indicate the frequency should be and the frequency
+currently being used.
+
+The reason this is not always zero is that a smoothing procedure is
+applied to the frequency.  Each time a measurement from the reference
+source is obtained and a new residual frequency computed, the estimated
+accuracy of this residual is compared with the estimated accuracy (see
+`skew' next) of the existing frequency value.  A weighted average is
+computed for the new frequency, with weights depending on these
+accuracies.  If the measurements from the reference source follow a
+consistent trend, the residual will be driven to zero over time.
+
+@item Skew
+This is the estimated error bound on the the frequency.
+
+@item Root delay
+This is the total of the network path delays to the stratum-1 computer
+from which the computer is ultimately synchronised.
+
+In certain extreme situations, this value can be negative.  (This can
+arise in a symmetric peer arrangement where the computers' frequencies
+are not tracking each other and the network delay is very short relative
+to the turn-around time at each computer.)
+
+@item Root dispersion
+This is the total dispersion accumulated through all the computers back
+to the stratum-1 computer from which the computer is ultimately
+synchronised.  Dispersion is due to system clock resolution, statistical
+measurement variations etc.
+
+An absolute bound on the computer's clock accuracy (assuming the
+stratum-1 computer is correct) is given by 
+
+@example
+clock_error <= root_dispersion + (0.5 * |root_delay|)
+@end example
+
+@end table
+@c }}}
+@c {{{ trimrtc
+@node trimrtc command
+@subsubsection trimrtc
+The @code{trimrtc} command is used to correct the system's real time
+clock (RTC) to the main system clock.  It has no effect if the error
+between the two clocks is currently estimated at less than a second (the
+resolution of the RTC is only 1 second).
+
+The command takes no arguments.  It performs the following steps (if the
+RTC is more than 1 second away from the system clock):
+
+@enumerate 1
+@item
+Remember the currently estimated gain/loss rate of the RTC and flush the
+previous measurements.
+@item
+Step the real time clock to bring it within a second of the system clock.
+@item
+Make several measurements to accurately determine the new offset between
+the RTC and the system clock (i.e. the remaining fraction of a second
+error)
+@item
+Save the RTC parameters to the RTC file (specified with the
+@code{rtcfile} directive in the configuration file (@pxref{rtcfile
+directive}).
+@end enumerate
+
+The last step is done as a precaution against the computer suffering a
+power failure before either the daemon exits or the @code{writertc}
+command is issued.
+
+@code{chronyd} will still work perfectly well both whilst operating and
+across machine reboots even if the @code{trimrtc} command is never used
+(and the RTC is allowed to drift away from true time).  The
+@code{trimrtc} command is provided as a method by which it can be
+corrected, in a manner compatible with @code{chronyd} using it to
+maintain accurate time across machine reboots.
+@c }}}
+@c {{{ writertc
+@node writertc command
+@subsubsection writertc
+The @code{writertc} command writes the currently estimated error and
+gain/loss rate parameters for the RTC to the RTC file (specified with
+the @code{rtcfile} directive (@pxref{rtcfile directive})).  This
+information is also written automatically when @code{chronyd} is killed
+(with SIGHUP, SIGINT, SIGQUIT or SIGTERM).
+@c }}}
+@c }}}
+@c }}}
+@c }}}
+@c {{{ apx: porting guide
+@node Porting guide
+@appendix Porting guide
+@c {{{ section top
+This appendix discusses issues that have arisen in writing the
+system-specific parts of the existing ports.  This will provide useful
+information for those attempting to write ports to other systems.
+
+
+@menu
+* System driver files::         What needs to go in a driver file for a
+                                particular type of system
+* Quirks of particular systems::  Problem areas that have been found on ports
+                                already written.
+@end menu
+@c }}}
+@c {{{ S:system driver files
+@node System driver files
+@section System driver files
+The system specific parts of the software are contained in files with
+names like @code{sys_linux.c}.
+
+The following functions are required in a system driver file:
+
+@enumerate
+@item
+A function to read the current frequency
+@item
+A function to set the current frequency
+@item
+A function to slew the system time by a specified delta
+@item
+A function to step the system time by a specified delta
+@item
+A function to work out the error at a particular time between the
+system's clock and @code{chronyd's} estimate of real time.  (This is required
+because some systems have to track real time by making the system time
+follow it in a 'sawtooth' fashion).
+@end enumerate
+
+The @dfn{frequency} is the rate at which the system gains or loses time,
+measured relative to the system when running uncompensated.
+@c }}}
+@c {{{ system quirks
+@node Quirks of particular systems
+@section Quirks of particular systems
+@c {{{ section top
+These sections describe quirks in each system type that needed to be
+investigated to port the software to each system type.
+
+@menu
+* Linux porting quirks::        
+* Solaris 2.5 porting quirks::  
+* SunOS 4.1.4 porting quirks::  
+@end menu
+@c }}}
+@c {{{ linux
+@node Linux porting quirks
+@subsection Linux
+The following quirks have been found in developing the Linux port.
+
+@enumerate 1
+@item
+In order to avoid floating point arithmetic, the kernel uses shifting
+and adding to approximate a scaling of 100/128.  This approximation
+implies that the frequency set via the @code{adjtimex()} system call is
+not the frequency that is actually obtained.  The method of
+approximation varies between kernel versions and must be determined by
+examining the kernel source.  An inverse factor must be included in the
+driver to compensate.
+@item
+In some kernel versions, an @code{adjtimex()} system call with the flags
+bits all zeroed will return the amount of offset still to be corrected.
+In others (e.g. the 2.0 series beyond 2.0.32), the offset must be
+changed in order to get the old offset returned (similar to
+@code{adjtime()} on other systems).
+
+@end enumerate
+@c }}}
+@c {{{ solaris 2.5
+@node Solaris 2.5 porting quirks
+@subsection Solaris 2.5
+
+The following quirks have been found in developing the Solaris port.
+
+@enumerate 1
+@item
+The @code{adjtime()} system call with a zero argument does not cancel an
+adjustment that is in progress - it just reports the remaining
+adjustment.
+@item
+The @code{settimeofday()} system call only observes the seconds part of
+the argument - any fractional seconds part is lost.
+second.
+@item
+The kernel variable @code{dosynctodr} has to be set to zero, otherwise
+the system clock is periodically reset to the real-time clock.
+@end enumerate
+@c }}}
+@c {{{ sunos 4.1.4
+@node SunOS 4.1.4 porting quirks
+@subsection SunOS 4.1.4
+The following quirks have been found in developing the SunOS port.
+
+@enumerate 1
+@item
+The @code{adjtime()} system call truncates its argument to a multiple of
+the system's @code{tickadj} variable.  (@code{chronyd} sets that to 100,
+giving a 1 part in 100 slewing capability for correcting offsets.)
+@item
+The kernel variable @code{dosynctodr} has to be set to zero, otherwise
+the system clock is periodically reset to the real-time clock.
+@end enumerate
+@c }}}
+@c }}}
+@c }}}
+@c {{{ apx:GNU General Public License
+@node GPL
+@appendix GNU General Public License
+
+@center GNU GENERAL PUBLIC LICENSE
+@center        Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+       Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+@c }}}
+@contents
+@bye
+@c vim:cms=@c\ %s:fdm=marker:fdc=5:syntax=off
+
diff --git a/chronyc.1 b/chronyc.1
new file mode 100644 (file)
index 0000000..9ac9e95
--- /dev/null
+++ b/chronyc.1
@@ -0,0 +1,56 @@
+.TH CHRONYC 1 "August 10, 2001" chrony "User's Manual"
+.SH NAME
+chronyc \- command-line interface for chronyd
+
+.SH SYNOPSIS
+.B chronyc
+[\fIOPTIONS\fR]
+
+.SH DESCRIPTION
+\fIchrony\fR is a pair of programs for maintaining the accuracy of computer
+clocks.
+
+\fBchronyc\fR is a command-line interface program which can be used to
+monitor \fIchronyd\fR's performance and to change various operating
+parateters whilst it is running.
+
+.SH USAGE
+A detailed description of all commands supported by \fBchronyc\fR is available
+via the documentation supplied with the distribution (\fIchrony.txt\fR and
+\fIchrony.texi\fR).
+
+.SH OPTIONS
+A summary of the options supported by \fBchronyc\fR is included below.
+
+.TP
+\fB\-h\fR \fIhostname\fR
+specify hostname
+.TP
+\fB\-p\fR \fIport-number\fR
+specify port-number
+.TP
+\fB\-n\fR
+display raw IP addresses (don't attempt to look up hostnames)
+.TP \fIcommand\fR
+specify command.  If no command is given, chronyc will read commands
+interactively.
+
+
+.SH VERSION
+1.17
+
+.SH BUGS
+To report bugs, please contact the author and/or visit \fIhttp://go.to/chrony\fR
+
+.SH "SEE ALSO"
+.BR chronyd(1),
+.BR chrony(1)
+
+.I http://chrony.sunsite.dk/
+
+.SH AUTHOR
+Richard Curnow <rc@rc0.org.uk>
+
+This man-page was written by Jan Schaumann <jschauma@netmeister.org> as part of "The Missing
+Man Pages Project".  Please see \fIhttp://www.netmeister.org/misc/m2p2/index.html\fR
+for details.
diff --git a/chronyd.8 b/chronyd.8
new file mode 100644 (file)
index 0000000..4f5fa06
--- /dev/null
+++ b/chronyd.8
@@ -0,0 +1,111 @@
+.TH CHRONYD 8 "August 10, 2001" chrony "System Administration"
+.SH NAME
+chronyd \- chrony background daemon
+
+.SH SYNOPSIS
+.B chronyd
+[\fIOPTIONS\fR]
+
+.SH DESCRIPTION
+\fIchrony\fR is a pair of programs for maintaining the accuracy of computer
+clocks. \fBchronyd\fR is a background daemon program that can be started at boot
+time.
+
+\fBchronyd\fR is a daemon which runs in background on the
+system.  It obtains measurements (e.g. via the network) of the
+system's offset relative to other systems, and adjusts the system
+time accordingly.  For isolated systems, the user can periodically
+enter the correct time by hand (using \fIchronyc\fR).  In either case,
+\fBchronyd\fR determines the rate at which the computer
+gains or loses time, and compensates for this.
+
+.SH USAGE
+\fBchronyd\fR is usually started at boot-time and requires superuser
+priviliges.
+
+If \fBchronyd\fR has been installed to its default location
+\fI/usr/local/sbin/chronyd\fR, starting it is simply a matter of entering the
+command:
+
+\fI/usr/local/sbin/chronyd\fR
+
+Information messages and warnings will be logged to syslog.
+
+
+.SH OPTIONS
+A summary of the options supported by \fBchronyd\fR is included below.
+
+.TP
+.B \-d
+When run in this mode, the program will not detach itself from the
+terminal, and all messages will be sent to the terminal instead of
+to syslog.
+.TP
+\fB\-f\fR \fIconf-file\fR
+This option can be used to specify an alternate location for the
+configuration file (default \fI/etc/chrony.conf\fR).
+.TP
+.B \-r
+This option will reload sample histories for each of the servers being used.
+These histories are created by using the \fIdump\fR command in \fIchronyc\fR,
+or by setting the \fIdumponexit\fR directive in the configuration file.  This
+option is useful if you want to stop and restart \fBchronyd\fR briefly for any
+reason, e.g. to install a new version.  However, it only makes sense on
+systems where the kernel can maintain clock compensation whilst not under
+\fBchronyd\fR's control.  The only version where this happens so far is Linux.
+On systems where this is not the case, e.g. Solaris and SunOS the option
+should not be used.
+.TP
+.B \-s
+This option will set the system clock from the computer's real-time
+clock.  This is analogous to supplying the \fI-s\fR flag to the
+\fI/sbin/clock\fR program during the Linux boot sequence.
+
+Support for real-time clocks is limited at present - the criteria
+are described in the section on the \fIrtcfile\fR directive in the
+documentation supplied with the distribution.
+
+If \fBchronyd\fR cannot support the real time clock on your computer,
+this option cannot be used and a warning message will be logged to
+the syslog.
+
+If used in conjunction with the \fB-r\fR flag, \fBchronyd\fR will attempt
+to preserve the old samples after setting the system clock from
+the real time clock.  This can be used to allow \fBchronyd\fR to
+perform long term averaging of the gain or loss rate across system
+reboots, and is useful for dial-up systems that are shut down when
+not in use.  For this to work well, it relies on \fBchronyd\fR having
+been able to determine accurate statistics for the difference
+between the real time clock and system clock last time the
+computer was on.
+.TP
+.B \-v
+This option displays \fBchronyd\fR's version number to the terminal and exits
+
+.SH FILES
+\fI/etc/chrony.conf\fR
+
+.SH VERSION
+Version 1.17
+
+.SH BUGS
+To report bugs, please contact the author and/or visit \fIhttp://chrony.sunsite.dk/\fR
+
+.SH "SEE ALSO"
+\fBchronyd\fR is documented in detail in the documentation supplied with the
+distribution (\fIchrony.txt\fR and \fIchrony.texi\fR) and is also available
+from \fIhttp://go.to/chrony\fR
+
+.BR chrony(1),
+.BR chronyc(1),
+.BR chrony.conf(5),
+.BR clock(8),
+.BR xntpd(8),
+.BR ntpd(8)
+
+.SH AUTHOR
+Richard Curnow <rc@rc0.org.uk>
+
+This man-page was written by Jan Schaumann <jschauma@netmeister.org> as part
+of "The Missing Man Pages Project".  Please see
+\fIhttp://www.netmeister.org/misc/m2p2/index.html\fR for details.
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..04d4b44
--- /dev/null
+++ b/client.c
@@ -0,0 +1,2589 @@
+/*
+  $Header: /cvs/src/chrony/client.c,v 1.66 2003/01/20 22:52:07 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Command line client for configuring the daemon and obtaining status
+  from it whilst running.
+  */
+
+#include "sysincl.h"
+
+#include "candm.h"
+#include "nameserv.h"
+#include "md5.h"
+#include "version.h"
+#include "getdate.h"
+#include "cmdparse.h"
+#include "pktlength.h"
+#include "memory.h"
+
+#ifdef FEAT_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+/* ================================================== */
+
+static int sock_fd;
+struct sockaddr_in his_addr;
+
+static int on_terminal = 0;
+
+static int no_dns = 0;
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void process_cmd_manual_list(const char *line);
+static void process_cmd_manual_delete(const char *line);
+
+/* ================================================== */
+/* Ought to extract some code from util.c to make
+   a new set of utilities that can be linked into either
+   the daemon or the client. */
+
+static char *
+time_to_log_form(time_t t)
+{
+  struct tm stm;
+  static char buffer[64];
+  static const char *months[] = {"Jan", "Feb", "Mar", "Apr",
+                                 "May", "Jun", "Jul", "Aug",
+                                 "Sep", "Oct", "Nov", "Dec"};
+
+
+  stm = *gmtime(&t);
+  sprintf(buffer, "%2d%s%02d %02d:%02d:%02d",
+          stm.tm_mday, months[stm.tm_mon], stm.tm_year % 100,
+          stm.tm_hour, stm.tm_min, stm.tm_sec);
+
+  return buffer;
+}
+
+/* ================================================== */
+
+static char *
+UTI_IPToDottedQuad(unsigned long ip)
+{
+  unsigned long a, b, c, d;
+  static char result[64];
+  a = (ip>>24) & 0xff;
+  b = (ip>>16) & 0xff;
+  c = (ip>> 8) & 0xff;
+  d = (ip>> 0) & 0xff;
+  sprintf(result, "%ld.%ld.%ld.%ld", a, b, c, d);
+  return result;
+}
+
+/* ================================================== */
+/* Read a single line of commands from standard input.  Eventually we
+   might want to use the GNU readline library. */
+
+static char *
+read_line(void)
+{
+  static char line[2048];
+  static const char *prompt = "chronyc> ";
+
+  if (on_terminal) {
+#ifdef FEAT_READLINE
+    char *cmd;
+
+    /* save line only if not empty */
+    cmd = readline(prompt);
+    if( cmd == NULL ) return( NULL );
+    
+    /* user pressed return */
+    if( *cmd != '\0' ) {
+      strncpy(line, cmd, sizeof(line) - 1);
+      line[sizeof(line) - 1] = '\0';
+      add_history(cmd);
+      /* free the buffer allocated by readline */
+      free(cmd);
+    } else {
+      /* simulate the user has entered an empty line */
+      *line = '\0';
+    }
+    return( line );
+#else
+    printf(prompt);
+#endif
+  }
+  if (fgets(line, sizeof(line), stdin)) {
+    return line;
+  } else {
+    return NULL;
+  }
+  
+}
+
+/* ================================================== */
+
+static unsigned long
+get_address(const char *hostname)
+{
+  unsigned char *address0;
+  struct hostent *host;
+  unsigned long result;
+
+  /* Note, this call could block for a while */
+  host = gethostbyname(hostname);
+  if (host == NULL) {
+    fprintf(stderr, "Could not get IP address for %s\n", hostname);
+    exit(1);
+  } else {
+    address0 = host->h_addr_list[0];
+    result = ((((unsigned long) address0[0]) << 24) |
+              (((unsigned long) address0[1]) << 16) |
+              (((unsigned long) address0[2]) <<  8) |
+              (((unsigned long) address0[3])));
+  }
+
+  return result;
+
+}
+
+/* ================================================== */
+/* Initialise the socket used to talk to the daemon */
+
+static void
+open_io(const char *hostname, int port)
+{
+  struct sockaddr_in my_addr;
+
+  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+  if (sock_fd < 0) {
+    perror("Can't create socket");
+    exit(1);
+  }
+
+  my_addr.sin_family = AF_INET;
+  my_addr.sin_port = htons(INADDR_ANY);
+  my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+  if(bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
+    perror("Can't bind socket");
+    exit(1);
+  }
+
+  /* Build the socket address structure for sending packets */
+  his_addr.sin_family = AF_INET;
+  his_addr.sin_addr.s_addr = htonl(get_address(hostname));
+
+  /* Eventually the port number needs to be a command line param */
+  his_addr.sin_port = htons(port);
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+close_io(void)
+{
+
+  close(sock_fd);
+
+}
+
+/* ================================================== */
+
+static int
+read_mask_address(char *line, unsigned long *mask, unsigned long *address)
+{
+  unsigned int ma, mb, mc, md, aa, ab, ac, ad;
+  int ok = 0;
+  char *p;
+
+  p = line;
+  while (*p && isspace((unsigned char)*p)) p++;
+  if (!*p) {
+    *mask = *address = 0;
+    ok = 1;
+  } else {
+
+    if (sscanf(line, "%u.%u.%u.%u/%u.%u.%u.%u",
+               &ma, &mb, &mc, &md,
+               &aa, &ab, &ac, &ad) != 8) {
+      fprintf(stderr, "Invalid syntax for mask/address\n");
+      ok = 0;
+    } else {
+      *mask =    (ma << 24) | (mb << 16) | (mc << 8) | md;
+      *address = (aa << 24) | (ab << 16) | (ac << 8) | ad;
+      ok = 1;
+    }
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_offline(CMD_Request *msg, char *line)
+{
+  unsigned long mask, address;
+  int ok;
+
+  if (read_mask_address(line, &mask, &address)) {
+    msg->data.offline.mask = htonl(mask);
+    msg->data.offline.address = htonl(address);
+    msg->command = htons(REQ_OFFLINE);
+    ok = 1;
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+
+static int
+process_cmd_online(CMD_Request *msg, char *line)
+{
+  unsigned long mask, address;
+  int ok;
+
+  if (read_mask_address(line, &mask, &address)) {
+    msg->data.online.mask = htonl(mask);
+    msg->data.online.address = htonl(address);
+    msg->command = htons(REQ_ONLINE);
+    ok = 1;
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static int
+read_address_integer(char *line, unsigned long *address, int *value)
+{
+  char hostname[2048];
+  int ok = 0;
+
+  if (sscanf(line, "%2047s %d", hostname, value) != 2) {
+    fprintf(stderr, "Invalid syntax for address value\n");
+    ok = 0;
+  } else {
+    *address = DNS_Name2IPAddress(hostname);
+    if (*address == DNS_Failed_Address) {
+      fprintf(stderr, "Could not get address for hostname\n");
+      ok = 0;
+    } else {
+      ok = 1;
+    }
+  }
+
+  return ok;
+
+}
+
+
+/* ================================================== */
+
+static int
+read_address_double(char *line, unsigned long *address, double *value)
+{
+  char hostname[2048];
+  int ok = 0;
+
+  if (sscanf(line, "%2047s %lf", hostname, value) != 2) {
+    fprintf(stderr, "Invalid syntax for address value\n");
+    ok = 0;
+  } else {
+    *address = DNS_Name2IPAddress(hostname);
+    if (*address == DNS_Failed_Address) {
+      fprintf(stderr, "Could not get address for hostname\n");
+      ok = 0;
+    } else {
+      ok = 1;
+    }
+  }
+
+  return ok;
+
+}
+
+
+/* ================================================== */
+
+static int
+process_cmd_minpoll(CMD_Request *msg, char *line)
+{
+  unsigned long address;
+  int minpoll;
+  int ok;
+  
+  if (read_address_integer(line, &address, &minpoll)) {
+    msg->data.modify_minpoll.address = htonl(address);
+    msg->data.modify_minpoll.new_minpoll = htonl(minpoll);
+    msg->command = htons(REQ_MODIFY_MINPOLL);
+    ok = 1;
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxpoll(CMD_Request *msg, char *line)
+{
+  unsigned long address;
+  int maxpoll;
+  int ok;
+  
+  if (read_address_integer(line, &address, &maxpoll)) {
+    msg->data.modify_maxpoll.address = htonl(address);
+    msg->data.modify_maxpoll.new_maxpoll = htonl(maxpoll);
+    msg->command = htons(REQ_MODIFY_MAXPOLL);
+    ok = 1;
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxdelay(CMD_Request *msg, char *line)
+{
+  unsigned long address;
+  double max_delay;
+  int ok;
+  
+  if (read_address_double(line, &address, &max_delay)) {
+    msg->data.modify_maxdelay.address = htonl(address);
+    msg->data.modify_maxdelay.new_max_delay = REAL2WIRE(max_delay);
+    msg->command = htons(REQ_MODIFY_MAXDELAY);
+    ok = 1;
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxdelayratio(CMD_Request *msg, char *line)
+{
+  unsigned long address;
+  double max_delay_ratio;
+  int ok;
+  
+  if (read_address_double(line, &address, &max_delay_ratio)) {
+    msg->data.modify_maxdelayratio.address = htonl(address);
+    msg->data.modify_maxdelayratio.new_max_delay_ratio = REAL2WIRE(max_delay_ratio);
+    msg->command = htons(REQ_MODIFY_MAXDELAYRATIO);
+    ok = 1;
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxupdateskew(CMD_Request *msg, char *line)
+{
+  int ok;
+  double new_max_update_skew;
+  
+  if (sscanf(line, "%lf", &new_max_update_skew) == 1) {
+    msg->data.modify_maxupdateskew.new_max_update_skew = REAL2WIRE(new_max_update_skew);
+    msg->command = htons(REQ_MODIFY_MAXUPDATESKEW);
+    ok = 1;
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_dump(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_DUMP);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_writertc(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_WRITERTC);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_trimrtc(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_TRIMRTC);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_cyclelogs(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_CYCLELOGS);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_burst(CMD_Request *msg, char *line)
+{
+  int ok;
+  int n_good_samples, n_total_samples;
+  unsigned int ma, mb, mc, md, aa, ab, ac, ad;
+  int n_parsed;
+
+  n_parsed = sscanf(line, "%d/%d %u.%u.%u.%u/%u.%u.%u.%u",
+                    &n_good_samples,
+                    &n_total_samples,
+                    &ma, &mb, &mc, &md,
+                    &aa, &ab, &ac, &ad);
+
+  msg->command = htons(REQ_BURST);
+  msg->data.burst.n_good_samples = ntohl(n_good_samples);
+  msg->data.burst.n_total_samples = ntohl(n_total_samples);
+
+  if (n_parsed == 10) {
+    msg->data.burst.mask = htonl((ma << 24) | (mb << 16) | (mc << 8) | md);
+    msg->data.burst.address = htonl((aa << 24) | (ab << 16) | (ac << 8) | ad);
+    ok = 1;
+  } else if (n_parsed == 2) {
+    msg->data.burst.mask = 0;
+    msg->data.burst.address = 0;
+    ok = 1;
+  } else {
+    ok = 0;
+    fprintf(stderr, "Invalid syntax for burst command\n");
+  }
+
+  return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_local(CMD_Request *msg, const char *line)
+{
+  const char *p;
+  int stratum;
+
+  p = line;
+  while (*p && isspace((unsigned char)*p)) p++;
+  
+  if (!*p) {
+    return 0;
+  } else if (!strncmp(p, "off", 3)) {
+    msg->data.local.on_off = htonl(0);
+  } else if (sscanf(p, "stratum%d", &stratum) == 1) {
+    msg->data.local.on_off = htonl(1);
+    msg->data.local.stratum = htonl(stratum);
+  } else {
+    fprintf(stderr, "Invalid syntax for local command\n");
+    return 0;
+  }
+
+  msg->command = htons(REQ_LOCAL);
+  return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_manual(CMD_Request *msg, const char *line)
+{
+  const char *p;
+
+  p = line;
+  while (*p && isspace((unsigned char)*p)) p++;
+
+  if (!*p) {
+    return 0;
+  } else if (!strncmp(p, "off", 3)) {
+    msg->data.manual.option = htonl(0);
+  } else if (!strncmp(p, "on", 2)) {
+    msg->data.manual.option = htonl(1);
+  } else if (!strncmp(p, "reset", 5)) {
+    msg->data.manual.option = htonl(2);
+  } else if (!strncmp(p, "list", 4)) {
+    process_cmd_manual_list(p);
+    return 0;
+  } else if (!strncmp(p, "delete", 6)) {
+    process_cmd_manual_delete(p+6);
+    return 0;
+  } else {
+    return 0;
+  }
+  msg->command = htons(REQ_MANUAL);
+
+  return 1;
+}
+
+/* ================================================== */
+
+static int
+parse_allow_deny(CMD_Request *msg, char *line)
+{
+  unsigned long a, b, c, d, n, ip;
+  char *p, *q;
+  
+  p = line;
+  while (*p && isspace((unsigned char)*p)) p++;
+  if (!*p) {
+    /* blank line - applies to all addresses */
+    msg->data.allow_deny.ip = htonl(0);
+    msg->data.allow_deny.subnet_bits = htonl(0);
+  } else {
+    char *slashpos;
+    slashpos = strchr(p, '/');
+    if (slashpos) *slashpos = 0;
+    
+    n = sscanf(p, "%lu.%lu.%lu.%lu", &a, &b, &c, &d);
+
+    if (n == 0) {
+      /* Try to parse as the name of a machine */
+      q = p;
+      while (*q) {
+        if (*q == '\n') *q = 0;
+        q++;
+      }
+      ip = DNS_Name2IPAddress(p);
+      if (ip == DNS_Failed_Address) {
+        fprintf(stderr, "Could not read address\n");
+        return 0;
+      } else {
+        msg->data.allow_deny.ip = htonl(ip);
+        msg->data.allow_deny.subnet_bits = htonl(32);
+      }        
+    } else {
+      
+      a &= 0xff;
+      b &= 0xff;
+      c &= 0xff;
+      d &= 0xff;
+      
+      switch (n) {
+        case 1:
+          msg->data.allow_deny.ip = htonl((a<<24));
+          msg->data.allow_deny.subnet_bits = htonl(8);
+          break;
+        case 2:
+          msg->data.allow_deny.ip = htonl((a<<24) | (b<<16));
+          msg->data.allow_deny.subnet_bits = htonl(16);
+          break;
+        case 3:
+          msg->data.allow_deny.ip = htonl((a<<24) | (b<<16) | (c<<8));
+          msg->data.allow_deny.subnet_bits = htonl(24);
+          break;
+        case 4:
+          msg->data.allow_deny.ip = htonl((a<<24) | (b<<16) | (c<<8) | d);
+          msg->data.allow_deny.subnet_bits = htonl(32);
+          break;
+        default:
+          assert(0);
+          
+      }
+
+      if (slashpos) {
+        int specified_subnet_bits, n;
+        n = sscanf(slashpos+1, "%d", &specified_subnet_bits);
+        if (n == 1) {
+          msg->data.allow_deny.subnet_bits = htonl(specified_subnet_bits);
+        } else {
+          fprintf(stderr, "Warning: badly formatted subnet size, using %ld\n", (long) ntohl(msg->data.allow_deny.subnet_bits));
+        }
+      } 
+    }
+  }
+  return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_allow(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_ALLOW);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_allowall(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_ALLOWALL);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_deny(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_DENY);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_denyall(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_DENYALL);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_cmdallow(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_CMDALLOW);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_cmdallowall(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_CMDALLOWALL);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_cmddeny(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_CMDDENY);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_cmddenyall(CMD_Request *msg, char *line)
+{
+  int status;
+  msg->command = htons(REQ_CMDDENYALL);
+  status = parse_allow_deny(msg, line);
+  return status;
+}
+
+/* ================================================== */
+
+static int
+accheck_getaddr(char *line, unsigned long *addr)
+{
+  unsigned long a, b, c, d, ip;
+  unsigned char *p, *q;
+  p = line;
+  while (*p && isspace(*p)) p++;
+  if (!*p) {
+    return 0;
+  } else {
+    if (sscanf(p, "%lu.%lu.%lu.%lu", &a, &b, &c, &d) == 4) {
+      *addr = (a<<24) | (b<<16) | (c<<8) | d;
+      return 1;
+    } else {
+      q = p;
+      while (*q) {
+        if (*q == '\n') *q = 0;
+        q++;
+      }
+      ip = DNS_Name2IPAddress(p);
+      if (ip == DNS_Failed_Address) {
+        return 0;
+      } else {
+        *addr = ip;
+        return 1;
+      }
+    }
+  }
+}
+
+/* ================================================== */
+
+static int
+process_cmd_accheck(CMD_Request *msg, char *line)
+{
+  unsigned long ip;
+  msg->command = htons(REQ_ACCHECK);
+  if (accheck_getaddr(line, &ip)) {
+    msg->data.ac_check.ip = htonl(ip);
+    return 1;
+  } else {    
+    fprintf(stderr, "Could not read address\n");
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static int
+process_cmd_cmdaccheck(CMD_Request *msg, char *line)
+{
+  unsigned long ip;
+  msg->command = htons(REQ_CMDACCHECK);
+  if (accheck_getaddr(line, &ip)) {
+    msg->data.ac_check.ip = htonl(ip);
+    return 1;
+  } else {    
+    fprintf(stderr, "Could not read address\n");
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static void
+process_cmd_dfreq(CMD_Request *msg, char *line)
+{
+  double dfreq;
+  msg->command = htons(REQ_DFREQ);
+  if (sscanf(line, "%lf", &dfreq) == 1) {
+    msg->data.dfreq.dfreq = REAL2WIRE(dfreq);
+  } else {
+    msg->data.dfreq.dfreq = REAL2WIRE(0.0);
+  }
+}
+
+/* ================================================== */
+
+static void
+cvt_to_sec_usec(double x, long *sec, long *usec) {
+  long s, us;
+  s = (long) x;
+  us = (long)(0.5 + 1.0e6 * (x - (double) s));
+  while (us >= 1000000) {
+    us -= 1000000;
+    s += 1;
+  }
+  while (us < 0) {
+    us += 1000000;
+    s -= 1;
+  }
+  
+  *sec = s;
+  *usec = us;
+  return;
+}
+
+/* ================================================== */
+
+static void
+process_cmd_doffset(CMD_Request *msg, char *line)
+{
+  double doffset;
+  long sec, usec;
+  msg->command = htons(REQ_DOFFSET);
+  if (sscanf(line, "%lf", &doffset) == 1) {
+    cvt_to_sec_usec(doffset, &sec, &usec);
+    msg->data.doffset.sec = htonl(sec);
+    msg->data.doffset.usec = htonl(usec);
+  } else {
+    msg->data.doffset.sec = htonl(0);
+    msg->data.doffset.usec = htonl(0);
+  }
+}
+
+/* ================================================== */
+
+static int
+process_cmd_add_server_or_peer(CMD_Request *msg, char *line)
+{
+  CPS_NTP_Source data;
+  CPS_Status status;
+  int result = 0;
+  
+  status = CPS_ParseNTPSourceAdd(line, &data);
+  switch (status) {
+    case CPS_Success:
+      msg->data.ntp_source.port = htonl((unsigned long) data.port);
+      msg->data.ntp_source.ip_addr = htonl(data.ip_addr);
+      msg->data.ntp_source.minpoll = htonl(data.params.minpoll);
+      msg->data.ntp_source.maxpoll = htonl(data.params.maxpoll);
+      msg->data.ntp_source.presend_minpoll = htonl(data.params.presend_minpoll);
+      msg->data.ntp_source.online = htonl(data.params.online);
+      msg->data.ntp_source.auto_offline = htonl(data.params.auto_offline);
+      msg->data.ntp_source.authkey = htonl(data.params.authkey);
+      msg->data.ntp_source.max_delay = REAL2WIRE(data.params.max_delay);
+      msg->data.ntp_source.max_delay_ratio = REAL2WIRE(data.params.max_delay_ratio);
+      result = 1;
+
+      break;
+    case CPS_BadOption:
+      fprintf(stderr, "Unrecognized subcommand\n");
+      break;
+    case CPS_BadHost:
+      fprintf(stderr, "Invalid host/IP address\n");
+      break;
+    case CPS_BadPort:
+      fprintf(stderr, "Unreadable port number\n");
+      break;
+    case CPS_BadMinpoll:
+      fprintf(stderr, "Unreadable minpoll value\n");
+      break;
+    case CPS_BadMaxpoll:
+      fprintf(stderr, "Unreadable maxpoll value\n");
+      break;
+    case CPS_BadPresend:
+      fprintf(stderr, "Unreadable presend value\n");
+      break;
+    case CPS_BadMaxdelayratio:
+      fprintf(stderr, "Unreadable max delay ratio value\n");
+      break;
+    case CPS_BadMaxdelay:
+      fprintf(stderr, "Unreadable max delay value\n");
+      break;
+    case CPS_BadKey:
+      fprintf(stderr, "Unreadable key value\n");
+      break;
+  }
+
+  return result;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_add_server(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_ADD_SERVER);
+  return process_cmd_add_server_or_peer(msg, line);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_add_peer(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_ADD_PEER);
+  return process_cmd_add_server_or_peer(msg, line);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_delete(CMD_Request *msg, char *line)
+{
+  char hostname[2048];
+  int ok = 0;
+  unsigned long address = 0UL;
+
+  msg->command = htons(REQ_DEL_SOURCE);
+
+  if (sscanf(line, "%2047s", hostname) != 1) {
+    fprintf(stderr, "Invalid syntax for address\n");
+    ok = 0;
+  } else {
+    address = DNS_Name2IPAddress(hostname);
+    if (address == DNS_Failed_Address) {
+      fprintf(stderr, "Could not get address for hostname\n");
+      ok = 0;
+    } else {
+      ok = 1;
+    }
+  }
+
+  msg->data.del_source.ip_addr = htonl(address);
+
+  return ok;
+  
+}
+
+/* ================================================== */
+
+static int password_seen = 0;
+static MD5_CTX md5_after_just_password;
+
+/* ================================================== */
+
+static int
+process_cmd_password(CMD_Request *msg, char *line)
+{
+  char *p, *q;
+  char *password;
+  struct timezone tz;
+
+  p = line;
+  while (*p && isspace((unsigned char)*p))
+    p++;
+
+  /* Get rid of trailing newline */
+  for (q=p; *q; q++) {
+    if (isspace((unsigned char)*q)) *q = 0;
+  }
+
+  if (*p) {
+    password = p;
+  } else {
+    /* blank line, prompt for password */
+    password = getpass("Password: ");
+  }
+
+  if (!*password) {
+    password_seen = 0;
+  } else {
+    password_seen = 1;
+  }
+
+  /* Generate MD5 initial context */
+  MD5Init(&md5_after_just_password);
+  MD5Update(&md5_after_just_password, (unsigned char *) password, strlen(password));
+  
+  /* Blank the password for security */
+  for (p = password; *p; p++) {
+    *p = 0;
+  }
+    
+  if (gettimeofday(&msg->data.logon.ts, &tz) < 0) {
+    printf("500 - Could not read time of day\n");
+    return 0;
+  } else {
+    msg->command = htons(REQ_LOGON); /* Just force a round trip so that we get tokens etc */
+    msg->data.logon.ts.tv_sec = htonl(msg->data.logon.ts.tv_sec);
+    msg->data.logon.ts.tv_usec = htonl(msg->data.logon.ts.tv_usec);
+    return 1;
+  }
+}
+
+/* ================================================== */
+
+static void
+generate_auth(CMD_Request *msg)
+{
+  MD5_CTX ctx;
+  int pkt_len;
+
+  pkt_len = PKL_CommandLength(msg);
+  ctx = md5_after_just_password;
+  MD5Update(&ctx, (unsigned char *) msg, offsetof(CMD_Request, auth));
+  if (pkt_len > offsetof(CMD_Request, data)) {
+    MD5Update(&ctx, (unsigned char *) &(msg->data), pkt_len - offsetof(CMD_Request, data));
+  }
+  MD5Final(&ctx);
+  memcpy(&(msg->auth), &ctx.digest, 16);
+}
+
+/* ================================================== */
+
+static int
+check_reply_auth(CMD_Reply *msg)
+{
+  int pkt_len;
+  MD5_CTX ctx;
+
+  pkt_len = PKL_ReplyLength(msg);
+  ctx = md5_after_just_password;
+  MD5Update(&ctx, (unsigned char *) msg, offsetof(CMD_Request, auth));
+  if (pkt_len > offsetof(CMD_Reply, data)) {
+    MD5Update(&ctx, (unsigned char *) &(msg->data), pkt_len - offsetof(CMD_Reply, data));
+  }
+  MD5Final(&ctx);
+
+  if (!memcmp((void *) &ctx.digest, (void *) &(msg->auth), 16)) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static void
+give_help(void)
+{
+  printf("205 - Help follows\n");
+  printf("accheck <address> : Check whether NTP access is allowed to <address>\n");
+  printf("activity : Check how many NTP sources are online/offline\n");
+  printf("allow [<subnet-addr>] : Allow NTP access to that subnet as a default\n");
+  printf("allow all [<subnet-addr>] : Allow NTP access to that subnet and all children\n");
+  printf("burst <n-good>/<n-max> [<mask>/<masked-address>] : Start a rapid set of measurements\n");
+  printf("clients : Report on clients that have accessed the server\n");
+  printf("cmdaccheck <address> : Check whether command access is allowed to <address>\n");
+  printf("cmdallow [<subnet-addr>] : Allow command access to that subnet as a default\n");
+  printf("cmdallow all [<subnet-addr>] : Allow command access to that subnet and all children\n");
+  printf("cmddeny [<subnet-addr>] : Deny command access to that subnet as a default\n");
+  printf("cmddeny all [<subnet-addr>] : Deny command access to that subnet and all children\n");
+  printf("cyclelogs : Close and re-open logs files\n");
+  printf("deny [<subnet-addr>] : Deny NTP access to that subnet as a default\n");
+  printf("deny all [<subnet-addr>] : Deny NTP access to that subnet and all children\n");
+  printf("dump : Dump all measurements to save files\n");
+  printf("exit : Leave the program\n");
+  printf("help : Generate this help\n");
+  printf("local off : Disable server capability for unsynchronised clock\n");
+  printf("local stratum <stratum> : Enable server capability for unsynchronised clock\n");
+  printf("makestep : Jump the time to remove any correction being slewed\n");
+  printf("manual off|on|reset : Disable/enable/reset settime command and statistics\n");
+  printf("manual list : Show previous settime entries\n");
+  printf("minpoll <address> <new-minpoll> : Modify minimum polling interval of source\n");
+  printf("maxdelay <address> <new-max-delay> : Modify maximum round-trip valid sample delay for source\n");
+  printf("maxdelayratio <address> <new-max-ratio> : Modify max round-trip delay ratio for source\n");
+  printf("maxpoll <address> <new-minpoll> : Modify maximum polling interval of source\n");
+  printf("maxupdateskew <new-max-skew> : Modify maximum skew for a clock frequency update to be made\n");
+  printf("offline [<mask>/<masked-address>] : Set sources in subnet to offline status\n");
+  printf("online [<mask>/<masked-address>] : Set sources in subnet to online status\n");
+  printf("password [<new-password>] : Set command authentication password\n");
+  printf("quit : Leave the program\n");
+  printf("rtcdata : Print current RTC performance parameters\n");
+  printf("settime <date/time (e.g. Nov 21, 1997 16:30:05 or 16:30:05)> : Manually set the daemon time\n");
+  printf("sources [-v] : Display information about current sources\n");
+  printf("sourcestats [-v] : Display estimation information about current sources\n");
+  printf("tracking : Display system time information\n");
+  printf("trimrtc : Correct RTC relative to system clock\n");
+  printf("writertc : Save RTC parameters to file\n");
+  printf(".\n");
+}
+
+/* ================================================== */
+
+static unsigned long sequence = 0;
+static unsigned long utoken = 0;
+static unsigned long token = 0;
+
+#define MAX_ATTEMPTS 5
+
+
+/* This is the core protocol module.  Complete particular fields in
+   the outgoing packet, send it, wait for a response, handle retries,
+   etc.  Returns a Boolean indicating whether the protocol was
+   successful or not.*/
+
+static int
+submit_request(CMD_Request *request, CMD_Reply *reply, int *reply_auth_ok)
+{
+  unsigned long tx_sequence;
+  int where_from_len;
+  struct sockaddr_in where_from;
+  int bad_length, bad_sender, bad_sequence, bad_header;
+  int select_status;
+  int recvfrom_status;
+  int read_length;
+  int expected_length;
+  int command_length;
+  struct timeval timeout;
+  int timeout_seconds;
+  int n_attempts;
+  fd_set rdfd, wrfd, exfd;
+
+  request->version = PROTO_VERSION_NUMBER;
+  request->pkt_type = PKT_TYPE_CMD_REQUEST;
+  request->res1 = 0;
+  request->res2 = 0;
+  tx_sequence = sequence++;
+  request->sequence = htonl(tx_sequence);
+  request->attempt = 0;
+  request->utoken = htonl(utoken);
+  request->token = htonl(token);
+
+
+  timeout_seconds = 120;
+
+  n_attempts = 0;
+
+  do {
+
+    /* Decide whether to authenticate */
+    if (password_seen) {
+      if (!utoken || (request->command == htons(REQ_LOGON))) {
+        /* Otherwise, the daemon won't bother authenticating our
+           packet and we won't get a token back */
+        request->utoken = htonl(SPECIAL_UTOKEN);
+      }
+      generate_auth(request);
+    }
+
+    command_length = PKL_CommandLength(request);
+#if 0
+    printf("Sent command length=%d bytes\n", command_length);
+#endif
+
+    if (sendto(sock_fd, (void *) request, command_length, 0,
+               (struct sockaddr *) &his_addr, sizeof(his_addr)) < 0) {
+
+
+#if 0
+      perror("Could not send packet");
+#endif
+      return 0;
+    }
+
+    /* Increment this for next time */
+    ++ request->attempt;
+      
+    timeout.tv_sec = timeout_seconds;
+    timeout.tv_usec = 0;
+
+    timeout_seconds += 1;
+    FD_ZERO(&rdfd);
+    FD_ZERO(&wrfd);
+    FD_ZERO(&exfd);
+
+    FD_SET(sock_fd, &rdfd);
+
+    select_status = select(sock_fd + 1, &rdfd, &wrfd, &exfd, &timeout);
+
+    if (select_status < 0) {
+#if 0
+      perror("Select returned negative status");
+#endif
+    } else if (select_status == 0) {
+      /* Timeout must have elapsed, try a resend? */
+      n_attempts ++;
+      if (n_attempts == MAX_ATTEMPTS) {
+        return 0;
+      }
+
+      /* Back to top of loop to do resend */
+      continue;
+      
+    } else {
+      
+      where_from_len = sizeof(where_from);
+      recvfrom_status = recvfrom(sock_fd, (void *) reply, sizeof(CMD_Reply), 0,
+                                 (struct sockaddr *) &where_from, &where_from_len);
+      
+
+#if 0
+      printf("Received packet, status=%d\n", recvfrom_status);
+#endif
+
+      if (recvfrom_status < 0) {
+        /* If we get connrefused here, it suggests the sendto is
+           going to a dead port - but only if the daemon machine is
+           running Linux (Solaris doesn't return anything) */
+        n_attempts++;
+        if (n_attempts == MAX_ATTEMPTS) {
+          return 0;
+        }
+      } else {
+        
+        read_length = recvfrom_status;
+        expected_length = PKL_ReplyLength(reply);
+
+        bad_length = (read_length != expected_length);
+        bad_sender = ((where_from.sin_addr.s_addr != his_addr.sin_addr.s_addr) ||
+                      (where_from.sin_port != his_addr.sin_port));
+        
+        if (!bad_length) {
+          bad_sequence = (ntohl(reply->sequence) != tx_sequence);
+        } else {
+          bad_sequence = 0;
+        }
+        
+        if (bad_length || bad_sender || bad_sequence) {
+          n_attempts++;
+          if (n_attempts == MAX_ATTEMPTS) {
+            return 0;
+          }
+          continue;
+        }
+        
+        bad_header = ((reply->version != PROTO_VERSION_NUMBER) ||
+                      (reply->pkt_type != PKT_TYPE_CMD_REPLY) ||
+                      (reply->res1 != 0) ||
+                      (reply->res2 != 0));
+        
+        if (bad_header) {
+          n_attempts++;
+          if (n_attempts == MAX_ATTEMPTS) {
+            return 0;
+          }
+          continue;
+        }
+        
+        /* Good packet received, print out results */
+#if 0
+        printf("Reply cmd=%d reply=%d stat=%d num=%d tot=%d seq=%d utok=%08lx tok=%d\n",
+               ntohs(reply->command), ntohs(reply->reply),
+               ntohs(reply->status), ntohs(reply->number),
+               ntohs(reply->total),
+               ntohl(reply->sequence),
+               ntohl(reply->utoken),
+               ntohl(reply->token));
+#endif
+
+        if (password_seen) {
+          *reply_auth_ok = check_reply_auth(reply);
+        } else {
+          /* Assume in this case that the reply is always considered
+             to be authentic */
+          *reply_auth_ok = 1;
+        }
+            
+        utoken = ntohl(reply->utoken);
+
+        if (*reply_auth_ok) {
+          /* If we're in authenticated mode, only acquire the utoken
+             and new token values if the reply authenticated properly.
+             This protects against forged packets with bogus tokens
+             in.  We won't accept a repeat of an old message with a
+             stale token in it, due to bad_sequence processing
+             earlier. */
+          utoken = ntohl(reply->utoken);
+          token = ntohl(reply->token);
+        }
+        
+        break;
+
+      }
+    }
+  } while (1);
+
+  return 1;
+}
+
+/* ================================================== */
+
+static void
+print_seconds(unsigned long s)
+{
+  unsigned long d;
+  if (s <= 1024) {
+    printf("%4ld", s);
+  } else if (s < 36000) {
+    printf("%3ldm", s / 60);
+  } else if (s < 345600) {
+    printf("%3ldh", s / 3600);
+  } else {
+    d = s / 86400;
+    if (d > 999) {
+      printf("%3ldy", d / 365);
+    } else {
+      printf("%3ldd", d);
+    }
+  }
+}
+
+/* ================================================== */
+
+static void
+print_microseconds(unsigned long us)
+{
+  if (us <= 9999) {
+    printf("%4ldus", us);
+  } else if (us <= 9999999) {
+    printf("%4ldms", us / 1000);
+  } else if (us <= 999999999) {
+    printf("%3ld.%01lds", us / 1000000, (us/100000) % 10);
+  } else {
+    printf("%5lds", us / 1000000);
+  }
+}
+
+/* ================================================== */
+
+static void
+print_signed_microseconds(long us)
+{
+  long x = abs(us);
+  if (x <= 9999) {
+    printf("%+5ldus", us);
+  } else if (x <= 9999999) {
+    printf("%+5ldms", us / 1000);
+  } else {
+    printf("%+6lds", us / 1000000);
+  }
+}
+
+/* ================================================== */
+
+static int
+check_for_verbose_flag(char *line)
+{
+  char *p = line;
+  while (*p && isspace((unsigned char)*p)) p++;
+  if (!strncmp(p, "-v", 2)) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static void
+process_cmd_sources(char *line)
+{
+  int submit_ok;
+  int auth_ok;
+  CMD_Request request;
+  CMD_Reply reply;
+  int n_sources, i;
+  int verbose = 0;
+
+  long orig_latest_meas, latest_meas, est_offset;
+  unsigned long ip_addr;
+  unsigned long latest_meas_err, est_offset_err;
+  unsigned long latest_meas_ago;
+  unsigned short poll, stratum;
+  unsigned short state, mode;
+  double resid_freq, resid_skew;
+  const char *dns_lookup;
+  char hostname_buf[32];
+  unsigned short status;
+
+  /* Check whether to output verbose headers */
+  verbose = check_for_verbose_flag(line);
+  
+  request.command = htons(REQ_N_SOURCES);
+  submit_ok = submit_request(&request, &reply, &auth_ok);
+
+  if (submit_ok) {
+    status = ntohs(reply.status);
+    switch (status) {
+      case STT_INVALID:
+        printf("502 Invalid command\n");
+        return;
+        break;
+      case STT_NOHOSTACCESS:
+        printf("510 No command access from this host\n");
+        return;
+        break;
+      default:
+        break;
+    }
+
+    n_sources = ntohl(reply.data.n_sources.n_sources);
+    printf("210 Number of sources = %d\n", n_sources);
+    if (verbose) {
+      printf("\n");
+      printf("  .-- Source mode  '^' = server, '=' = peer, '#' = local clock.\n");
+      printf(" / .- Source state '*' = current synced, '+' = OK for sync, '?' = unreachable,\n");
+      printf("| /                'x' = time may be in error, '~' = time is too variable.\n");
+      printf("||                                                 .- xxxx [ yyyy ] +/- zzzz\n");
+      printf("||                                                /   xxxx = adjusted offset,\n");
+      printf("||         Log2(Polling interval) -.             |    yyyy = measured offset,\n");
+      printf("||                                  \\            |    zzzz = estimated error.\n");
+      printf("||                                   |           |                         \n");
+    }
+
+    printf("MS Name/IP address           Stratum Poll LastRx Last sample\n");
+    printf("============================================================================\n");
+
+    /*     "MS NNNNNNNNNNNNNNNNNNNNNNNNN    SS    PP   RRRR  SSSSSSS[SSSSSSS] +/- SSSSSS" */
+
+    for (i=0; i<n_sources; i++) {
+      request.command = htons(REQ_SOURCE_DATA);
+      request.data.source_data.index = htonl(i);
+      submit_ok = submit_request(&request, &reply, &auth_ok);
+
+      if (submit_ok) {
+        if (ntohs(reply.status) == STT_SUCCESS) {
+          ip_addr = ntohl(reply.data.source_data.ip_addr);
+          poll = ntohs(reply.data.source_data.poll);
+          stratum = ntohs(reply.data.source_data.stratum);
+          state = ntohs(reply.data.source_data.state);
+          mode = ntohs(reply.data.source_data.mode);
+          latest_meas_ago = ntohl(reply.data.source_data.since_sample);
+          orig_latest_meas = ntohl(reply.data.source_data.orig_latest_meas);
+          latest_meas = ntohl(reply.data.source_data.latest_meas);
+          latest_meas_err = ntohl(reply.data.source_data.latest_meas_err);
+          est_offset = ntohl(reply.data.source_data.est_offset);
+          est_offset_err = ntohl(reply.data.source_data.est_offset_err);
+          resid_freq = (double) ((long) ntohl(reply.data.source_data.resid_freq)) * 1.0e-3;
+          resid_skew = (double) (ntohl(reply.data.source_data.resid_skew)) * 1.0e-3;
+
+          hostname_buf[25] = 0;
+          if (no_dns) {
+            sprintf(hostname_buf, "%s", UTI_IPToDottedQuad(ip_addr));
+          } else {
+            dns_lookup = DNS_IPAddress2Name(ip_addr);
+            strncpy(hostname_buf, dns_lookup, 25);
+          }
+
+          switch (mode) {
+            case RPY_SD_MD_CLIENT:
+              printf("^"); break;
+            case RPY_SD_MD_PEER:
+              printf("="); break;
+            case RPY_SD_MD_REF:
+              printf("#"); break;
+          }
+          switch (state) {
+            case RPY_SD_ST_SYNC:
+              printf("*"); break;
+            case RPY_SD_ST_UNREACH:
+              printf("?"); break;
+            case RPY_SD_ST_FALSETICKER:
+              printf("x"); break;
+            case RPY_SD_ST_JITTERY:
+              printf("~"); break;
+            case RPY_SD_ST_OTHER:
+              printf("+"); break;
+          }
+
+          printf(" %-25s    %2d   %2d   ", hostname_buf, stratum, poll);
+          print_seconds(latest_meas_ago);
+          printf("  ");
+          print_signed_microseconds(latest_meas);
+          printf("[");
+          print_signed_microseconds(orig_latest_meas);
+          printf("]");
+          printf(" +/- ");
+          print_microseconds(latest_meas_err);
+          printf("\n");
+        }
+      }
+    }
+  } else {
+    printf("506 Cannot talk to daemon\n");
+  }
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_sourcestats(char *line)
+{
+  int submit_ok;
+  int auth_ok;
+  CMD_Request request;
+  CMD_Reply reply;
+  int n_sources, i;
+  int verbose = 0;
+
+  const char *dns_lookup;
+  char hostname_buf[32];
+  unsigned long n_samples, n_runs, span_seconds;
+  double resid_freq_ppm, skew_ppm;
+  unsigned long sd_us;
+  unsigned long ip_addr;
+  unsigned short status;
+
+  verbose = check_for_verbose_flag(line);
+
+  request.command = htons(REQ_N_SOURCES);
+  submit_ok = submit_request(&request, &reply, &auth_ok);
+
+  if (submit_ok) {
+    status = ntohs(reply.status);
+    switch (status) {
+      case STT_INVALID:
+        printf("502 Invalid command\n");
+        return;
+        break;
+      case STT_NOHOSTACCESS:
+        printf("510 No command access from this host\n");
+        return;
+        break;
+      default:
+        break;
+    }
+
+    n_sources = ntohl(reply.data.n_sources.n_sources);
+    printf("210 Number of sources = %d\n", n_sources);
+    if (verbose) {
+      printf("                             .- Number of sample points in measurement set.\n");
+      printf("                            /    .- Number of residual runs with same sign.\n");
+      printf("                           |    /    .- Length of measurement set (time).\n");
+      printf("                           |   |    /      .- Est. clock freq error (ppm).\n");
+      printf("                           |   |   |      /            .- Est error in freq.\n");
+      printf("                           |   |   |     |            /            .- On the\n");
+      printf("                           |   |   |     |           |            /   samples.\n");
+      printf("                           |   |   |     |           |           |\n");
+    }
+
+    printf("Name/IP Address            NP  NR  Span  Frequency   Freq Skew   Std Dev\n");
+    printf("========================================================================\n");
+
+    /*      NNNNNNNNNNNNNNNNNNNNNNNNN  NP  NR  SSSS  FFFFFFFFFF  SSSSSSSSSS  SSSSSSS */
+
+    for (i=0; i<n_sources; i++) {
+      request.command = htons(REQ_SOURCESTATS);
+      request.data.source_data.index = htonl(i);
+      submit_ok = submit_request(&request, &reply, &auth_ok);
+
+      if (submit_ok) {
+        if (ntohs(reply.status) == STT_SUCCESS) {
+          
+          ip_addr = ntohl(reply.data.sourcestats.ip_addr);
+          n_samples = ntohl(reply.data.sourcestats.n_samples);
+          n_runs = ntohl(reply.data.sourcestats.n_runs);
+          span_seconds = ntohl(reply.data.sourcestats.span_seconds);
+          resid_freq_ppm = WIRE2REAL(reply.data.sourcestats.resid_freq_ppm);
+          skew_ppm = WIRE2REAL(reply.data.sourcestats.skew_ppm);
+          sd_us = ntohl(reply.data.sourcestats.sd_us);
+
+          hostname_buf[25] = 0;
+          if (no_dns) {
+            sprintf(hostname_buf, "%s", UTI_IPToDottedQuad(ip_addr));
+          } else {
+            dns_lookup = DNS_IPAddress2Name(ip_addr);
+            strncpy(hostname_buf, dns_lookup, 25);
+          }
+
+          printf("%-25s  %2lu  %2lu  ", hostname_buf, n_samples, n_runs);
+          print_seconds(span_seconds);
+          printf("  %10.3f  %10.3f  ", resid_freq_ppm, skew_ppm);
+          print_microseconds(sd_us);
+          printf("\n");
+        }
+      }
+    }
+  } else {
+    printf("506 Cannot talk to daemon\n");
+  }
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_tracking(char *line)
+{
+  int status;
+  int submit_ok;
+  int auth_ok;
+  CMD_Request request;
+  CMD_Reply reply;
+  unsigned long ref_id;
+  struct timeval ref_time;
+  struct tm ref_time_tm;
+  unsigned long a, b, c, d;
+  struct timeval correction_tv;
+  double correction;
+  double freq_ppm;
+  double resid_freq_ppm;
+  double skew_ppm;
+  double root_delay;
+  double root_dispersion;
+  
+  request.command = htons(REQ_TRACKING);
+  submit_ok = submit_request(&request, &reply, &auth_ok);
+
+  if (submit_ok) {
+    status = ntohs(reply.status);
+    switch (status) {
+      case STT_INVALID:
+        printf("502 Invalid command\n");
+        return;
+        break;
+      case STT_NOHOSTACCESS:
+        printf("510 No command access from this host\n");
+        return;
+        break;
+      default:
+        break;
+    }
+
+    ref_id = ntohl(reply.data.tracking.ref_id);
+    a = (ref_id >> 24);
+    b = (ref_id >> 16) & 0xff;
+    c = (ref_id >> 8) & 0xff;
+    d = (ref_id) & 0xff;
+    printf("Reference ID    : %lu.%lu.%lu.%lu (%s)\n",
+           a, b, c, d,
+           (no_dns) ? UTI_IPToDottedQuad(ref_id) : DNS_IPAddress2Name(ref_id));
+    printf("Stratum         : %lu\n", (unsigned long) ntohl(reply.data.tracking.stratum));
+    ref_time.tv_sec = ntohl(reply.data.tracking.ref_time_s);
+    ref_time.tv_usec = ntohl(reply.data.tracking.ref_time_us);
+    ref_time_tm = *gmtime((time_t *)&ref_time.tv_sec);
+    printf("Ref time (UTC)  : %s", asctime(&ref_time_tm));
+    correction_tv.tv_sec = ntohl(reply.data.tracking.current_correction_s);
+    correction_tv.tv_usec = ntohl(reply.data.tracking.current_correction_us);
+    correction = (double) correction_tv.tv_sec + 1.0e-6 * correction_tv.tv_usec;
+    printf("System time     : %.6f seconds %s of NTP time\n", fabs(correction),
+           (correction > 0.0) ? "slow" : "fast");
+    freq_ppm = WIRE2REAL(reply.data.tracking.freq_ppm);
+    resid_freq_ppm = WIRE2REAL(reply.data.tracking.resid_freq_ppm);
+    skew_ppm = WIRE2REAL(reply.data.tracking.skew_ppm);
+    root_delay = WIRE2REAL(reply.data.tracking.root_delay);
+    root_dispersion = WIRE2REAL(reply.data.tracking.root_dispersion);
+    printf("Frequency       : %.3f ppm %s\n", fabs(freq_ppm), (freq_ppm < 0.0) ? "slow" : "fast"); 
+    printf("Residual freq   : %.3f ppm\n", resid_freq_ppm);
+    printf("Skew            : %.3f ppm\n", skew_ppm);
+    printf("Root delay      : %.6f seconds\n", root_delay);
+    printf("Root dispersion : %.6f seconds\n", root_dispersion);
+  } else {
+    printf("506 Cannot talk to daemon\n");
+  }
+}
+/* ================================================== */
+
+static void
+process_cmd_rtcreport(char *line)
+{
+  int status;
+  int submit_ok;
+  int auth_ok;
+  CMD_Request request;
+  CMD_Reply reply;
+  time_t ref_time;
+  struct tm ref_time_tm;
+  unsigned short n_samples;
+  unsigned short n_runs;
+  unsigned long span_seconds;
+  double coef_seconds_fast;
+  double coef_gain_rate_ppm;
+  
+  request.command = htons(REQ_RTCREPORT);
+  submit_ok = submit_request(&request, &reply, &auth_ok);
+
+  if (submit_ok) {
+    status = ntohs(reply.status);
+    switch (status) {
+      case STT_INVALID:
+        printf("502 Invalid command\n");
+        return;
+        break;
+      case STT_NORTC:
+        printf("513 No RTC driver\n");
+        return;
+        break;
+      case STT_NOHOSTACCESS:
+        printf("510 No command access from this host\n");
+        return;
+        break;
+      default:
+        break;
+    }
+    ref_time = (time_t) ntohl(reply.data.rtc.ref_time);
+    ref_time_tm = *gmtime(&ref_time);
+    n_samples = ntohs(reply.data.rtc.n_samples);
+    n_runs = ntohs(reply.data.rtc.n_runs);
+    span_seconds = ntohl(reply.data.rtc.span_seconds);
+    coef_seconds_fast = WIRE2REAL(reply.data.rtc.rtc_seconds_fast);
+    coef_gain_rate_ppm = WIRE2REAL(reply.data.rtc.rtc_gain_rate_ppm);
+    printf("RTC ref time (UTC) : %s", asctime(&ref_time_tm));
+    printf("Number of samples  : %d\n", n_samples);
+    printf("Number of runs     : %d\n", n_runs);
+    printf("Sample span period : ");
+    print_seconds(span_seconds);
+    printf("\n");
+    printf("RTC is fast by     : %12.6f seconds\n", coef_seconds_fast);
+    printf("RTC gains time at  : %9.3f ppm\n", coef_gain_rate_ppm);
+  } else {
+    printf("506 Cannot talk to daemon\n");
+  }
+}
+
+/* ================================================== */
+
+#if 0
+
+/* This is a previous attempt at implementing the clients command.  It
+   could be re-instated sometime as a way of looking at all clients in a
+   particular subnet.  The problem with it is that is requires at least 5
+   round trips to the server even if the server only has one client to
+   report. */
+
+typedef struct XSubnetToDo {
+  struct XSubnetToDo *next;
+  unsigned long ip;
+  unsigned long bits;
+} SubnetToDo;
+
+static void
+process_cmd_clients(char *line)
+{
+  CMD_Request request;
+  CMD_Reply reply;
+  SubnetToDo *head, *todo, *tail, *p, *next_node, *new_node;
+  int submit_ok, auth_ok;
+  int status;
+  int i, j, nets_looked_up, clients_looked_up;
+  int word;
+  unsigned long mask;
+  unsigned long ip, bits;
+  unsigned long client_hits;
+  unsigned long peer_hits;
+  unsigned long cmd_hits_auth;
+  unsigned long cmd_hits_normal;
+  unsigned long cmd_hits_bad;
+  unsigned long last_ntp_hit_ago;
+  unsigned long last_cmd_hit_ago;
+  char hostname_buf[32];
+  const char *dns_lookup;
+
+  int n_replies;
+
+  head = todo = MallocNew(SubnetToDo);
+  todo->next = NULL;
+  /* Set up initial query = root subnet */
+  todo->ip = 0;
+  todo->bits = 0;
+  tail = todo;
+
+  do {
+
+    request.command = htons(REQ_SUBNETS_ACCESSED);
+    /* Build list of subnets to examine */
+    i=0;
+    p=todo;
+    while((i < MAX_SUBNETS_ACCESSED) &&
+          p &&
+          (p->bits < 32)) {
+      
+      request.data.subnets_accessed.subnets[i].ip = htonl(p->ip);
+      request.data.subnets_accessed.subnets[i].bits_specd = htonl(p->bits);
+      p = p->next;
+      i++;
+    }         
+
+    nets_looked_up = i;
+
+    if (nets_looked_up == 0) {
+      /* No subnets need examining */
+      break;
+    }
+
+    request.data.subnets_accessed.n_subnets = htonl(nets_looked_up);
+
+    submit_ok = submit_request(&request, &reply, &auth_ok);
+  
+    if (submit_ok) {
+      status = ntohs(reply.status);
+      switch (status) {
+        case STT_SUCCESS:
+          n_replies = ntohl(reply.data.subnets_accessed.n_subnets);
+          for (j=0; j<n_replies; j++) {
+            ip = ntohl(reply.data.subnets_accessed.subnets[j].ip);
+            bits = ntohl(reply.data.subnets_accessed.subnets[j].bits_specd);
+            for (i=0; i<256; i++) {
+              word = i/32;
+              mask = 1UL << (i%32);
+              if (ntohl(reply.data.subnets_accessed.subnets[j].bitmap[word]) & mask) {
+                /* Add this subnet to the todo list */
+                new_node = MallocNew(SubnetToDo);
+                new_node->next = NULL;
+                new_node->bits = bits + 8;
+                new_node->ip = ip | (i << (24 - bits));
+                tail->next = new_node;
+                tail = new_node;
+#if 0
+                printf("%08lx %2d %3d %08lx\n", ip, bits, i, new_node->ip);
+#endif
+              }
+            }
+          }
+
+          /* Skip the todo pointer forwards by the number of nets looked
+             up.  Can't do this earlier, because we might have to point
+             at the next layer of subnets that have only just been
+             concatenated to the linked list. */
+          for (i=0; i<nets_looked_up; i++) {
+            todo = todo->next;
+          }
+          
+          break;
+        case STT_BADSUBNET:
+          /* We should never generate any bad subnet messages */
+          assert(0);
+          break;
+        case STT_INACTIVE:
+          printf("519 Client logging is not active in the daemon\n");
+          goto cleanup;
+        case STT_NOHOSTACCESS:
+          printf("510 No command access from this host\n");
+          goto cleanup;
+        default:
+          printf("520 Got unexpected error from daemon\n");
+          goto cleanup;
+      }
+      
+    } else {
+      printf("506 Cannot talk to daemon\n");
+      return;
+    }
+  } while (1); /* keep going until all subnets have been expanded,
+                  down to single nodes */
+
+  /* Now the todo list consists of client records */
+  request.command = htons(REQ_CLIENT_ACCESSES);
+
+#if 0
+  printf("%d %d\n", sizeof (RPY_ClientAccesses_Client), offsetof(CMD_Reply, data.client_accesses.clients));
+#endif
+
+  printf("Hostname                   Client    Peer CmdAuth CmdNorm  CmdBad  LstN  LstC\n"
+         "=========================  ======  ======  ======  ======  ======  ====  ====\n");
+
+  do {
+
+    i = 0;
+    p = todo;
+    while ((i < MAX_CLIENT_ACCESSES) &&
+           p) {
+
+      request.data.client_accesses.client_ips[i] = htonl(p->ip);
+      p = p->next;
+      i++;
+    }
+
+    clients_looked_up = i;
+
+    if (clients_looked_up == 0) {
+      /* No more clients to do */
+      break;
+    }
+
+    request.data.client_accesses.n_clients = htonl(clients_looked_up);
+
+    submit_ok = submit_request(&request, &reply, &auth_ok);
+    
+    if (submit_ok) {
+      status = ntohs(reply.status);
+      switch (status) {
+        case STT_SUCCESS:
+          n_replies = ntohl(reply.data.client_accesses.n_clients);
+          for (j=0; j<n_replies; j++) {
+            ip = ntohl(reply.data.client_accesses.clients[j].ip);
+            if (ip != 0UL) {
+              /* ip == 0 implies that the node could not be found in
+                 the daemon's tables; we shouldn't ever generate this
+                 case, but ignore it if we do.  (In future there might
+                 be a protocol to reset the client logging; if another
+                 administrator runs that while we're doing the clients
+                 command, there will be a race condition that could
+                 cause this). */
+              
+              client_hits = ntohl(reply.data.client_accesses.clients[j].client_hits);
+              peer_hits = ntohl(reply.data.client_accesses.clients[j].peer_hits);
+              cmd_hits_auth = ntohl(reply.data.client_accesses.clients[j].cmd_hits_auth);
+              cmd_hits_normal = ntohl(reply.data.client_accesses.clients[j].cmd_hits_normal);
+              cmd_hits_bad = ntohl(reply.data.client_accesses.clients[j].cmd_hits_bad);
+              last_ntp_hit_ago = ntohl(reply.data.client_accesses.clients[j].last_ntp_hit_ago);
+              last_cmd_hit_ago = ntohl(reply.data.client_accesses.clients[j].last_cmd_hit_ago);
+
+              if (no_dns) {
+                sprintf(hostname_buf, "%s", UTI_IPToDottedQuad(ip));
+              } else {
+                dns_lookup = DNS_IPAddress2Name(ip);
+                hostname_buf[25] = 0;
+                strncpy(hostname_buf, dns_lookup, 25);
+              }
+              printf("%-25s  %6d  %6d  %6d  %6d  %6d  ",
+                     hostname_buf,
+                     client_hits, peer_hits,
+                     cmd_hits_auth, cmd_hits_normal, cmd_hits_bad);
+              print_seconds(last_ntp_hit_ago);
+              printf("  ");
+              print_seconds(last_cmd_hit_ago);
+              printf("\n");
+            }
+          }              
+
+          /* Skip the todo pointer forwards by the number of nets looked
+             up.  Can't do this earlier, because we might have to point
+             at the next layer of subnets that have only just been
+             concatenated to the linked list. */
+          for (i=0; i<clients_looked_up; i++) {
+            todo = todo->next;
+          }
+
+          break;
+
+        case STT_BADSUBNET:
+          /* We should never generate any bad subnet messages */
+          assert(0);
+          break;
+        case STT_INACTIVE:
+          printf("519 Client logging is not active in the daemon\n");
+          goto cleanup;
+        case STT_NOHOSTACCESS:
+          printf("510 No command access from this host\n");
+          goto cleanup;
+        default:
+          printf("520 Got unexpected error from daemon\n");
+          goto cleanup;
+      }
+
+    }
+  
+  } while (1);
+  
+cleanup:
+  for (p = head; p; ) {
+    next_node = p->next;
+    Free(p);
+    p = next_node;
+  }
+  
+}
+
+#endif
+
+/* New implementation of clients command */
+
+static void
+process_cmd_clients(char *line)
+{
+  CMD_Request request;
+  CMD_Reply reply;
+  int submit_ok, auth_ok;
+  int status;
+  unsigned long next_index;
+  int j;
+  unsigned long ip;
+  unsigned long client_hits;
+  unsigned long peer_hits;
+  unsigned long cmd_hits_auth;
+  unsigned long cmd_hits_normal;
+  unsigned long cmd_hits_bad;
+  unsigned long last_ntp_hit_ago;
+  unsigned long last_cmd_hit_ago;
+  char hostname_buf[32];
+  const char *dns_lookup;
+
+  int n_replies;
+  int n_indices_in_table;
+
+  next_index = 0;
+
+  printf("Hostname                   Client    Peer CmdAuth CmdNorm  CmdBad  LstN  LstC\n"
+         "=========================  ======  ======  ======  ======  ======  ====  ====\n");
+
+  do {
+
+    request.command = htons(REQ_CLIENT_ACCESSES_BY_INDEX);
+    request.data.client_accesses_by_index.first_index = htonl(next_index);
+    request.data.client_accesses_by_index.n_indices = htonl(MAX_CLIENT_ACCESSES);
+
+    submit_ok = submit_request(&request, &reply, &auth_ok);
+  
+    if (submit_ok) {
+      status = ntohs(reply.status);
+      switch (status) {
+        case STT_SUCCESS:
+          n_replies = ntohl(reply.data.client_accesses_by_index.n_clients);
+          n_indices_in_table = ntohl(reply.data.client_accesses_by_index.n_indices);
+          if (n_replies == 0) {
+            goto finished;
+          }
+          for (j=0; j<n_replies; j++) {
+            ip = ntohl(reply.data.client_accesses_by_index.clients[j].ip);
+            if (ip != 0UL) {
+              /* ip == 0 implies that the node could not be found in
+                 the daemon's tables; we shouldn't ever generate this
+                 case, but ignore it if we do.  (In future there might
+                 be a protocol to reset the client logging; if another
+                 administrator runs that while we're doing the clients
+                 command, there will be a race condition that could
+                 cause this). */
+              
+              client_hits = ntohl(reply.data.client_accesses_by_index.clients[j].client_hits);
+              peer_hits = ntohl(reply.data.client_accesses_by_index.clients[j].peer_hits);
+              cmd_hits_auth = ntohl(reply.data.client_accesses_by_index.clients[j].cmd_hits_auth);
+              cmd_hits_normal = ntohl(reply.data.client_accesses_by_index.clients[j].cmd_hits_normal);
+              cmd_hits_bad = ntohl(reply.data.client_accesses_by_index.clients[j].cmd_hits_bad);
+              last_ntp_hit_ago = ntohl(reply.data.client_accesses_by_index.clients[j].last_ntp_hit_ago);
+              last_cmd_hit_ago = ntohl(reply.data.client_accesses_by_index.clients[j].last_cmd_hit_ago);
+
+              if (no_dns) {
+                sprintf(hostname_buf, "%s", UTI_IPToDottedQuad(ip));
+              } else {
+                dns_lookup = DNS_IPAddress2Name(ip);
+                hostname_buf[25] = 0;
+                strncpy(hostname_buf, dns_lookup, 25);
+              }
+              printf("%-25s  %6ld  %6ld  %6ld  %6ld  %6ld  ",
+                     hostname_buf,
+                     client_hits, peer_hits,
+                     cmd_hits_auth, cmd_hits_normal, cmd_hits_bad);
+              print_seconds(last_ntp_hit_ago);
+              printf("  ");
+              print_seconds(last_cmd_hit_ago);
+              printf("\n");
+            }
+          }              
+
+          /* Set the next index to probe based on what the server tells us */
+          next_index = ntohl(reply.data.client_accesses_by_index.next_index);
+          if (next_index >= n_indices_in_table) {
+            goto finished;
+          }
+
+          break;
+        case STT_BADSUBNET:
+          /* We should never generate any bad subnet messages */
+          assert(0);
+          break;
+        case STT_INACTIVE:
+          printf("519 Client logging is not active in the daemon\n");
+          goto finished;
+        case STT_NOHOSTACCESS:
+          printf("510 No command access from this host\n");
+          goto finished;
+        default:
+          printf("520 Got unexpected error from daemon\n");
+          goto finished;
+      }
+      
+    } else {
+      printf("506 Cannot talk to daemon\n");
+      return;
+    }
+  } while (1); /* keep going until all subnets have been expanded,
+                  down to single nodes */
+
+finished:
+  return;
+
+}
+
+
+/* ================================================== */
+/* Process the manual list command */
+static void
+process_cmd_manual_list(const char *line)
+{
+  CMD_Request request;
+  CMD_Reply reply;
+  int submit_ok, auth_ok;
+  int status;
+  int n_samples;
+  RPY_ManualListSample *sample;
+  int i;
+  time_t when;
+  double slewed_offset, orig_offset, residual;
+
+  request.command = htons(REQ_MANUAL_LIST);
+  submit_ok = submit_request(&request, &reply, &auth_ok);
+  
+  if (submit_ok) {
+    status = ntohs(reply.status);
+      switch (status) {
+        case STT_SUCCESS:
+          n_samples = ntohl(reply.data.manual_list.n_samples);
+          printf("210 n_samples = %d\n", n_samples);
+          printf("#    Date  Time(UTC)    Slewed   Original   Residual\n"
+                 "====================================================\n");
+          for (i=0; i<n_samples; i++) {
+            sample = &reply.data.manual_list.samples[i];
+            when = ntohl(sample->when);
+            slewed_offset = WIRE2REAL(sample->slewed_offset);
+            orig_offset = WIRE2REAL(sample->orig_offset);
+            residual = WIRE2REAL(sample->residual);
+            printf("%2d %s %10.2f %10.2f %10.2f\n", i, time_to_log_form(when), slewed_offset, orig_offset, residual);
+          }
+          break;
+        case STT_NOHOSTACCESS:
+          printf("510 No command access from this host\n");
+          break;
+        default:
+          printf("520 Got unexpected error from daemon\n");
+          break;
+      }
+  }
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_manual_delete(const char *line)
+{
+  int index;
+  CMD_Request request;
+  CMD_Reply reply;
+  int submit_ok, auth_ok;
+  int status;
+
+  if (sscanf(line, "%d", &index) != 1) {
+    fprintf(stderr, "Bad syntax for manual delete command\n");
+    return;
+
+  }
+
+  request.command = htons(REQ_MANUAL_DELETE);
+  request.data.manual_delete.index = htonl(index);
+
+  submit_ok = submit_request(&request, &reply, &auth_ok);
+  
+  if (submit_ok) {
+    status = ntohs(reply.status);
+    switch (status) {
+      case STT_SUCCESS:
+        printf("200 OK\n");
+        break;
+      case STT_BADSAMPLE:
+        printf("516 Sample index out of range\n");
+        break;
+      case STT_NOHOSTACCESS:
+        printf("510 No command access from this host\n");
+        break;
+      default:
+        printf("520 Got unexpected error from daemon\n");
+        break;
+    }
+  }
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_settime(char *line)
+{
+  time_t now, new_time;
+  CMD_Request request;
+  CMD_Reply reply;
+  int submit_ok, reply_auth_ok;
+  long offset_cs;
+  double dfreq_ppm, new_afreq_ppm;
+  double offset;
+  int status;
+
+  now = time(NULL);
+  new_time = get_date(line, &now);
+
+  if (new_time == -1) {
+    printf("510 - Could not parse date string\n");
+  } else {
+    request.data.settime.ts.tv_sec = htonl(new_time);
+    request.data.settime.ts.tv_usec = htonl(0);
+    request.command = htons(REQ_SETTIME);
+    submit_ok = submit_request(&request, &reply, &reply_auth_ok);
+    if (submit_ok) {
+      status = ntohs(reply.status);
+      switch (status) {
+        case STT_SUCCESS:
+          offset_cs = ntohl(reply.data.manual_timestamp.centiseconds);
+          offset = 0.01 * (double) offset_cs;
+          dfreq_ppm = WIRE2REAL(reply.data.manual_timestamp.dfreq_ppm);
+          new_afreq_ppm = WIRE2REAL(reply.data.manual_timestamp.new_afreq_ppm);
+          printf("200 OK : Clock was %.2f seconds fast.  Frequency change = %.2fppm, new frequency = %.2fppm",
+                 offset, dfreq_ppm, new_afreq_ppm);
+          break;
+        case STT_FAILED:
+          printf("500 Failure");
+          break;
+        case STT_UNAUTH:
+          printf("501 Not authorised");
+          break;
+        case STT_INVALID:
+          printf("502 Invalid command");
+          break;
+        case STT_NOTENABLED:
+          printf("505 Facility not enabled in daemon");
+          break;
+        case STT_NOHOSTACCESS:
+          printf("510 No command access from this host");
+          break;
+      }
+      if (reply_auth_ok) {
+        printf("\n");
+      } else {
+        printf(" --- Reply not authenticated\n");
+      }
+    } else {
+      printf("506 Could not submit settime command\n");
+    }
+  }
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_rekey(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_REKEY);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_makestep(CMD_Request *msg, char *line)
+{
+  msg->command = htons(REQ_MAKESTEP);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_activity(const char *line)
+{
+  CMD_Request request;
+  CMD_Reply reply;
+  int submit_ok, status, reply_auth_ok;
+  request.command = htons(REQ_ACTIVITY);
+  submit_ok = submit_request(&request, &reply, &reply_auth_ok);
+  if (submit_ok) {
+    status = ntohs(reply.status);
+    switch (status) {
+      case STT_SUCCESS:
+        printf("200 OK\n"
+               "%ld sources online\n"
+               "%ld sources offline\n"
+               "%ld sources doing burst (return to online)\n"
+               "%ld sources doing burst (return to offline)\n",
+                (long) ntohl(reply.data.activity.online),
+                (long) ntohl(reply.data.activity.offline),
+                (long) ntohl(reply.data.activity.burst_online),
+                (long) ntohl(reply.data.activity.burst_offline));
+        break;
+      default:
+        printf("Unexpected error returned\n");
+        break;
+    }
+    if (!reply_auth_ok) {
+      printf(" --- Reply not authenticated\n");
+    }
+  } else {
+    printf("506 Could not submit activity command\n");
+  }
+}
+
+/* ================================================== */
+
+static int
+process_line(char *line)
+{
+  char *p;
+  int quit;
+  int do_normal_submit;
+  CMD_Request tx_message;
+  CMD_Reply rx_message;
+  int reply_auth_ok, request_submitted_ok;
+
+  quit = 0;
+
+  do_normal_submit = 1;
+
+  /* Check for line being blank */
+  p = line;
+  while (*p && isspace((unsigned char)*p)) p++;
+  if (!*p) return quit;
+
+  if (!strncmp(p, "offline", 7)) {
+    do_normal_submit = process_cmd_offline(&tx_message, p+7);
+  } else if (!strncmp(p, "online", 6)) {
+    do_normal_submit = process_cmd_online(&tx_message, p+6);
+  } else if (!strncmp(p, "burst", 5)) {
+    do_normal_submit = process_cmd_burst(&tx_message, p+5);
+  } else if (!strncmp(p, "password", 8)) {
+    do_normal_submit = process_cmd_password(&tx_message, p+8);
+  } else if (!strncmp(p, "minpoll", 7)) {
+    do_normal_submit = process_cmd_minpoll(&tx_message, p+7);
+  } else if (!strncmp(p, "maxpoll", 7)) {
+    do_normal_submit = process_cmd_maxpoll(&tx_message, p+7);
+  } else if (!strncmp(p, "dump", 4)) {
+    process_cmd_dump(&tx_message, p+4);
+  } else if (!strncmp(p, "maxdelayratio", 13)) {
+    do_normal_submit = process_cmd_maxdelayratio(&tx_message, p+13);
+  } else if (!strncmp(p, "maxdelay", 8)) {
+    do_normal_submit = process_cmd_maxdelay(&tx_message, p+8);
+  } else if (!strncmp(p, "maxupdateskew", 13)) {
+    do_normal_submit = process_cmd_maxupdateskew(&tx_message, p+13);
+  } else if (!strncmp(p, "settime", 7)) {
+    do_normal_submit = 0;
+    process_cmd_settime(p+7);
+  } else if (!strncmp(p, "local", 5)) {
+    do_normal_submit = process_cmd_local(&tx_message, p+5);
+  } else if (!strncmp(p, "manual", 6)) {
+    do_normal_submit = process_cmd_manual(&tx_message, p+6);
+  } else if (!strncmp(p, "sourcestats", 11)) {
+    do_normal_submit = 0;
+    process_cmd_sourcestats(p+11);
+  } else if (!strncmp(p, "sources", 7)) {
+    do_normal_submit = 0;
+    process_cmd_sources(p+7);
+  } else if (!strncmp(p, "rekey", 5)) {
+    process_cmd_rekey(&tx_message, p+5);
+  } else if (!strncmp(p, "allow all", 9)) {
+    do_normal_submit = process_cmd_allowall(&tx_message, p+9);
+  } else if (!strncmp(p, "allow", 5)) {
+    do_normal_submit = process_cmd_allow(&tx_message, p+5);
+  } else if (!strncmp(p, "deny all", 8)) {
+    do_normal_submit = process_cmd_denyall(&tx_message, p+8);
+  } else if (!strncmp(p, "deny", 4)) {
+    do_normal_submit = process_cmd_deny(&tx_message, p+4);
+  } else if (!strncmp(p, "cmdallow all", 12)) {
+    do_normal_submit = process_cmd_cmdallowall(&tx_message, p+12);
+  } else if (!strncmp(p, "cmdallow", 8)) {
+    do_normal_submit = process_cmd_cmdallow(&tx_message, p+8);
+  } else if (!strncmp(p, "cmddeny all", 11)) {
+    do_normal_submit = process_cmd_cmddenyall(&tx_message, p+11);
+  } else if (!strncmp(p, "cmddeny", 7)) {
+    do_normal_submit = process_cmd_cmddeny(&tx_message, p+7);
+  } else if (!strncmp(p, "accheck", 7)) {
+    do_normal_submit = process_cmd_accheck(&tx_message, p+7);
+  } else if (!strncmp(p, "cmdaccheck", 10)) {
+    do_normal_submit = process_cmd_cmdaccheck(&tx_message, p+10);
+  } else if (!strncmp(p, "add server", 10)) {
+    do_normal_submit = process_cmd_add_server(&tx_message, p+10);
+  } else if (!strncmp(p, "add peer", 8)) {
+    do_normal_submit = process_cmd_add_peer(&tx_message, p+8);
+  } else if (!strncmp(p, "delete", 6)) {
+    do_normal_submit = process_cmd_delete(&tx_message, p+6);
+  } else if (!strncmp(p, "writertc", 7)) {
+    process_cmd_writertc(&tx_message, p+7);
+  } else if (!strncmp(p, "rtcdata", 7)) {
+    do_normal_submit = 0;
+    process_cmd_rtcreport(p);
+  } else if (!strncmp(p, "trimrtc", 7)) {
+    process_cmd_trimrtc(&tx_message, p);
+  } else if (!strncmp(p, "cyclelogs", 9)) {
+    process_cmd_cyclelogs(&tx_message, p);
+  } else if (!strncmp(p, "dfreq", 5)) {
+    process_cmd_dfreq(&tx_message, p+5);
+  } else if (!strncmp(p, "doffset", 7)) {
+    process_cmd_doffset(&tx_message, p+7);
+  } else if (!strncmp(p, "tracking", 8)) {
+    process_cmd_tracking(p+8);
+    do_normal_submit = 0;
+  } else if (!strncmp(p, "clients", 7)) {
+    process_cmd_clients(p+7);
+    do_normal_submit = 0;
+  } else if (!strncmp(p, "makestep", 8)) {
+    process_cmd_makestep(&tx_message, p+8);
+  } else if (!strncmp(p, "activity", 8)) {
+    process_cmd_activity(p+8);
+    do_normal_submit = 0;
+  } else if (!strncmp(p, "help", 4)) {
+    do_normal_submit = 0;
+    give_help();
+  } else if (!strncmp(p, "quit", 4)) {
+    do_normal_submit = 0;
+    quit = 1;
+  } else if (!strncmp(p, "exit", 4)) {
+    do_normal_submit = 0;
+    quit = 1;
+  } else {
+    fprintf(stderr, "Unrecognized command\n");
+    do_normal_submit = 0;
+  }
+    
+  if (do_normal_submit) {
+
+    request_submitted_ok = submit_request(&tx_message, &rx_message, &reply_auth_ok);
+
+    if (request_submitted_ok) {
+      switch(ntohs(rx_message.status)) {
+        case STT_SUCCESS:
+          printf("200 OK");
+          break;
+        case STT_FAILED:
+          printf("500 Failure");
+          break;
+        case STT_UNAUTH:
+          printf("501 Not authorised");
+          break;
+        case STT_INVALID:
+          printf("502 Invalid command");
+          break;
+        case STT_NOSUCHSOURCE:
+          printf("503 No such source");
+          break;
+        case STT_INVALIDTS:
+          printf("504 Duplicate or stale logon detected");
+          break;
+        case STT_NOTENABLED:
+          printf("505 Facility not enabled in daemon");
+          break;
+        case STT_BADSUBNET:
+          printf("507 Bad subnet");
+          break;
+        case STT_ACCESSALLOWED:
+          printf("208 Access allowed");
+          break;
+        case STT_ACCESSDENIED:
+          printf("209 Access denied");
+          break;
+        case STT_NOHOSTACCESS:
+          printf("510 No command access from this host");
+          break;
+        case STT_SOURCEALREADYKNOWN:
+          printf("511 Source already present");
+          break;
+        case STT_TOOMANYSOURCES:
+          printf("512 Too many sources present");
+          break;
+        case STT_NORTC:
+          printf("513 RTC driver not running");
+          break;
+        case STT_BADRTCFILE:
+          printf("514 Can't write RTC parameters");
+          break;
+      }
+      
+      if (reply_auth_ok) {
+        printf("\n");
+      } else {
+        printf(" --- Reply not authenticated\n");
+      }
+    }
+  }
+
+  return quit;
+}
+
+/* ================================================== */
+
+static void
+process_args(int argc, char **argv)
+{
+  int total_length, i;
+  char *line;
+
+  total_length = 0;
+  for(i=0; i<argc; i++) {
+    total_length += strlen(argv[i]);
+  }
+
+  line = (char *) malloc((2 + total_length) * sizeof(char));
+  line[0] = 0;
+  for (i=0; i<argc; i++) {
+    strcat(line, argv[i]);
+  }
+  strcat(line, "\n");
+
+  process_line(line);
+
+  free(line);
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+display_gpl(void)
+{
+    printf("chrony version %s, copyright (C) 1997-2002 Richard P. Curnow\n"
+           "chrony comes with ABSOLUTELY NO WARRANTY.\n"
+           "This is free software, and you are welcome to redistribute it\n"
+           "under certain conditions.\n"
+           "See the GNU General Public License version 2 for details.\n\n",
+           PROGRAM_VERSION_STRING);
+}
+
+/* ================================================== */
+
+int
+main(int argc, char **argv)
+{
+  char *line;
+  const char *progname = argv[0];
+  const char *hostname = "localhost";
+  int quit = 0;
+  int port = DEFAULT_CANDM_PORT;
+
+  /* Parse command line options */
+  while (++argv, --argc) {
+    if (!strcmp(*argv, "-h")) {
+      ++argv, --argc;
+      if (*argv) {
+        hostname = *argv;
+      }
+    } else if (!strcmp(*argv, "-p")) {
+      ++argv, --argc;
+      if (*argv) {
+        port = atoi(*argv);
+      }
+    } else if (!strcmp(*argv, "-n")) {
+      no_dns = 1;
+    } else if (!strcmp("-v", *argv) || !strcmp("--version",*argv)) {
+      printf("chronyc (chrony) version %s\n", PROGRAM_VERSION_STRING);
+      exit(0);
+    } else if (!strncmp(*argv, "-", 1)) {
+      fprintf(stderr, "Usage : %s [-h <hostname>] [-p <port-number>] [-n] [command]\n", progname);
+      exit(1);
+    } else {
+      break; /* And process remainder of line as a command */
+    }
+  }
+
+  if (isatty(0) && isatty(1) && isatty(2)) {
+    on_terminal = 1;
+  }
+
+  if (on_terminal && (argc == 0)) {
+    display_gpl();
+  }
+  
+  open_io(hostname, port);
+
+  if (argc > 0) {
+    process_args(argc, argv);
+  } else {
+    do {
+      line = read_line();
+      if (line) {
+        quit = process_line(line);
+      }else {
+       /* supply the final '\n' when user exits via ^D */
+        if( on_terminal ) printf("\n");
+      }
+    } while (line && !quit);
+  }
+
+  close_io();
+
+  return 0;
+}
+
+
diff --git a/clientlog.c b/clientlog.c
new file mode 100644 (file)
index 0000000..fec7f37
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+  $Header: /cvs/src/chrony/clientlog.c,v 1.10 2003/01/20 22:24:20 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This module keeps a count of the number of successful accesses by
+  clients, and the times of the last accesses.
+
+  This can be used for status reporting, and (in the case of a
+  server), if it needs to know which clients have made use of its data
+  recently.
+
+  */
+
+#include "sysincl.h"
+#include "clientlog.h"
+#include "conf.h"
+#include "memory.h"
+#include "reports.h"
+#include "util.h"
+
+/* Number of bits of address per layer of the table.  This value has
+   been chosen on the basis that a server will predominantly be serving
+   a lot of hosts in a few subnets, rather than a few hosts scattered
+   across many subnets. */
+
+#define NBITS 8
+
+/* Number of entries in each subtable */
+#define TABLE_SIZE (1UL<<NBITS)
+
+typedef struct _Node {
+  unsigned long ip_addr;
+  unsigned long client_hits;
+  unsigned long peer_hits;
+  unsigned long cmd_hits_bad;
+  unsigned long cmd_hits_normal;
+  unsigned long cmd_hits_auth;
+  time_t last_ntp_hit;
+  time_t last_cmd_hit;
+} Node;
+
+typedef struct _Subnet {
+  void *entry[TABLE_SIZE];
+} Subnet;
+
+/* ================================================== */
+
+/* Table for the class A subnet */
+static Subnet top_subnet;
+
+/* Table containing pointers directly to all nodes that have been
+   allocated. */
+static Node **nodes = NULL;
+
+/* Number of nodes actually in the table. */
+static int n_nodes = 0;
+
+/* Number of entries for which the table has been sized. */
+static int max_nodes = 0;
+
+#define NODE_TABLE_INCREMENT 4
+
+/* Flag indicating whether facility is turned on or not */
+static int active = 0;
+
+/* ================================================== */
+
+static void
+clear_subnet(Subnet *subnet)
+{
+  int i;
+
+  for (i=0; i<TABLE_SIZE; i++) {
+    subnet->entry[i] = NULL;
+  }
+}
+
+/* ================================================== */
+
+static void
+clear_node(Node *node)
+{
+  node->client_hits = 0;
+  node->peer_hits = 0;
+  node->cmd_hits_auth = 0;
+  node->cmd_hits_normal = 0;
+  node->cmd_hits_bad = 0;
+  node->last_ntp_hit = (time_t) 0;
+  node->last_cmd_hit = (time_t) 0;
+}
+
+/* ================================================== */
+
+void
+CLG_Initialise(void)
+{
+  clear_subnet(&top_subnet);
+  if (CNF_GetNoClientLog()) {
+    active = 0;
+  } else {
+    active = 1;
+  }
+
+  nodes = NULL;
+  max_nodes = 0;
+  n_nodes = 0;
+
+}
+
+/* ================================================== */
+
+void
+CLG_Finalise(void)
+{
+  return;
+}
+
+/* ================================================== */
+
+static void
+create_subnet(Subnet *parent_subnet, int the_entry)
+{
+  parent_subnet->entry[the_entry] = (void *) MallocNew(Subnet);
+  clear_subnet((Subnet *) parent_subnet->entry[the_entry]);
+}
+
+/* ================================================== */
+
+static void
+create_node(Subnet *parent_subnet, int the_entry)
+{
+  Node *new_node;
+  new_node = MallocNew(Node);
+  parent_subnet->entry[the_entry] = (void *) new_node;
+  clear_node(new_node);
+
+  if (n_nodes == max_nodes) {
+    if (nodes) {
+      max_nodes += NODE_TABLE_INCREMENT;
+      nodes = ReallocArray(Node *, max_nodes, nodes);
+    } else {
+      if (max_nodes != 0) {
+        CROAK("max_nodes should be 0");
+      }
+      max_nodes = NODE_TABLE_INCREMENT;
+      nodes = MallocArray(Node *, max_nodes);
+    }
+  }
+  nodes[n_nodes++] = (Node *) new_node;
+}
+
+/* ================================================== */
+/* Recursively seek out the Node entry for a particular address,
+   expanding subnet tables and node entries as we go if necessary. */
+
+static void *
+find_subnet(Subnet *subnet, CLG_IP_Addr addr, int bits_left)
+{
+  unsigned long this_subnet, new_subnet, mask, shift;
+  unsigned long new_bits_left;
+  
+  shift = 32 - NBITS;
+  mask = (1UL<<shift) - 1;
+  this_subnet = addr >> shift;
+  new_subnet = (addr & mask) << NBITS;
+  new_bits_left = bits_left - NBITS;
+
+#if 0
+  fprintf(stderr, "fs addr=%08lx bl=%d ma=%08lx this=%08lx newsn=%08lx nbl=%d\n",
+          addr, bits_left, mask, this_subnet, new_subnet, new_bits_left);
+#endif
+
+  if (new_bits_left > 0) {
+    if (!subnet->entry[this_subnet]) {
+      create_subnet(subnet, this_subnet);
+    }
+    return find_subnet((Subnet *) subnet->entry[this_subnet], new_subnet, new_bits_left);
+  } else {
+    if (!subnet->entry[this_subnet]) {
+      create_node(subnet, this_subnet);
+    }
+    return subnet->entry[this_subnet];
+  }
+}
+
+
+/* ================================================== */
+/* Search for the record for a particular subnet, but return NULL if
+   one of the parents does not exist - never open a node out */
+
+static void *
+find_subnet_dont_open(Subnet *subnet, CLG_IP_Addr addr, int bits_left)
+{
+  unsigned long this_subnet, new_subnet, mask, shift;
+  unsigned long new_bits_left;
+
+  if (bits_left == 0) {
+    return subnet;
+  } else {
+    
+    shift = 32 - NBITS;
+    mask = (1UL<<shift) - 1;
+    this_subnet = addr >> shift;
+    new_subnet = (addr & mask) << NBITS;
+    new_bits_left = bits_left - NBITS;
+
+#if 0
+    fprintf(stderr, "fsdo addr=%08lx bl=%d this=%08lx newsn=%08lx nbl=%d\n",
+            addr, bits_left, this_subnet, new_subnet, new_bits_left);
+#endif
+    
+    if (!subnet->entry[this_subnet]) {
+      return NULL;
+    } else {
+      return find_subnet_dont_open((Subnet *) subnet->entry[this_subnet], new_subnet, new_bits_left);
+    }
+  }
+}
+
+/* ================================================== */
+
+void
+CLG_LogNTPClientAccess (CLG_IP_Addr client, time_t now)
+{
+  Node *node;
+  if (active) {
+    node = (Node *) find_subnet(&top_subnet, client, 32);
+    node->ip_addr = client;
+    ++node->client_hits;
+    node->last_ntp_hit = now;
+  }
+}
+
+/* ================================================== */
+
+void
+CLG_LogNTPPeerAccess(CLG_IP_Addr client, time_t now)
+{
+  Node *node;
+  if (active) {
+    node = (Node *) find_subnet(&top_subnet, client, 32);
+    node->ip_addr = client;
+    ++node->peer_hits;
+    node->last_ntp_hit = now;
+  }
+}
+
+/* ================================================== */
+
+void
+CLG_LogCommandAccess(CLG_IP_Addr client, CLG_Command_Type type, time_t now)
+{
+  Node *node;
+  if (active) {
+    node = (Node *) find_subnet(&top_subnet, client, 32);
+    node->ip_addr = client;
+    node->last_cmd_hit = now;
+    switch (type) {
+      case CLG_CMD_AUTH:
+        ++node->cmd_hits_auth;
+        break;
+      case CLG_CMD_NORMAL:
+        ++node->cmd_hits_normal;
+        break;
+      case CLG_CMD_BAD_PKT:
+        ++node->cmd_hits_bad;
+        break;
+      default:
+        CROAK("Impossible");
+        break;
+    }
+  }
+}
+
+/* ================================================== */
+
+CLG_Status
+CLG_GetSubnetBitmap(CLG_IP_Addr subnet, int bits, CLG_Bitmap result)
+{
+  Subnet *s;
+  unsigned long i;
+  unsigned long word, bit, mask;
+
+  if ((bits == 0) || (bits == 8) || (bits == 16) || (bits == 24)) {
+    memset (result, 0, TABLE_SIZE/8);
+    if (active) {
+      s = find_subnet_dont_open(&top_subnet, subnet, bits);
+      if (s) {
+        for (i=0; i<256; i++) {
+          if (s->entry[i]) {
+            word = i / 32;
+            bit =  i % 32;
+            mask = 1UL << bit;
+            result[word] |= mask;
+          }
+        }
+        return CLG_SUCCESS;
+      } else {
+        return CLG_EMPTYSUBNET;
+      }
+    } else {
+      return CLG_INACTIVE;
+    }
+  } else {
+    return CLG_BADSUBNET;
+  }
+}
+
+/* ================================================== */
+
+CLG_Status
+CLG_GetClientAccessReportByIP(unsigned long ip, RPT_ClientAccess_Report *report, time_t now)
+{
+  Node *node;
+
+  if (!active) {
+    return CLG_INACTIVE;
+  } else {
+    node = (Node *) find_subnet_dont_open(&top_subnet, ip, 32);
+  
+    if (!node) {
+      return CLG_EMPTYSUBNET;
+    } else {
+      report->client_hits = node->client_hits;
+      report->peer_hits = node->peer_hits;
+      report->cmd_hits_auth = node->cmd_hits_auth;
+      report->cmd_hits_normal = node->cmd_hits_normal;
+      report->cmd_hits_bad = node->cmd_hits_bad;
+      report->last_ntp_hit_ago = now - node->last_ntp_hit;
+      report->last_cmd_hit_ago = now - node->last_cmd_hit;
+
+      return CLG_SUCCESS;
+    }
+  }
+}
+
+/* ================================================== */
+
+CLG_Status
+CLG_GetClientAccessReportByIndex(int index, RPT_ClientAccessByIndex_Report *report,
+                                 time_t now, unsigned long *n_indices)
+{
+  Node *node;
+
+  *n_indices = n_nodes;
+
+  if (!active) {
+    return CLG_INACTIVE;
+  } else {
+
+    if ((index < 0) || (index >= n_nodes)) {
+      return CLG_INDEXTOOLARGE;
+    }
+    
+    node = nodes[index];
+    
+    report->ip_addr = node->ip_addr;
+    report->client_hits = node->client_hits;
+    report->peer_hits = node->peer_hits;
+    report->cmd_hits_auth = node->cmd_hits_auth;
+    report->cmd_hits_normal = node->cmd_hits_normal;
+    report->cmd_hits_bad = node->cmd_hits_bad;
+    report->last_ntp_hit_ago = now - node->last_ntp_hit;
+    report->last_cmd_hit_ago = now - node->last_cmd_hit;
+    
+    return CLG_SUCCESS;
+  }
+
+}
diff --git a/clientlog.h b/clientlog.h
new file mode 100644 (file)
index 0000000..7242745
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+  $Header: /cvs/src/chrony/clientlog.h,v 1.8 2003/04/01 20:54:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This module contains facilities for logging access by clients.
+
+  */
+
+#ifndef GOT_CLIENTLOG_H
+#define GOT_CLIENTLOG_H
+
+#include "sysincl.h"
+#include "reports.h"
+
+typedef unsigned long CLG_IP_Addr;
+
+/* Enough to hold flags for 256 hosts in a class C */
+typedef uint32_t CLG_Bitmap[8];
+
+extern void CLG_Initialise(void);
+extern void CLG_Finalise(void);
+extern void CLG_LogNTPClientAccess(CLG_IP_Addr client, time_t now);
+extern void CLG_LogNTPPeerAccess(CLG_IP_Addr client, time_t now);
+
+/* When logging command packets, there are several subtypes */
+
+typedef enum {
+  CLG_CMD_AUTH,                 /* authenticated */
+  CLG_CMD_NORMAL,               /* normal */
+  CLG_CMD_BAD_PKT               /* bad version or packet length */
+} CLG_Command_Type;
+
+extern void CLG_LogCommandAccess(CLG_IP_Addr client, CLG_Command_Type type, time_t now);
+
+/* And some reporting functions, for use by chronyc. */
+/* TBD */
+
+typedef enum {
+  CLG_SUCCESS,                  /* All is well */
+  CLG_EMPTYSUBNET,              /* No hosts logged in requested subnet */
+  CLG_BADSUBNET,                /* Subnet requested is not 0, 8, 16 or 24 bits */
+  CLG_INACTIVE,                 /* Facility not active */
+  CLG_INDEXTOOLARGE             /* Node index is higher than number of nodes present */
+} CLG_Status;
+
+/* For bits=0, 8, 16, flag which immediate subnets of that subnet are
+   known.  For bits=24, flag which hosts in that subnet are known.
+   Other values, return 0 (failed) */
+
+extern CLG_Status CLG_GetSubnetBitmap(CLG_IP_Addr subnet, int bits, CLG_Bitmap result);
+
+extern CLG_Status
+CLG_GetClientAccessReportByIP(unsigned long ip, RPT_ClientAccess_Report *report, time_t now);
+
+CLG_Status
+CLG_GetClientAccessReportByIndex(int index, RPT_ClientAccessByIndex_Report *report,
+                                 time_t now, unsigned long *n_indices);
+
+/* And an iterating function, to call 'fn' for each client or peer
+   that has accessed us since 'since'. */
+
+extern void CLG_IterateNTPClients
+(void (*fn)(CLG_IP_Addr client, void *arb),
+ void *arb,
+ time_t since);
+
+
+#endif /* GOT_CLIENTLOG_H */
diff --git a/cmdmon.c b/cmdmon.c
new file mode 100644 (file)
index 0000000..5f99d99
--- /dev/null
+++ b/cmdmon.c
@@ -0,0 +1,2119 @@
+/*
+  $Header: /cvs/src/chrony/cmdmon.c,v 1.54 2003/04/01 20:54:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Command and monitoring module in the main program
+  */
+
+#include "sysincl.h"
+
+#include "cmdmon.h"
+#include "candm.h"
+#include "sched.h"
+#include "util.h"
+#include "logging.h"
+#include "md5.h"
+#include "keys.h"
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "sources.h"
+#include "sourcestats.h"
+#include "reference.h"
+#include "manual.h"
+#include "memory.h"
+#include "local.h"
+#include "addrfilt.h"
+#include "conf.h"
+#include "rtc.h"
+#include "pktlength.h"
+#include "clientlog.h"
+
+/* ================================================== */
+
+/* File descriptor for command and monitoring socket */
+static int sock_fd;
+
+/* Flag indicating whether this module has been initialised or not */
+static int initialised = 0;
+
+/* Token which is unique every time the daemon is run */
+static unsigned long utoken;
+
+/* The register of issued tokens */
+static unsigned long issued_tokens;
+
+/* The register of received tokens */
+static unsigned long returned_tokens;
+
+/* The token number corresponding to the base of the registers */
+static unsigned long token_base;
+
+/* The position of the next free token to issue in the issue register */
+static unsigned long issue_pointer;
+
+/* Type and linked list for buffering responses */
+typedef struct _ResponseCell {
+  struct _ResponseCell *next;
+  unsigned long tok; /* The token that the client sent in the message
+                        to which this was the reply */
+  unsigned long next_tok; /* The next token issued to the same client.
+                             If we receive a request with this token,
+                             it implies the reply stored in this cell
+                             was successfully received */
+  unsigned long msg_seq; /* Client's sequence number used in request
+                            to which this is the response. */
+  unsigned long attempt; /* Attempt number that we saw in the last request
+                            with this sequence number (prevents attacker
+                            firing the same request at us to make us
+                            keep generating the same reply). */
+  struct timeval ts; /* Time we saved the reply - allows purging based
+                        on staleness. */
+  CMD_Reply rpy;
+} ResponseCell;
+
+static ResponseCell kept_replies;
+static ResponseCell *free_replies;
+
+/* ================================================== */
+/* Array of permission levels for command types */
+
+static int permissions[] = {
+  PERMIT_OPEN, /* NULL */
+  PERMIT_AUTH, /* ONLINE */
+  PERMIT_AUTH, /* OFFLINE */
+  PERMIT_AUTH, /* BURST */
+  PERMIT_AUTH, /* MODIFY_MINPOLL */
+  PERMIT_AUTH, /* MODIFY_MAXPOLL */
+  PERMIT_AUTH, /* DUMP */
+  PERMIT_AUTH, /* MODIFY_MAXDELAY */
+  PERMIT_AUTH, /* MODIFY_MAXDELAYRATIO */
+  PERMIT_AUTH, /* MODIFY_MAXUPDATESKEW */
+  PERMIT_OPEN, /* LOGON */
+  PERMIT_AUTH, /* SETTIME */
+  PERMIT_AUTH, /* LOCAL */
+  PERMIT_AUTH, /* MANUAL */
+  PERMIT_OPEN, /* N_SOURCES */
+  PERMIT_OPEN, /* SOURCE_DATA */
+  PERMIT_AUTH, /* REKEY */
+  PERMIT_AUTH, /* ALLOW */
+  PERMIT_AUTH, /* ALLOWALL */
+  PERMIT_AUTH, /* DENY */
+  PERMIT_AUTH, /* DENYALL */
+  PERMIT_AUTH, /* CMDALLOW */
+  PERMIT_AUTH, /* CMDALLOWALL */
+  PERMIT_AUTH, /* CMDDENY */
+  PERMIT_AUTH, /* CMDDENYALL */
+  PERMIT_AUTH, /* ACCHECK */
+  PERMIT_AUTH, /* CMDACCHECK */
+  PERMIT_AUTH, /* ADD_SERVER */
+  PERMIT_AUTH, /* ADD_PEER */
+  PERMIT_AUTH, /* DEL_SOURCE */
+  PERMIT_AUTH, /* WRITERTC */
+  PERMIT_AUTH, /* DFREQ */
+  PERMIT_AUTH, /* DOFFSET */
+  PERMIT_OPEN, /* TRACKING */
+  PERMIT_OPEN, /* SOURCESTATS */
+  PERMIT_OPEN, /* RTCREPORT */
+  PERMIT_AUTH, /* TRIMRTC */
+  PERMIT_AUTH, /* CYCLELOGS */
+  PERMIT_OPEN, /* SUBNETS_ACCESSED */
+  PERMIT_OPEN, /* CLIENT_ACCESSES (by subnet) */
+  PERMIT_OPEN, /* CLIENT_ACCESSES_BY_INDEX */
+  PERMIT_OPEN, /* MANUAL_LIST */
+  PERMIT_AUTH, /* MANUAL_DELETE */
+  PERMIT_AUTH, /* MAKESTEP */
+  PERMIT_OPEN  /* ACTIVITY */
+};
+
+/* ================================================== */
+
+/* This authorisation table is used for checking whether particular
+   machines are allowed to make command and monitoring requests. */
+static ADF_AuthTable access_auth_table;
+
+/* ================================================== */
+/* Forward prototypes */
+static void read_from_cmd_socket(void *anything);
+
+/* ================================================== */
+
+void
+CAM_Initialise(void)
+{
+  int port_number;
+  struct sockaddr_in my_addr;
+  unsigned long bind_address;
+  int on_off;
+
+  if (initialised) {
+    CROAK("Shouldn't be initialised");
+  }
+
+  initialised = 1;
+
+  if ((sizeof(permissions)/sizeof(permissions[0])) != N_REQUEST_TYPES) {
+    CROAK("Permissions table size wrong");
+  }
+
+  utoken = (unsigned long) time(NULL);
+
+  issued_tokens = returned_tokens = issue_pointer = 0;
+  token_base = 1; /* zero is the value used when the previous command was
+                     unauthenticated */
+
+  free_replies = NULL;
+  kept_replies.next = NULL;
+
+  port_number = CNF_GetCommandPort();
+  if (port_number < 0) {
+    port_number = DEFAULT_CANDM_PORT;
+  }
+
+  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+  if (sock_fd < 0) {
+    LOG_FATAL(LOGF_CmdMon, "Could not open socket : %s", strerror(errno));
+  }
+
+  /* Allow reuse of port number */
+  if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on_off, sizeof(on_off)) < 0) {
+    LOG(LOGS_ERR, LOGF_CmdMon, "Could not set socket options");
+    /* Don't quit - we might survive anyway */
+  }
+
+  my_addr.sin_family = AF_INET;
+  my_addr.sin_port = htons((unsigned short) port_number);
+
+  CNF_GetBindCommandAddress(&bind_address);
+
+  if (bind_address != 0UL) {
+    my_addr.sin_addr.s_addr = htonl(bind_address);
+  } else {
+    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+  }
+
+  if (bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
+    LOG_FATAL(LOGF_CmdMon, "Could not bind socket : %s", strerror(errno));
+  }
+
+  /* Register handler for read events on the socket */
+  SCH_AddInputFileHandler(sock_fd, read_from_cmd_socket, NULL);
+
+  access_auth_table = ADF_CreateTable();
+
+}
+
+/* ================================================== */
+
+void
+CAM_Finalise(void)
+{
+  SCH_RemoveInputFileHandler(sock_fd);
+  close(sock_fd);
+  sock_fd = -1;
+
+  ADF_DestroyTable(access_auth_table);
+
+  initialised = 0;
+  return;
+}
+
+/* ================================================== */
+/* This function checks whether the authenticator field of the packet
+   checks correctly against what we would compute locally given the
+   rest of the packet */
+
+static int
+check_rx_packet_auth(CMD_Request *packet)
+{
+
+  char *key;
+  int keylen;
+  int pkt_len;
+  MD5_CTX ctx;
+
+  pkt_len = PKL_CommandLength(packet);
+
+  KEY_CommandKey(&key, &keylen);
+
+  MD5Init(&ctx);
+  MD5Update(&ctx, (unsigned char *) key, keylen);
+  MD5Update(&ctx, (unsigned char *) packet, offsetof(CMD_Request, auth));
+  if (pkt_len > offsetof(CMD_Request, data)) {
+    MD5Update(&ctx, (unsigned char *) &(packet->data), pkt_len - offsetof(CMD_Request, data));
+  }
+  MD5Final(&ctx);
+
+  if (!memcmp((void *) &ctx.digest, (void *) &(packet->auth), 16)) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static void
+generate_tx_packet_auth(CMD_Reply *packet)
+{
+  char *key;
+  int keylen;
+  MD5_CTX ctx;
+  int pkt_len;
+
+  pkt_len = PKL_ReplyLength(packet);
+
+  KEY_CommandKey(&key, &keylen);
+
+  MD5Init(&ctx);
+  MD5Update(&ctx, (unsigned char *) key, keylen);
+  MD5Update(&ctx, (unsigned char *) packet, offsetof(CMD_Request, auth));
+  if (pkt_len > offsetof(CMD_Reply, data)) {
+    MD5Update(&ctx, (unsigned char *) &(packet->data), pkt_len - offsetof(CMD_Reply, data));
+  }
+  MD5Final(&ctx);
+
+  memcpy(&(packet->auth), &ctx.digest, 16);
+
+}
+
+/* ================================================== */
+
+static void
+shift_tokens(void)
+{
+  do {
+    issued_tokens >>= 1;
+    returned_tokens >>= 1;
+    token_base++;
+    issue_pointer--;
+  } while ((issued_tokens & 1) && (returned_tokens & 1));
+}
+
+/* ================================================== */
+
+static unsigned long
+get_token(void)
+{
+  unsigned long result;
+
+  if (issue_pointer == 32) {
+    /* The lowest number open token has not been returned - bad luck
+       to that command client */
+    shift_tokens();
+  }
+
+  result = token_base + issue_pointer;
+  issued_tokens |= (1UL << issue_pointer);
+  issue_pointer++;
+
+  return result;
+}
+
+/* ================================================== */
+
+static int
+check_token(unsigned long token)
+{
+  int result;
+  unsigned long pos;
+
+  if (token < token_base) {
+    /* Token too old */
+    result = 0;
+  } else {
+    pos = token - token_base;
+    if (pos >= issue_pointer) {
+      /* Token hasn't been issued yet */
+      result = 0;
+    } else {
+      if (returned_tokens & (1UL << pos)) {
+        /* Token has already been returned */
+        result = 0;
+      } else {
+        /* Token is OK */
+        result = 1;
+        returned_tokens |= (1UL << pos);
+        if (pos == 0) {
+          shift_tokens();
+        }
+      }
+    }
+  }
+
+  return result;
+
+}
+
+/* ================================================== */
+
+#define TS_MARGIN 20
+
+/* ================================================== */
+
+typedef struct _TimestampCell {
+  struct _TimestampCell *next;
+  struct timeval ts;
+} TimestampCell;
+
+static struct _TimestampCell seen_ts_list={NULL};
+static struct _TimestampCell *free_ts_list=NULL;
+
+#define EXTEND_QUANTUM 32
+
+/* ================================================== */
+
+static TimestampCell *
+allocate_ts_cell(void)
+{
+  TimestampCell *result;
+  int i;
+  if (free_ts_list == NULL) {
+    free_ts_list = MallocArray(TimestampCell, EXTEND_QUANTUM);
+    for (i=0; i<EXTEND_QUANTUM-1; i++) {
+      free_ts_list[i].next = free_ts_list + i + 1;
+    }
+    free_ts_list[EXTEND_QUANTUM - 1].next = NULL;
+  }
+
+  result = free_ts_list;
+  free_ts_list = free_ts_list->next;
+  return result;
+}
+
+/* ================================================== */
+
+static void
+release_ts_cell(TimestampCell *node)
+{
+  node->next = free_ts_list;
+  free_ts_list = node;
+}
+
+/* ================================================== */
+/* Return 1 if not found, 0 if found (i.e. not unique).  Prune out any
+   stale entries. */
+
+static int
+check_unique_ts(struct timeval *ts, struct timeval *now)
+{
+  TimestampCell *last_valid, *cell, *next;
+  int ok;
+
+  ok = 1;
+  last_valid = &(seen_ts_list);
+  cell = last_valid->next;
+
+  while (cell) {
+    next = cell->next;
+    /* Check if stale */
+    if ((now->tv_sec - cell->ts.tv_sec) > TS_MARGIN) {
+      release_ts_cell(cell);
+      last_valid->next = next;
+    } else {
+      /* Timestamp in cell is still within window */
+      last_valid->next = cell;
+      last_valid = cell;
+      if ((cell->ts.tv_sec == ts->tv_sec) && (cell->ts.tv_usec == ts->tv_usec)) {
+        ok = 0;
+      }
+    }
+    cell = next;
+  }
+
+  if (ok) {
+    /* Need to add this timestamp to the list */
+    cell = allocate_ts_cell();
+    last_valid->next = cell;
+    cell->next = NULL;
+    cell->ts = *ts;
+  }
+
+  return ok;
+}
+
+/* ================================================== */
+
+static int
+ts_is_unique_and_not_stale(struct timeval *ts, struct timeval *now)
+{
+  long tv_sec;
+  struct timeval host_order_ts;
+  int within_margin=0;
+  int is_unique=0;
+  long diff;
+
+  host_order_ts.tv_sec = tv_sec = ntohl(ts->tv_sec);
+  host_order_ts.tv_usec = ntohl(ts->tv_usec);
+
+  diff = now->tv_sec - tv_sec;
+  if ((diff < TS_MARGIN) && (diff > -TS_MARGIN)) {
+    within_margin = 1;
+  } else {
+    within_margin = 0;
+  }
+  is_unique = check_unique_ts(&host_order_ts, now);
+    
+  return within_margin && is_unique;
+}
+
+/* ================================================== */
+
+#define REPLY_EXTEND_QUANTUM 32
+
+static void
+get_more_replies(void)
+{
+  ResponseCell *new_replies;
+  int i;
+
+  if (!free_replies) {
+    new_replies = MallocArray(ResponseCell, REPLY_EXTEND_QUANTUM);
+    for (i=1; i<REPLY_EXTEND_QUANTUM; i++) {
+      new_replies[i-1].next = new_replies + i;
+    }
+    free_replies = new_replies;
+  }
+}
+
+/* ================================================== */
+
+static ResponseCell *
+get_reply_slot(void)
+{
+  ResponseCell *result;
+  if (!free_replies) {
+    get_more_replies();
+  }
+  result = free_replies;
+  free_replies = result->next;
+  return result;
+}
+
+/* ================================================== */
+
+static void
+free_reply_slot(ResponseCell *cell)
+{
+  cell->next = free_replies;
+  free_replies = cell;
+}
+
+/* ================================================== */
+
+static void
+save_reply(CMD_Reply *msg,
+           unsigned long tok_reply_to,
+           unsigned long new_tok_issued,
+           unsigned long client_msg_seq,
+           unsigned short attempt,
+           struct timeval *now)
+{
+  ResponseCell *cell;
+
+  cell = get_reply_slot();
+
+  cell->ts = *now;
+  memcpy(&cell->rpy, msg, sizeof(CMD_Reply));
+  cell->tok = tok_reply_to;
+  cell->next_tok = new_tok_issued;
+  cell->msg_seq = client_msg_seq;
+  cell->attempt = (unsigned long) attempt;
+
+  cell->next = kept_replies.next;
+  kept_replies.next = cell;
+
+}
+
+/* ================================================== */
+
+static CMD_Reply *
+lookup_reply(unsigned long prev_msg_token, unsigned long client_msg_seq, unsigned short attempt)
+{
+  ResponseCell *ptr;
+
+  ptr = kept_replies.next;
+  while (ptr) {
+    if ((ptr->tok == prev_msg_token) &&
+        (ptr->msg_seq == client_msg_seq) &&
+        ((unsigned long) attempt > ptr->attempt)) {
+
+      /* Set the attempt field to remember the highest number we have
+         had so far */
+      ptr->attempt = (unsigned long) attempt;
+      return &ptr->rpy;
+    }
+    ptr = ptr->next;
+  }
+
+  return NULL;
+}
+
+
+/* ================================================== */
+
+#define REPLY_MAXAGE 300
+
+static void
+token_acknowledged(unsigned long token, struct timeval *now)
+{
+  ResponseCell *last_valid, *cell, *next;
+
+  last_valid = &kept_replies;
+  cell = kept_replies.next;
+  
+  while(cell) {
+    next = cell->next;
+
+    /* Discard if it's the one or if the reply is stale */
+    if ((cell->next_tok == token) ||
+        ((now->tv_sec - cell->ts.tv_sec) > REPLY_MAXAGE)) {
+      free_reply_slot(cell);
+      last_valid->next = next;
+    } else {
+      last_valid->next = cell;
+      last_valid = cell;
+    }
+    cell = next;
+  }
+}
+
+/* ================================================== */
+
+#if 0
+/* These two routines are not legal if the program is operating as a daemon, since
+   stderr is no longer open */
+
+static void
+print_command_packet(CMD_Request *pkt, int length)
+{
+  unsigned char *x;
+  int i;
+  x = (unsigned char *) pkt;
+  for (i=0; i<length; i++) {
+    fprintf(stderr, "%02x ", x[i]);
+    if (i%16 == 15) {
+      fprintf(stderr, "\n");
+    }
+  }
+  fprintf(stderr, "\n");
+}
+
+/* ================================================== */
+
+static void
+print_reply_packet(CMD_Reply *pkt)
+{
+  unsigned char *x;
+  int i;
+  x = (unsigned char *) pkt;
+  for (i=0; i<sizeof(CMD_Reply); i++) {
+    fprintf(stderr, "%02x ", x[i]);
+    if (i%16 == 15) {
+      fprintf(stderr, "\n");
+    }
+  }
+  fprintf(stderr, "\n");
+}
+
+#endif 
+
+/* ================================================== */
+
+static void
+transmit_reply(CMD_Reply *msg, struct sockaddr_in *where_to)
+{
+  int status;
+  int tx_message_length;
+  unsigned long remote_ip;
+  unsigned short remote_port;
+
+  tx_message_length = PKL_ReplyLength(msg);
+  status = sendto(sock_fd, (void *) msg, tx_message_length, 0,
+                  (struct sockaddr *) where_to, sizeof(struct sockaddr_in));
+
+  if (status < 0) {
+    remote_ip = ntohl(where_to->sin_addr.s_addr);
+    remote_port = ntohs(where_to->sin_port);
+    LOG(LOGS_WARN, LOGF_CmdMon, "Could not send response to %s:%hu", UTI_IPToDottedQuad(remote_ip), remote_port);
+  }
+
+  return;
+}
+  
+
+/* ================================================== */
+
+static void
+handle_null(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  tx_message->status = htons(STT_SUCCESS);
+}
+
+/* ================================================== */
+
+static void
+handle_online(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = NSR_TakeSourcesOnline(ntohl(rx_message->data.online.mask), ntohl(rx_message->data.online.address));
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_offline(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = NSR_TakeSourcesOffline(ntohl(rx_message->data.offline.mask), ntohl(rx_message->data.offline.address));
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_burst(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = NSR_InitiateSampleBurst(ntohl(rx_message->data.burst.n_good_samples),
+                                   ntohl(rx_message->data.burst.n_total_samples),
+                                   ntohl(rx_message->data.burst.mask),
+                                   ntohl(rx_message->data.burst.address));
+  
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_modify_minpoll(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = NSR_ModifyMinpoll(ntohl(rx_message->data.modify_minpoll.address),
+                             ntohl(rx_message->data.modify_minpoll.new_minpoll));
+  
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxpoll(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = NSR_ModifyMaxpoll(ntohl(rx_message->data.modify_minpoll.address),
+                             ntohl(rx_message->data.modify_minpoll.new_minpoll));
+  
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxdelay(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = NSR_ModifyMaxdelay(ntohl(rx_message->data.modify_maxdelay.address),
+                              WIRE2REAL(rx_message->data.modify_maxdelay.new_max_delay));
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxdelayratio(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = NSR_ModifyMaxdelayratio(ntohl(rx_message->data.modify_maxdelayratio.address),
+                                   WIRE2REAL(rx_message->data.modify_maxdelayratio.new_max_delay_ratio));
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxupdateskew(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  REF_ModifyMaxupdateskew(WIRE2REAL(rx_message->data.modify_maxupdateskew.new_max_update_skew));
+  tx_message->status = htons(STT_SUCCESS);
+}
+
+/* ================================================== */
+
+static void
+handle_settime(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  struct timeval ts;
+  long offset_cs;
+  double dfreq_ppm, new_afreq_ppm;
+  ts.tv_sec = ntohl(rx_message->data.settime.ts.tv_sec);
+  ts.tv_usec = ntohl(rx_message->data.settime.ts.tv_usec);
+  if (MNL_AcceptTimestamp(&ts, &offset_cs, &dfreq_ppm, &new_afreq_ppm)) {
+    tx_message->status = htons(STT_SUCCESS);
+    tx_message->reply = htons(RPY_MANUAL_TIMESTAMP);
+    tx_message->data.manual_timestamp.centiseconds = htonl(offset_cs);
+    tx_message->data.manual_timestamp.dfreq_ppm = REAL2WIRE(dfreq_ppm);
+    tx_message->data.manual_timestamp.new_afreq_ppm = REAL2WIRE(new_afreq_ppm);
+  } else {
+    tx_message->status = htons(STT_NOTENABLED);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_local(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int on_off, stratum;
+  on_off = ntohl(rx_message->data.local.on_off);
+  if (on_off) {
+    stratum = ntohl(rx_message->data.local.stratum);
+    REF_EnableLocal(stratum);
+  } else {
+    REF_DisableLocal();
+  }
+  tx_message->status = htons(STT_SUCCESS);
+}
+
+/* ================================================== */
+
+static void
+handle_manual(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int option;
+  option = ntohl(rx_message->data.manual.option);
+  switch (option) {
+    case 0:
+      MNL_Disable();
+      break;
+    case 1:
+      MNL_Enable();
+      break;
+    case 2:
+      MNL_Reset();
+      break;
+  }
+  tx_message->status = htons(STT_SUCCESS);
+}
+
+/* ================================================== */
+
+static void
+handle_n_sources(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int n_sources;
+  n_sources = SRC_ReadNumberOfSources();
+  tx_message->status = htons(STT_SUCCESS);
+  tx_message->reply = htons(RPY_N_SOURCES);
+  tx_message->data.n_sources.n_sources = htonl(n_sources);
+}
+
+/* ================================================== */
+
+static void
+handle_source_data(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  RPT_SourceReport report;
+  struct timeval now_corr;
+  double local_clock_err;
+
+  /* Get data */
+  LCL_ReadCookedTime(&now_corr, &local_clock_err);
+  if (SRC_ReportSource(ntohl(rx_message->data.source_data.index), &report, &now_corr)) {
+    NSR_ReportSource(&report, &now_corr);
+    
+    tx_message->status = htons(STT_SUCCESS);
+    tx_message->reply  = htons(RPY_SOURCE_DATA);
+    
+    tx_message->data.source_data.ip_addr = htonl(report.ip_addr);
+    tx_message->data.source_data.stratum = htons(report.stratum);
+    tx_message->data.source_data.poll    = htons(report.poll);
+    switch (report.state) {
+      case RPT_SYNC:
+        tx_message->data.source_data.state   = htons(RPY_SD_ST_SYNC);
+        break;
+      case RPT_UNREACH:
+        tx_message->data.source_data.state   = htons(RPY_SD_ST_UNREACH);
+        break;
+      case RPT_FALSETICKER:
+        tx_message->data.source_data.state   = htons(RPY_SD_ST_FALSETICKER);
+        break;
+      case RPT_JITTERY:
+        tx_message->data.source_data.state   = htons(RPY_SD_ST_JITTERY);
+        break;
+      case RPT_OTHER:
+        tx_message->data.source_data.state   = htons(RPY_SD_ST_OTHER);
+        break;
+    }
+    switch (report.mode) {
+      case RPT_NTP_CLIENT:
+        tx_message->data.source_data.mode    = htons(RPY_SD_MD_CLIENT);
+        break;
+      case RPT_NTP_PEER:
+        tx_message->data.source_data.mode    = htons(RPY_SD_MD_PEER);
+        break;
+      case RPT_LOCAL_REFERENCE:
+        tx_message->data.source_data.mode    = htons(RPY_SD_MD_REF);
+        break;
+    }
+    tx_message->data.source_data.since_sample = htonl(report.latest_meas_ago);
+    tx_message->data.source_data.orig_latest_meas = htonl(report.orig_latest_meas);
+    tx_message->data.source_data.latest_meas = htonl(report.latest_meas);
+    tx_message->data.source_data.latest_meas_err = htonl(report.latest_meas_err);
+    tx_message->data.source_data.est_offset = htonl(report.est_offset);
+    tx_message->data.source_data.est_offset_err = htonl(report.est_offset_err);
+    tx_message->data.source_data.resid_freq = htonl(report.resid_freq);
+    tx_message->data.source_data.resid_skew = htonl(report.resid_skew);
+    
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_rekey(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  tx_message->status = htons(STT_SUCCESS);
+  KEY_Reload();
+}
+
+/* ================================================== */
+
+static void
+handle_allow(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (NCR_AddAccessRestriction(ip, subnet_bits, 1, 0)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_allowall(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (NCR_AddAccessRestriction(ip, subnet_bits, 1, 1)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_deny(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (NCR_AddAccessRestriction(ip, subnet_bits, 0, 0)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_denyall(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (NCR_AddAccessRestriction(ip, subnet_bits, 0, 1)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_cmdallow(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (CAM_AddAccessRestriction(ip, subnet_bits, 1, 0)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_cmdallowall(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (CAM_AddAccessRestriction(ip, subnet_bits, 1, 1)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_cmddeny(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (CAM_AddAccessRestriction(ip, subnet_bits, 0, 0)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_cmddenyall(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  int subnet_bits;
+  ip = ntohl(rx_message->data.allow_deny.ip);
+  subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+  if (CAM_AddAccessRestriction(ip, subnet_bits, 0, 1)) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_BADSUBNET);
+  }              
+}
+
+/* ================================================== */
+
+static void
+handle_accheck(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  ip = ntohl(rx_message->data.ac_check.ip);
+  if (NCR_CheckAccessRestriction(ip)) {
+    tx_message->status = htons(STT_ACCESSALLOWED);
+  } else {
+    tx_message->status = htons(STT_ACCESSDENIED);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_cmdaccheck(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  unsigned long ip;
+  ip = ntohl(rx_message->data.ac_check.ip);
+  if (CAM_CheckAccessRestriction(ip)) {
+    tx_message->status = htons(STT_ACCESSALLOWED);
+  } else {
+    tx_message->status = htons(STT_ACCESSDENIED);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_add_server(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  NTP_Remote_Address rem_addr;
+  SourceParameters params;
+  NSR_Status status;
+  
+  rem_addr.ip_addr = ntohl(rx_message->data.ntp_source.ip_addr);
+  rem_addr.port = (unsigned short)(ntohl(rx_message->data.ntp_source.port));
+  params.minpoll = ntohl(rx_message->data.ntp_source.minpoll);
+  params.maxpoll = ntohl(rx_message->data.ntp_source.maxpoll);
+  params.presend_minpoll = ntohl(rx_message->data.ntp_source.presend_minpoll);
+  params.authkey = ntohl(rx_message->data.ntp_source.authkey);
+  params.online  = ntohl(rx_message->data.ntp_source.online);
+  params.auto_offline = ntohl(rx_message->data.ntp_source.auto_offline);
+  params.max_delay = WIRE2REAL(rx_message->data.ntp_source.max_delay);
+  params.max_delay_ratio = WIRE2REAL(rx_message->data.ntp_source.max_delay_ratio);
+  status = NSR_AddServer(&rem_addr, &params);
+  switch (status) {
+    case NSR_Success:
+      tx_message->status = htons(STT_SUCCESS);
+      break;
+    case NSR_AlreadyInUse:
+      tx_message->status = htons(STT_SOURCEALREADYKNOWN);
+      break;
+    case NSR_TooManySources:
+      tx_message->status = htons(STT_TOOMANYSOURCES);
+      break;
+    case NSR_NoSuchSource:
+      CROAK("Impossible");
+      break;
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_add_peer(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  NTP_Remote_Address rem_addr;
+  SourceParameters params;
+  NSR_Status status;
+  
+  rem_addr.ip_addr = ntohl(rx_message->data.ntp_source.ip_addr);
+  rem_addr.port = (unsigned short)(ntohl(rx_message->data.ntp_source.port));
+  params.minpoll = ntohl(rx_message->data.ntp_source.minpoll);
+  params.maxpoll = ntohl(rx_message->data.ntp_source.maxpoll);
+  params.presend_minpoll = ntohl(rx_message->data.ntp_source.presend_minpoll);
+  params.authkey = ntohl(rx_message->data.ntp_source.authkey);
+  params.online  = ntohl(rx_message->data.ntp_source.online);
+  params.max_delay = WIRE2REAL(rx_message->data.ntp_source.max_delay);
+  params.max_delay_ratio = WIRE2REAL(rx_message->data.ntp_source.max_delay_ratio);
+  status = NSR_AddPeer(&rem_addr, &params);
+  switch (status) {
+    case NSR_Success:
+      tx_message->status = htons(STT_SUCCESS);
+      break;
+    case NSR_AlreadyInUse:
+      tx_message->status = htons(STT_SOURCEALREADYKNOWN);
+      break;
+    case NSR_TooManySources:
+      tx_message->status = htons(STT_TOOMANYSOURCES);
+      break;
+    case NSR_NoSuchSource:
+      CROAK("Impossible");
+      break;
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_del_source(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  NTP_Remote_Address rem_addr;
+  NSR_Status status;
+  
+  rem_addr.ip_addr = ntohl(rx_message->data.del_source.ip_addr);
+  rem_addr.port = 0;
+  
+  status = NSR_RemoveSource(&rem_addr);
+  switch (status) {
+    case NSR_Success:
+      tx_message->status = htons(STT_SUCCESS);
+      break;
+    case NSR_NoSuchSource:
+      tx_message->status = htons(STT_NOSUCHSOURCE);
+      break;
+    case NSR_TooManySources:
+    case NSR_AlreadyInUse:
+      CROAK("Impossible");
+      break;
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_writertc(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  switch (RTC_WriteParameters()) {
+    case RTC_ST_OK:
+      tx_message->status = htons(STT_SUCCESS);
+      break;
+    case RTC_ST_NODRV:
+      tx_message->status = htons(STT_NORTC);
+      break;
+    case RTC_ST_BADFILE:
+      tx_message->status = htons(STT_BADRTCFILE);
+      break;
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_dfreq(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  double dfreq;
+  dfreq = WIRE2REAL(rx_message->data.dfreq.dfreq);
+  LCL_AccumulateDeltaFrequency(dfreq * 1.0e-6);
+  LOG(LOGS_INFO, LOGF_CmdMon, "Accumulated delta freq of %.3fppm", dfreq);
+}
+
+/* ================================================== */
+
+static void
+handle_doffset(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  long sec, usec;
+  double doffset;
+  sec = (long)(ntohl(rx_message->data.doffset.sec));
+  usec = (long)(ntohl(rx_message->data.doffset.usec));
+  doffset = (double) sec + 1.0e-6 * (double) usec;
+  LOG(LOGS_INFO, LOGF_CmdMon, "Accumulated delta offset of %.6f seconds", doffset);
+  LCL_AccumulateOffset(doffset);
+}
+
+/* ================================================== */
+
+static void
+handle_tracking(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  RPT_TrackingReport rpt;
+
+  REF_GetTrackingReport(&rpt);
+  tx_message->status = htons(STT_SUCCESS);
+  tx_message->reply  = htons(RPY_TRACKING);
+  tx_message->data.tracking.ref_id = htonl(rpt.ref_id);
+  tx_message->data.tracking.stratum = htonl(rpt.stratum);
+  tx_message->data.tracking.ref_time_s = htonl(rpt.ref_time.tv_sec);
+  tx_message->data.tracking.ref_time_us = htonl(rpt.ref_time.tv_usec);
+  tx_message->data.tracking.current_correction_s = htonl(rpt.current_correction.tv_sec);
+  tx_message->data.tracking.current_correction_us = htonl(rpt.current_correction.tv_usec);
+  tx_message->data.tracking.freq_ppm = REAL2WIRE(rpt.freq_ppm);
+  tx_message->data.tracking.resid_freq_ppm = REAL2WIRE(rpt.resid_freq_ppm);
+  tx_message->data.tracking.skew_ppm = REAL2WIRE(rpt.skew_ppm);
+  tx_message->data.tracking.root_delay = REAL2WIRE(rpt.root_delay);
+  tx_message->data.tracking.root_dispersion = REAL2WIRE(rpt.root_dispersion);
+}
+
+/* ================================================== */
+
+static void
+handle_sourcestats(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  RPT_SourcestatsReport report;
+  status = SRC_ReportSourcestats(ntohl(rx_message->data.sourcestats.index),
+                                 &report);
+
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+    tx_message->reply = htons(RPY_SOURCESTATS);
+    tx_message->data.sourcestats.ip_addr = htonl(report.ip_addr);
+    tx_message->data.sourcestats.n_samples = htonl(report.n_samples);
+    tx_message->data.sourcestats.n_runs = htonl(report.n_runs);
+    tx_message->data.sourcestats.span_seconds = htonl(report.span_seconds);
+    tx_message->data.sourcestats.resid_freq_ppm = REAL2WIRE(report.resid_freq_ppm);
+    tx_message->data.sourcestats.skew_ppm = REAL2WIRE(report.skew_ppm);
+    tx_message->data.sourcestats.sd_us = htonl((unsigned long) (0.5 + report.sd_us));
+  } else {
+    tx_message->status = htons(STT_NOSUCHSOURCE);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_rtcreport(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  RPT_RTC_Report report;
+  status = RTC_GetReport(&report);
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+    tx_message->reply  = htons(RPY_RTC);
+    tx_message->data.rtc.ref_time = htonl(report.ref_time);
+    tx_message->data.rtc.n_samples = htons(report.n_samples);
+    tx_message->data.rtc.n_runs = htons(report.n_runs);
+    tx_message->data.rtc.span_seconds = htonl(report.span_seconds);
+    tx_message->data.rtc.rtc_seconds_fast = REAL2WIRE(report.rtc_seconds_fast);
+    tx_message->data.rtc.rtc_gain_rate_ppm = REAL2WIRE(report.rtc_gain_rate_ppm);
+  } else {
+    tx_message->status = htons(STT_NORTC);
+  }
+  return;
+}
+
+/* ================================================== */
+
+static void
+handle_trimrtc(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = RTC_Trim();
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NORTC);
+  }
+  return;
+}
+
+/* ================================================== */
+
+static void
+handle_cyclelogs(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  NCR_CycleLogFile();
+  SST_CycleLogFile();
+  REF_CycleLogFile();
+  RTC_CycleLogFile();
+  
+  tx_message->status = htons(STT_SUCCESS);
+  return;
+}
+
+/* ================================================== */
+
+#define FLIPL(X) ((X) = htonl(X))
+
+static void
+handle_subnets_accessed(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int i, j;
+  unsigned long ns;
+  unsigned long ip, bits_specd;
+  CLG_Status result;
+  
+  ns = ntohl(rx_message->data.subnets_accessed.n_subnets);
+  tx_message->status = htons(STT_SUCCESS);
+  tx_message->reply = htons(RPY_SUBNETS_ACCESSED);
+  tx_message->data.subnets_accessed.n_subnets = htonl(ns);
+
+  for (i=0; i<ns; i++) {
+    ip = ntohl(rx_message->data.subnets_accessed.subnets[i].ip);
+    bits_specd = ntohl(rx_message->data.subnets_accessed.subnets[i].bits_specd);
+
+    tx_message->data.subnets_accessed.subnets[i].ip = htonl(ip);
+    tx_message->data.subnets_accessed.subnets[i].bits_specd = htonl(bits_specd);
+    
+    result = CLG_GetSubnetBitmap(ip, bits_specd, tx_message->data.subnets_accessed.subnets[i].bitmap);
+    switch (result) {
+      case CLG_SUCCESS:
+      case CLG_EMPTYSUBNET:
+        /* Flip endianness of each 4 byte word.  Don't care if subnet
+           is empty - just return an all-zero bitmap. */
+        for (j=0; j<8; j++) {
+          FLIPL(tx_message->data.subnets_accessed.subnets[i].bitmap[j]);
+        }
+        break;
+      case CLG_BADSUBNET:
+        tx_message->status = htons(STT_BADSUBNET);
+        return;
+      case CLG_INACTIVE:
+        tx_message->status = htons(STT_INACTIVE);
+        return;
+      default:
+        CROAK("Impossible");
+        break;
+    }
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+handle_client_accesses(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  CLG_Status result;
+  RPT_ClientAccess_Report report;
+  unsigned long nc;
+  unsigned long ip;
+  int i;
+  struct timeval now;
+  double local_time_error;
+
+  LCL_ReadCookedTime(&now, &local_time_error);
+
+  nc = ntohl(rx_message->data.client_accesses.n_clients);
+  tx_message->status = htons(STT_SUCCESS);
+  tx_message->reply = htons(RPY_CLIENT_ACCESSES);
+  tx_message->data.client_accesses.n_clients = htonl(nc);
+
+  printf("%d %d\n", (int)sizeof(RPY_ClientAccesses_Client), (int)offsetof(CMD_Reply, data.client_accesses.clients));
+
+  for (i=0; i<nc; i++) {
+    ip = ntohl(rx_message->data.client_accesses.client_ips[i]);
+    tx_message->data.client_accesses.clients[i].ip = htonl(ip);
+
+    result = CLG_GetClientAccessReportByIP(ip, &report, now.tv_sec);
+    switch (result) {
+      case CLG_SUCCESS:
+        tx_message->data.client_accesses.clients[i].client_hits = htonl(report.client_hits);
+        tx_message->data.client_accesses.clients[i].peer_hits = htonl(report.peer_hits);
+        tx_message->data.client_accesses.clients[i].cmd_hits_auth = htonl(report.cmd_hits_auth);
+        tx_message->data.client_accesses.clients[i].cmd_hits_normal = htonl(report.cmd_hits_normal);
+        tx_message->data.client_accesses.clients[i].cmd_hits_bad = htonl(report.cmd_hits_bad);
+        tx_message->data.client_accesses.clients[i].last_ntp_hit_ago = htonl(report.last_ntp_hit_ago);
+        tx_message->data.client_accesses.clients[i].last_cmd_hit_ago = htonl(report.last_cmd_hit_ago);
+        printf("%08lx %lu %lu %lu %lu %lu %lu %lu\n", ip, report.client_hits, report.peer_hits, report.cmd_hits_auth, report.cmd_hits_normal, report.cmd_hits_bad, report.last_ntp_hit_ago, report.last_cmd_hit_ago);
+        break;
+      case CLG_EMPTYSUBNET:
+        /* Signal back to the client that this single client address
+           was unknown, by specifying the zero ip address, which will
+           always be invalid (hopefully) */
+        tx_message->data.client_accesses.clients[i].ip = htonl(0);
+        break;
+      case CLG_INACTIVE:
+        tx_message->status = htons(STT_INACTIVE);
+        return;
+      default:
+        CROAK("Impossible");
+        break;
+    }
+  }
+
+}
+
+/* ================================================== */
+
+static void
+handle_client_accesses_by_index(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  CLG_Status result;
+  RPT_ClientAccessByIndex_Report report;
+  unsigned long first_index, n_indices, last_index, n_indices_in_table;
+  int i, j;
+  struct timeval now;
+  double local_time_error;
+
+  LCL_ReadCookedTime(&now, &local_time_error);
+
+  first_index = ntohl(rx_message->data.client_accesses_by_index.first_index);
+  n_indices = ntohl(rx_message->data.client_accesses_by_index.n_indices);
+  last_index = first_index + n_indices - 1;
+
+  tx_message->status = htons(STT_SUCCESS);
+  tx_message->reply = htons(RPY_CLIENT_ACCESSES_BY_INDEX);
+
+  for (i = first_index, j = 0;
+       (i <= last_index) && (j < MAX_CLIENT_ACCESSES);
+       i++) {
+
+    result = CLG_GetClientAccessReportByIndex(i, &report, now.tv_sec, &n_indices_in_table);
+    tx_message->data.client_accesses_by_index.n_indices = htonl(n_indices_in_table);
+
+    switch (result) {
+      case CLG_SUCCESS:
+        tx_message->data.client_accesses_by_index.clients[j].ip = htonl(report.ip_addr);
+        tx_message->data.client_accesses_by_index.clients[j].client_hits = htonl(report.client_hits);
+        tx_message->data.client_accesses_by_index.clients[j].peer_hits = htonl(report.peer_hits);
+        tx_message->data.client_accesses_by_index.clients[j].cmd_hits_auth = htonl(report.cmd_hits_auth);
+        tx_message->data.client_accesses_by_index.clients[j].cmd_hits_normal = htonl(report.cmd_hits_normal);
+        tx_message->data.client_accesses_by_index.clients[j].cmd_hits_bad = htonl(report.cmd_hits_bad);
+        tx_message->data.client_accesses_by_index.clients[j].last_ntp_hit_ago = htonl(report.last_ntp_hit_ago);
+        tx_message->data.client_accesses_by_index.clients[j].last_cmd_hit_ago = htonl(report.last_cmd_hit_ago);
+        j++;
+        break;
+      case CLG_INDEXTOOLARGE:
+        break; /* ignore this index */
+      case CLG_INACTIVE:
+        tx_message->status = htons(STT_INACTIVE);
+        return;
+      default:
+        CROAK("Impossible");
+        break;
+    }
+  }
+
+  tx_message->data.client_accesses_by_index.next_index = htonl(i);
+  tx_message->data.client_accesses_by_index.n_clients = htonl(j);
+}
+
+/* ================================================== */
+
+static void
+handle_manual_list(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int n_samples;
+  int i;
+  RPY_ManualListSample *sample;
+  RPT_ManualSamplesReport report[MAX_MANUAL_LIST_SAMPLES];
+
+  tx_message->status = htons(STT_SUCCESS);
+  tx_message->reply = htons(RPY_MANUAL_LIST);
+  
+  MNL_ReportSamples(report, MAX_MANUAL_LIST_SAMPLES, &n_samples);
+  tx_message->data.manual_list.n_samples = htonl(n_samples);
+  for (i=0; i<n_samples; i++) {
+    sample = &tx_message->data.manual_list.samples[i];
+    sample->when = htonl(report[i].when);
+    sample->slewed_offset = REAL2WIRE(report[i].slewed_offset);
+    sample->orig_offset = REAL2WIRE(report[i].orig_offset);
+    sample->residual = REAL2WIRE(report[i].residual);
+  }
+}
+
+/* ================================================== */
+
+static void
+handle_manual_delete(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  int index;
+
+  index = ntohl(rx_message->data.manual_delete.index);
+  status = MNL_DeleteSample(index);
+  if (!status) {
+    tx_message->status = htons(STT_BADSAMPLE);
+  } else {
+    tx_message->status = htons(STT_SUCCESS);
+  }
+}  
+
+/* ================================================== */
+
+static void
+handle_make_step(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+  status = LCL_MakeStep();
+  if (status) {
+    tx_message->status = htons(STT_SUCCESS);
+  } else {
+    tx_message->status = htons(STT_NOTENABLED);
+  }
+  return;
+}
+
+/* ================================================== */
+
+static void
+handle_activity(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  RPT_ActivityReport report;
+  NSR_GetActivityReport(&report);
+  tx_message->data.activity.online = htonl(report.online);
+  tx_message->data.activity.offline = htonl(report.offline);
+  tx_message->data.activity.burst_online = htonl(report.burst_online);
+  tx_message->data.activity.burst_offline = htonl(report.burst_offline);
+  tx_message->status = htons(STT_SUCCESS);
+  tx_message->reply = htons(RPY_ACTIVITY);
+}
+
+/* ================================================== */
+
+#if 0
+/* ================================================== */
+
+static void
+handle_(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+  int status;
+}
+
+
+#endif
+
+/* ================================================== */
+/* Read a packet and process it */
+
+static void
+read_from_cmd_socket(void *anything)
+{
+  int status;
+  int read_length; /* Length of packet read */
+  int expected_length; /* Expected length of packet */
+  unsigned long flags;
+  CMD_Request rx_message;
+  CMD_Reply tx_message, *prev_tx_message;
+  int rx_message_length, tx_message_length;
+  struct sockaddr_in where_from;
+  int from_length;
+  unsigned long remote_ip;
+  unsigned short remote_port;
+  int md5_ok;
+  int utoken_ok, token_ok;
+  int issue_token;
+  int valid_ts;
+  int authenticated;
+  int localhost;
+  unsigned short rx_command;
+  unsigned long rx_message_token;
+  unsigned long tx_message_token;
+  unsigned long rx_message_seq;
+  unsigned long rx_attempt;
+  struct timeval now;
+  struct timeval cooked_now;
+  double local_clock_err;
+
+  flags = 0;
+  rx_message_length = sizeof(rx_message);
+  from_length = sizeof(where_from);
+
+  status = recvfrom(sock_fd, (char *)&rx_message, rx_message_length, flags,
+                    (struct sockaddr *)&where_from, &from_length);
+
+  if (status < 0) {
+    LOG(LOGS_WARN, LOGF_CmdMon, "Error [%s] reading from control socket (IP=%s port=%d)",
+        strerror(errno),
+        UTI_IPToDottedQuad(ntohl(where_from.sin_addr.s_addr)),
+        ntohs(where_from.sin_port));
+  }
+
+  read_length = status;
+  expected_length = PKL_CommandLength(&rx_message);
+
+  LCL_ReadRawTime(&now);
+  LCL_ReadCookedTime(&cooked_now, &local_clock_err);
+
+  tx_message.version = PROTO_VERSION_NUMBER;
+  tx_message.pkt_type = PKT_TYPE_CMD_REPLY;
+  tx_message.res1 = 0;
+  tx_message.res2 = 0;
+  tx_message.command = rx_message.command;
+  tx_message.sequence = rx_message.sequence;
+  tx_message.reply = htons(RPY_NULL);
+  tx_message.number = htons(1);
+  tx_message.total = htons(1);
+  tx_message.utoken = htonl(utoken);
+  /* Set this to a default (invalid) value.  This protects against the
+     token field being set to an arbitrary value if we reject the
+     message, e.g. due to the host failing the access check. */
+  tx_message.token = htonl(0xffffffffUL);
+
+  remote_ip = ntohl(where_from.sin_addr.s_addr);
+  remote_port = ntohs(where_from.sin_port);
+
+  localhost = (remote_ip == 0x7f000001UL);
+
+  if ((!ADF_IsAllowed(access_auth_table, remote_ip)) &&
+      (!localhost)) {
+    /* The client is not allowed access, so don't waste any more time
+       on him.  Note that localhost is always allowed access
+       regardless of the defined access rules - otherwise, we could
+       shut ourselves out completely! */
+
+    /* We ought to find another way to log this, there is an attack
+       here against the host because an adversary can just keep
+       hitting us with bad packets until our log file(s) fill up. */
+
+    LOG(LOGS_WARN, LOGF_CmdMon, "Command packet received from unauthorised host %s port %d",
+        UTI_IPToDottedQuad(remote_ip),
+        remote_port);
+
+    tx_message.status = htons(STT_NOHOSTACCESS);
+    transmit_reply(&tx_message, &where_from);
+
+    return;
+  }
+
+
+  if (read_length != expected_length) {
+    LOG(LOGS_WARN, LOGF_CmdMon, "Read incorrectly sized packet from %s:%hu", UTI_IPToDottedQuad(remote_ip), remote_port);
+    CLG_LogCommandAccess(remote_ip, CLG_CMD_BAD_PKT, cooked_now.tv_sec);
+    /* For now, just ignore the packet.  We may want to send a reply
+       back eventually */
+    return;
+  }
+
+  if ((rx_message.version != PROTO_VERSION_NUMBER) ||
+      (rx_message.pkt_type != PKT_TYPE_CMD_REQUEST) ||
+      (rx_message.res1 != 0) ||
+      (rx_message.res2 != 0)) {
+
+    /* We don't know how to process anything like this */
+    CLG_LogCommandAccess(remote_ip, CLG_CMD_BAD_PKT, cooked_now.tv_sec);
+    
+    return;
+  }
+
+  rx_command = ntohs(rx_message.command);
+
+  /* OK, we have a valid message.  Now dispatch on message type and process it. */
+
+  /* Do authentication stuff and command tokens here.  Well-behaved
+     clients will set their utokens to 0 to save us wasting our time
+     if the packet is unauthenticatable. */
+  if (rx_message.utoken != 0) {
+    md5_ok = check_rx_packet_auth(&rx_message);
+  } else {
+    md5_ok = 0;
+  }
+
+  /* All this malarky is to protect the system against various forms
+     of attack.
+
+     Simple packet forgeries are blocked by requiring the packet to
+     authenticate properly with MD5.  (The assumption is that the
+     command key is in a read-only keys file read by the daemon, and
+     is known only to administrators.)
+
+     Replay attacks are prevented by 2 fields in the packet.  The
+     'token' field is where the client plays back to us a token that
+     he was issued in an earlier reply.  Each time we reply to a
+     suitable packet, we issue a new token.  The 'utoken' field is set
+     to a new (hopefully increasing) value each time the daemon is
+     run.  This prevents packets from a previous incarnation being
+     played back at us when the same point in the 'token' sequence
+     comes up.  (The token mechanism also prevents a non-idempotent
+     command from being executed twice from the same client, if the
+     client fails to receive our reply the first time and tries a
+     resend.)
+
+     The problem is how a client should get its first token.  Our
+     token handling only remembers a finite number of issued tokens
+     (actually 32) - if a client replies with a (legitimate) token
+     older than that, it will be treated as though a duplicate token
+     has been supplied.  If a simple token-request protocol were used,
+     the whole thing would be vulnerable to a denial of service
+     attack, where an attacker just replays valid token-request
+     packets at us, causing us to keep issuing new tokens,
+     invalidating all the ones we have given out to true clients
+     already.
+
+     To protect against this, the token-request (REQ_LOGON) packet
+     includes a timestamp field.  To issue a token, we require that
+     this field is different from any we have processed before.  To
+     bound our storage, we require that the timestamp is within a
+     certain period of our current time.  For clients running on the
+     same host this will be easily satisfied.
+
+     */
+
+  utoken_ok = (ntohl(rx_message.utoken) == utoken);
+
+  /* Avoid binning a valid user's token if we merely get a forged
+     packet */
+  rx_message_token = ntohl(rx_message.token);
+  rx_message_seq = ntohl(rx_message.sequence);
+  rx_attempt = ntohs(rx_message.attempt);
+
+  if (md5_ok && utoken_ok) {
+    token_ok = check_token(rx_message_token);
+  } else {
+    token_ok = 0;
+  }
+
+  if (md5_ok && utoken_ok && !token_ok) {
+    /* This might be a resent message, due to the client not getting
+       our reply to the first attempt.  See if we can find the message. */
+    prev_tx_message = lookup_reply(rx_message_token, rx_message_seq, rx_attempt);
+    if (prev_tx_message) {
+      /* Just send this message again */
+      tx_message_length = PKL_ReplyLength(prev_tx_message);
+      status = sendto(sock_fd, (void *) prev_tx_message, tx_message_length, 0,
+                      (struct sockaddr *) &where_from, sizeof(where_from));
+      if (status < 0) {
+        LOG(LOGS_WARN, LOGF_CmdMon, "Could not send response to %s:%hu", UTI_IPToDottedQuad(remote_ip), remote_port);
+      }
+      return;
+    }
+    /* Otherwise, just fall through into normal processing */
+
+  }
+
+  if (md5_ok && utoken_ok && token_ok) {
+    /* See whether we can discard the previous reply from storage */
+    token_acknowledged(rx_message_token, &now);
+  }
+
+  valid_ts = 0;
+
+  if (md5_ok && ((utoken_ok && token_ok) ||
+                 ((ntohl(rx_message.utoken) == SPECIAL_UTOKEN) &&
+                  (rx_command == REQ_LOGON) &&
+                  (valid_ts = ts_is_unique_and_not_stale(&rx_message.data.logon.ts, &now))))) {
+    issue_token = 1;
+  } else {
+    issue_token = 0;
+  }
+
+  authenticated = md5_ok & utoken_ok & token_ok;
+
+  if (authenticated) {
+    CLG_LogCommandAccess(remote_ip, CLG_CMD_AUTH, cooked_now.tv_sec);
+  } else {
+    CLG_LogCommandAccess(remote_ip, CLG_CMD_NORMAL, cooked_now.tv_sec);
+  }
+
+  if (issue_token) {
+    /* Only command clients where the user has apparently 'logged on'
+       get a token to allow them to emit an authenticated command next
+       time */
+    tx_message_token = get_token();
+  } else {
+    tx_message_token = 0xffffffffUL;
+  }
+
+  tx_message.token = htonl(tx_message_token);
+
+
+  /* If command type is invalid, send back reply */
+  if (rx_command >= N_REQUEST_TYPES) {
+    tx_message.status = htons(STT_INVALID);
+    tx_message.reply = htons(RPY_NULL);
+  } else {
+    int allowed = 0;
+
+    /* Check level of authority required to issue the command */
+    switch(permissions[rx_command]) {
+      case PERMIT_AUTH:
+        if (authenticated) {
+          allowed = 1;
+        } else {
+          allowed = 0;
+        }
+        break;
+      case PERMIT_LOCAL:
+        if (authenticated || localhost) {
+          allowed = 1;
+        } else {
+          allowed = 0;
+        }
+        break;
+      case PERMIT_OPEN:
+        allowed = 1;
+        break;
+      default:
+        CROAK("Impossible");
+    }
+
+    if (allowed) {
+      switch(rx_command) {
+        case REQ_NULL:
+          handle_null(&rx_message, &tx_message);
+          break;
+
+        case REQ_ONLINE:
+          handle_online(&rx_message, &tx_message);
+          break;
+
+        case REQ_OFFLINE:
+          handle_offline(&rx_message, &tx_message);
+          break;
+
+        case REQ_BURST:
+          handle_burst(&rx_message, &tx_message);
+          break;
+
+        case REQ_MODIFY_MINPOLL:
+          handle_modify_minpoll(&rx_message, &tx_message);
+          break;
+
+        case REQ_MODIFY_MAXPOLL:
+          handle_modify_maxpoll(&rx_message, &tx_message);
+          break;
+
+        case REQ_DUMP:
+          SRC_DumpSources();
+          tx_message.status = htons(STT_SUCCESS);
+          break;
+
+        case REQ_MODIFY_MAXDELAY:
+          handle_modify_maxdelay(&rx_message, &tx_message);
+          break;
+
+        case REQ_MODIFY_MAXDELAYRATIO:
+          handle_modify_maxdelayratio(&rx_message, &tx_message);
+          break;
+
+        case REQ_MODIFY_MAXUPDATESKEW:
+          handle_modify_maxupdateskew(&rx_message, &tx_message);
+          break;
+
+        case REQ_LOGON:
+          /* If the log-on fails, record the reason why */
+          if (!issue_token) {
+            LOG(LOGS_WARN, LOGF_CmdMon,
+                "Bad command logon from %s port %d (md5_ok=%d valid_ts=%d)\n",
+                UTI_IPToDottedQuad(remote_ip),
+                remote_port,
+                md5_ok, valid_ts);
+          }
+
+          if (issue_token == 1) {
+            tx_message.status = htons(STT_SUCCESS);
+          } else if (!md5_ok) {
+            tx_message.status = htons(STT_UNAUTH);
+          } else if (!valid_ts) {
+            tx_message.status = htons(STT_INVALIDTS);
+          } else {
+            tx_message.status = htons(STT_FAILED);
+          }
+            
+          break;
+
+        case REQ_SETTIME:
+          handle_settime(&rx_message, &tx_message);
+          break;
+        
+        case REQ_LOCAL:
+          handle_local(&rx_message, &tx_message);
+          break;
+
+        case REQ_MANUAL:
+          handle_manual(&rx_message, &tx_message);
+          break;
+
+        case REQ_N_SOURCES:
+          handle_n_sources(&rx_message, &tx_message);
+          break;
+
+        case REQ_SOURCE_DATA:
+          handle_source_data(&rx_message, &tx_message);
+          break;
+
+        case REQ_REKEY:
+          handle_rekey(&rx_message, &tx_message);
+          break;
+
+        case REQ_ALLOW:
+          handle_allow(&rx_message, &tx_message);
+          break;
+
+        case REQ_ALLOWALL:
+          handle_allowall(&rx_message, &tx_message);
+          break;
+
+        case REQ_DENY:
+          handle_deny(&rx_message, &tx_message);
+          break;
+
+        case REQ_DENYALL:
+          handle_denyall(&rx_message, &tx_message);
+          break;
+
+        case REQ_CMDALLOW:
+          handle_cmdallow(&rx_message, &tx_message);
+          break;
+
+        case REQ_CMDALLOWALL:
+          handle_cmdallowall(&rx_message, &tx_message);
+          break;
+
+        case REQ_CMDDENY:
+          handle_cmddeny(&rx_message, &tx_message);
+          break;
+
+        case REQ_CMDDENYALL:
+          handle_cmddenyall(&rx_message, &tx_message);
+          break;
+
+        case REQ_ACCHECK:
+          handle_accheck(&rx_message, &tx_message);
+          break;
+
+        case REQ_CMDACCHECK:
+          handle_cmdaccheck(&rx_message, &tx_message);
+          break;
+
+        case REQ_ADD_SERVER:
+          handle_add_server(&rx_message, &tx_message);
+          break;
+
+        case REQ_ADD_PEER:
+          handle_add_peer(&rx_message, &tx_message);
+          break;
+
+        case REQ_DEL_SOURCE:
+          handle_del_source(&rx_message, &tx_message);
+          break;
+
+        case REQ_WRITERTC:
+          handle_writertc(&rx_message, &tx_message);
+          break;
+          
+        case REQ_DFREQ:
+          handle_dfreq(&rx_message, &tx_message);
+          break;
+
+        case REQ_DOFFSET:
+          handle_doffset(&rx_message, &tx_message);
+          break;
+
+        case REQ_TRACKING:
+          handle_tracking(&rx_message, &tx_message);
+          break;
+
+        case REQ_SOURCESTATS:
+          handle_sourcestats(&rx_message, &tx_message);
+          break;
+
+        case REQ_RTCREPORT:
+          handle_rtcreport(&rx_message, &tx_message);
+          break;
+          
+        case REQ_TRIMRTC:
+          handle_trimrtc(&rx_message, &tx_message);
+          break;
+
+        case REQ_CYCLELOGS:
+          handle_cyclelogs(&rx_message, &tx_message);
+          break;
+
+        case REQ_SUBNETS_ACCESSED:
+          handle_subnets_accessed(&rx_message, &tx_message);
+          break;
+
+        case REQ_CLIENT_ACCESSES:
+          handle_client_accesses(&rx_message, &tx_message);
+          break;
+
+        case REQ_CLIENT_ACCESSES_BY_INDEX:
+          handle_client_accesses_by_index(&rx_message, &tx_message);
+          break;
+
+        case REQ_MANUAL_LIST:
+          handle_manual_list(&rx_message, &tx_message);
+          break;
+
+        case REQ_MANUAL_DELETE:
+          handle_manual_delete(&rx_message, &tx_message);
+          break;
+
+        case REQ_MAKESTEP:
+          handle_make_step(&rx_message, &tx_message);
+          break;
+
+        case REQ_ACTIVITY:
+          handle_activity(&rx_message, &tx_message);
+          break;
+
+        default:
+          /* Ignore message */
+          break;
+      }
+    } else {
+      tx_message.status = htons(STT_UNAUTH);
+    }
+  }
+
+  if (md5_ok) {
+    generate_tx_packet_auth(&tx_message);
+  }
+
+  if (token_ok) {
+    save_reply(&tx_message,
+               rx_message_token,
+               tx_message_token,
+               rx_message_seq,
+               rx_attempt,
+               &now);
+  }
+
+  /* Transmit the response */
+  {
+    /* Include a simple way to lose one message in three to test resend */
+
+    static int do_it=1;
+
+    if (do_it) {
+      transmit_reply(&tx_message, &where_from);
+    }
+
+#if 0
+    do_it = ((do_it + 1) % 3);
+#endif
+  }
+
+  return;
+
+}
+
+/* ================================================== */
+
+int
+CAM_AddAccessRestriction(unsigned long ip_addr, int subnet_bits, int allow, int all)
+ {
+  ADF_Status status;
+
+  if (allow) {
+    if (all) {
+      status = ADF_AllowAll(access_auth_table, ip_addr, subnet_bits);
+    } else {
+      status = ADF_Allow(access_auth_table, ip_addr, subnet_bits);
+    }
+  } else {
+    if (all) {
+      status = ADF_DenyAll(access_auth_table, ip_addr, subnet_bits);
+    } else {
+      status = ADF_Deny(access_auth_table, ip_addr, subnet_bits);
+    }
+  }
+
+  if (status == ADF_BADSUBNET) {
+    return 0;
+  } else if (status == ADF_SUCCESS) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+int
+CAM_CheckAccessRestriction(unsigned long ip_addr)
+{
+  return ADF_IsAllowed(access_auth_table, ip_addr);
+}
+
+
+/* ================================================== */
+/* ================================================== */
diff --git a/cmdmon.h b/cmdmon.h
new file mode 100644 (file)
index 0000000..7dcd539
--- /dev/null
+++ b/cmdmon.h
@@ -0,0 +1,41 @@
+/*
+  $Header: /cvs/src/chrony/cmdmon.h,v 1.8 2002/02/28 23:27:09 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for the control and monitoring module in the software
+  */
+
+#ifndef GOT_CMDMON_H
+#define GOT_CMDMON_H
+
+extern void CAM_Initialise(void);
+
+extern void CAM_Finalise(void);
+
+extern int CAM_AddAccessRestriction(unsigned long ip_addr, int subnet_bits, int allow, int all);
+extern int CAM_CheckAccessRestriction(unsigned long ip_addr);
+
+#endif /* GOT_CMDMON_H */
diff --git a/cmdparse.c b/cmdparse.c
new file mode 100644 (file)
index 0000000..3353ac1
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+  $Header: /cvs/src/chrony/cmdparse.c,v 1.11 2003/01/20 22:52:07 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+  
+  Module for parsing various forms of directive and command lines that
+  are common to the configuration file and to the command client.
+
+  */
+
+#include "sysincl.h"
+
+#include "cmdparse.h"
+#include "nameserv.h"
+
+#define MAXLEN 2047
+#define SMAXLEN "2047"
+
+/* ================================================== */
+
+CPS_Status
+CPS_ParseNTPSourceAdd(const char *line, CPS_NTP_Source *src)
+{
+  int ok, n, done;
+  char cmd[MAXLEN+1], hostname[MAXLEN+1];
+  CPS_Status result;
+  
+  src->port = 123;
+  src->params.minpoll = 6;
+  src->params.maxpoll = 10;
+  src->params.presend_minpoll = 0;
+  src->params.authkey = INACTIVE_AUTHKEY;
+  src->params.max_delay = 16.0;
+  src->params.max_delay_ratio = 16384.0;
+  src->params.online = 1;
+  src->params.auto_offline = 0;
+
+  result = CPS_Success;
+  
+  ok = 0;
+  if (sscanf(line, "%" SMAXLEN "s%n", hostname, &n) == 1) {
+    src->ip_addr = DNS_Name2IPAddress(hostname);
+    if (src->ip_addr != DNS_Failed_Address) {
+      ok = 1;
+    }
+  }
+
+  if (!ok) {
+    result = CPS_BadHost;
+  } else {
+
+    line += n;
+    
+    /* Parse subfields */
+    ok = 1;
+    done = 0;
+    do {
+      if (sscanf(line, "%" SMAXLEN "s%n", cmd, &n) == 1) {
+        
+        line += n;
+        
+        if (!strncasecmp(cmd, "port", 4)) {
+          if (sscanf(line, "%hu%n", &src->port, &n) != 1) {
+            result = CPS_BadPort;
+            ok = 0;
+            done = 1;
+          } else {
+            line += n;
+          }
+        } else if (!strncasecmp(cmd, "minpoll", 7)) {
+          if (sscanf(line, "%d%n", &src->params.minpoll, &n) != 1) {
+            result = CPS_BadMinpoll;
+            ok = 0;
+            done = 1;
+          } else {
+            line += n;
+          }
+        } else if (!strncasecmp(cmd, "maxpoll", 7)) {
+          if (sscanf(line, "%d%n", &src->params.maxpoll, &n) != 1) {
+            result = CPS_BadMaxpoll;
+            ok = 0;
+            done = 1;
+          } else {
+            line += n;
+          }
+        } else if (!strncasecmp(cmd, "presend", 7)) {
+          if (sscanf(line, "%d%n", &src->params.presend_minpoll, &n) != 1) {
+            result = CPS_BadPresend;
+            ok = 0;
+            done = 1;
+          } else {
+            line += n;
+          }
+          /* This MUST come before the following one ! */
+        } else if (!strncasecmp(cmd, "maxdelayratio", 13)) {
+          if (sscanf(line, "%lf%n", &src->params.max_delay_ratio, &n) != 1) {
+            result = CPS_BadMaxdelayratio;
+            ok = 0;
+            done = 1;
+          } else {
+            line += n;
+          }
+        } else if (!strncasecmp(cmd, "maxdelay", 8)) {
+          if (sscanf(line, "%lf%n", &src->params.max_delay, &n) != 1) {
+            result = CPS_BadMaxdelay;
+            ok = 0;
+            done = 1;
+          } else {
+            line += n;
+          }
+        } else if (!strncasecmp(cmd, "key", 3)) {
+          if (sscanf(line, "%lu%n", &src->params.authkey, &n) != 1) {
+            result = CPS_BadKey;
+            ok = 0;
+            done = 1;
+          } else {
+            line += n;
+          }
+        } else if (!strncasecmp(cmd, "offline", 7)) {
+          src->params.online = 0;
+
+        } else if (!strncasecmp(cmd, "auto_offline", 12)) {
+          src->params.auto_offline = 1;
+        
+        } else {
+          result = CPS_BadOption;
+          ok = 0;
+          done = 1;
+        }
+      } else {
+        done = 1;
+      }
+    } while (!done);
+  }
+
+  return result;
+    
+}
+
+/* ================================================== */
+
diff --git a/cmdparse.h b/cmdparse.h
new file mode 100644 (file)
index 0000000..38cc74c
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+  $Header: /cvs/src/chrony/cmdparse.h,v 1.7 2002/02/28 23:27:09 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for the command parser
+  */
+
+#ifndef GOT_CMDPARSE_H
+#define GOT_CMDPARSE_H
+
+#include "srcparams.h"
+
+typedef enum {
+  CPS_Success,
+  CPS_BadOption,
+  CPS_BadHost,
+  CPS_BadPort,
+  CPS_BadMinpoll,
+  CPS_BadMaxpoll,
+  CPS_BadPresend,
+  CPS_BadMaxdelayratio,
+  CPS_BadMaxdelay,
+  CPS_BadKey
+} CPS_Status;
+
+typedef struct {
+  unsigned long ip_addr;
+  unsigned short port;
+  SourceParameters params;
+} CPS_NTP_Source;
+
+/* Parse a command to add an NTP server or peer */
+extern CPS_Status CPS_ParseNTPSourceAdd(const char *line, CPS_NTP_Source *src);
+  
+
+
+#endif /* GOT_CMDPARSE_H */
diff --git a/conf.c b/conf.c
new file mode 100644 (file)
index 0000000..48f9436
--- /dev/null
+++ b/conf.c
@@ -0,0 +1,1235 @@
+/*
+  $Header: /cvs/src/chrony/conf.c,v 1.42 2003/03/27 23:45:47 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Module that reads and processes the configuration file.
+
+1999-12-19  Kalle Olavi Niemitalo  <tosi@stekt.oulu.fi>
+
+       * conf.c: Added a new configuration setting "acquisitionport" and
+       a function CNF_GetAcquisitionPort to read its value.
+       (acquisition_port): New variable.
+       (parse_port): Delegate most work to new function parse_some_port.
+       (parse_acquisitionport): New function.
+       (commands): Added "acquisitionport".
+       (CNF_GetAcquisitionPort): New function.
+
+  */
+
+#include "sysincl.h"
+
+#include "conf.h"
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "cmdmon.h"
+#include "srcparams.h"
+#include "logging.h"
+#include "nameserv.h"
+#include "memory.h"
+#include "acquire.h"
+#include "cmdparse.h"
+#include "broadcast.h"
+
+/* ================================================== */
+
+#define DEFAULT_CONF_FILE "/etc/chrony.conf"
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void parse_commandkey(const char *);
+static void parse_driftfile(const char *);
+static void parse_dumpdir(const char *);
+static void parse_dumponexit(const char *);
+static void parse_keyfile(const char *);
+static void parse_rtcfile(const char *);
+static void parse_log(const char *);
+static void parse_logdir(const char *);
+static void parse_maxupdateskew(const char *);
+static void parse_peer(const char *);
+static void parse_acquisitionport(const char *);
+static void parse_port(const char *);
+static void parse_server(const char *);
+static void parse_local(const char *);
+static void parse_manual(const char *);
+static void parse_initstepslew(const char *);
+static void parse_allow(const char *);
+static void parse_deny(const char *);
+static void parse_cmdallow(const char *);
+static void parse_cmddeny(const char *);
+static void parse_cmdport(const char *);
+static void parse_rtconutc(const char *);
+static void parse_noclientlog(const char *);
+static void parse_logchange(const char *);
+static void parse_mailonchange(const char *);
+static void parse_bindaddress(const char *);
+static void parse_bindcmdaddress(const char *);
+static void parse_rtcdevice(const char *);
+static void parse_pidfile(const char *);
+static void parse_broadcast(const char *);
+static void parse_linux_hz(const char *);
+static void parse_linux_freq_scale(const char *);
+
+/* ================================================== */
+/* Configuration variables */
+
+static char *rtc_device = "/dev/rtc";
+static int acquisition_port = 0; /* 0 means let kernel choose port */
+static int ntp_port = 123;
+static char *keys_file = NULL;
+static char *drift_file = NULL;
+static char *rtc_file = NULL;
+static unsigned long command_key_id;
+static double max_update_skew = DBL_MAX;
+
+static int cmd_port = -1;
+
+static int do_log_measurements = 0;
+static int do_log_statistics = 0;
+static int do_log_tracking = 0;
+static int do_log_rtc = 0;
+static int do_dump_on_exit = 0;
+static char *logdir = ".";
+static char *dumpdir = ".";
+
+static int enable_local=0;
+#define DEFAULT_LOCAL_STRATUM 8
+static int local_stratum;
+
+static int do_init_stepslew = 0;
+static int n_init_srcs;
+
+/* Threshold (in seconds) - if absolute value of initial error is less
+   than this, slew instead of stepping */
+static int init_slew_threshold = -1;
+#define MAX_INIT_SRCS 8
+static unsigned long init_srcs_ip[MAX_INIT_SRCS];
+
+static int enable_manual=0;
+
+/* Flag set if the RTC runs UTC (default is it runs local time
+   incl. daylight saving). */
+static int rtc_on_utc = 0;
+
+/* Flag set if we should log to syslog when a time adjustment
+   exceeding the threshold is initiated */
+static int do_log_change = 0;
+static double log_change_threshold = 0.0;
+
+static char *mail_user_on_change = NULL;
+static double mail_change_threshold = 0.0;
+
+/* Flag indicating that we don't want to log clients, e.g. to save
+   memory */
+static int no_client_log = 0;
+
+/* IP address (host order) for binding the NTP socket to.  0 means INADDR_ANY
+   will be used */
+static unsigned long bind_address = 0UL;
+
+/* IP address (host order) for binding the command socket to.  0 means
+   use the value of bind_address */
+static unsigned long bind_cmd_address = 0UL;
+
+/* Filename to use for storing pid of running chronyd, to prevent multiple
+ * chronyds being started. */
+static char *pidfile = "/var/run/chronyd.pid";
+
+/* Boolean for whether the Linux HZ value has been overridden, and the
+ * new value. */
+static int set_linux_hz = 0;
+static int linux_hz;
+
+/* Boolean for whether the Linux frequency scaling value (i.e. the one that's
+ * approx (1<<SHIFT_HZ)/HZ) has been overridden, and the new value. */
+static int set_linux_freq_scale = 0;
+static double linux_freq_scale;
+
+/* ================================================== */
+
+typedef struct {
+  const char *keyword;
+  int len;
+  void (*handler)(const char *);
+} Command;
+
+static const Command commands[] = {
+  {"server", 6, parse_server},
+  {"peer", 4, parse_peer},
+  {"acquisitionport", 15, parse_acquisitionport},
+  {"port", 4, parse_port},
+  {"driftfile", 9, parse_driftfile},
+  {"keyfile", 7, parse_keyfile},
+  {"rtcfile", 7, parse_rtcfile},
+  {"logdir", 6, parse_logdir},
+  {"log", 3, parse_log},
+  {"dumponexit", 10, parse_dumponexit},
+  {"dumpdir", 7, parse_dumpdir},
+  {"maxupdateskew", 13, parse_maxupdateskew},
+  {"commandkey", 10, parse_commandkey},
+  {"initstepslew", 12, parse_initstepslew},
+  {"local", 5, parse_local},
+  {"manual", 6, parse_manual},
+  {"allow", 5, parse_allow},
+  {"deny", 4, parse_deny},
+  {"cmdallow", 8, parse_cmdallow},
+  {"cmddeny", 7, parse_cmddeny},
+  {"cmdport", 7, parse_cmdport},
+  {"rtconutc", 8, parse_rtconutc},
+  {"noclientlog", 11, parse_noclientlog},
+  {"logchange", 9, parse_logchange},
+  {"mailonchange", 12, parse_mailonchange},
+  {"bindaddress", 11, parse_bindaddress},
+  {"bindcmdaddress", 14, parse_bindcmdaddress},
+  {"rtcdevice", 9, parse_rtcdevice},
+  {"pidfile", 7, parse_pidfile},
+  {"broadcast", 9, parse_broadcast},
+  {"linux_hz", 8, parse_linux_hz},
+  {"linux_freq_scale", 16, parse_linux_freq_scale}
+};
+
+static int n_commands = (sizeof(commands) / sizeof(commands[0]));
+
+/* The line number in the configuration file being processed */
+static int line_number;
+
+/* ================================================== */
+
+typedef enum {
+  SERVER, PEER
+} NTP_Source_Type;
+
+typedef struct {
+  NTP_Source_Type type;
+  unsigned long ip_addr;
+  unsigned short port;
+  SourceParameters params;
+} NTP_Source;
+
+#define MAX_NTP_SOURCES 64
+
+static NTP_Source ntp_sources[MAX_NTP_SOURCES];
+static int n_ntp_sources = 0;
+
+/* ================================================== */
+
+typedef struct _AllowDeny {
+  struct _AllowDeny *next;
+  struct _AllowDeny *prev;
+  unsigned long ip;
+  int subnet_bits;
+  int all; /* 1 to override existing more specific defns */
+  int allow; /* 0 for deny, 1 for allow */
+} AllowDeny;
+
+static AllowDeny ntp_auth_list = {&ntp_auth_list, &ntp_auth_list};
+static AllowDeny cmd_auth_list = {&cmd_auth_list, &cmd_auth_list};
+
+/* ================================================== */
+
+/* Read the configuration file */
+void
+CNF_ReadFile(const char *filename)
+{
+  FILE *in;
+  char line[2048];
+  char *p;
+  int i, ok;
+
+  if (filename == NULL) {
+    filename = DEFAULT_CONF_FILE;
+  }
+
+  in = fopen(filename, "r");
+  if (!in) {
+    LOG(LOGS_ERR, LOGF_Configure, "Could not open configuration file [%s]", filename);
+  } else {
+
+    line_number = 0;
+
+    /* Success */
+    while (fgets(line, sizeof(line), in)) {
+
+      line_number++;
+
+      /* Strip trailing newline */
+      line[strlen(line) - 1] = 0;
+
+      /* Discard comment lines, blank lines etc */
+      p = line;
+      while(*p && (isspace((unsigned char)*p)))
+        p++;
+
+      if (!*p || (strchr("!;#%", *p) != NULL))
+        continue;
+
+      /* We have a real line, now try to match commands */
+      ok = 0;
+      for (i=0; i<n_commands; i++) {
+        if (!strncasecmp(commands[i].keyword, p, commands[i].len)) {
+          (*(commands[i].handler))(p + commands[i].len);
+          ok = 1;
+        }
+      }      
+
+      if (!ok) {
+        LOG(LOGS_WARN, LOGF_Configure, "Line %d in configuration file [%s] contains invalid command",
+            line_number, filename);
+      }
+
+    }
+
+    fclose(in);
+  }
+
+}
+
+/* ================================================== */
+
+static void
+parse_source(const char *line, NTP_Source_Type type)
+{
+  int i;
+  NTP_Source s;
+  CPS_Status status;
+  CPS_NTP_Source params;
+
+  s.type = type;
+  status = CPS_ParseNTPSourceAdd(line, &params);
+
+  switch (status) {
+    case CPS_Success:
+      s.port = params.port;
+      s.ip_addr = params.ip_addr;
+      s.params = params.params;
+
+      i = n_ntp_sources++;
+      ntp_sources[i] = s;
+
+      break;
+    case CPS_BadOption:
+      LOG(LOGS_WARN, LOGF_Configure, "Unrecognized subcommand at line %d", line_number);
+      break;
+    case CPS_BadHost:
+      LOG(LOGS_WARN, LOGF_Configure, "Invalid host/IP address at line %d", line_number);
+      break;
+    case CPS_BadPort:
+      LOG(LOGS_WARN, LOGF_Configure, "Unreadable port number at line %d", line_number);
+      break;
+    case CPS_BadMinpoll:
+      LOG(LOGS_WARN, LOGF_Configure, "Unreadable minpoll value at line %d", line_number);
+      break;
+    case CPS_BadMaxpoll:
+      LOG(LOGS_WARN, LOGF_Configure, "Unreadable maxpoll value at line %d", line_number);
+      break;
+    case CPS_BadPresend:
+      LOG(LOGS_WARN, LOGF_Configure, "Unreadable presend value at line %d", line_number);
+      break;
+    case CPS_BadMaxdelayratio:
+      LOG(LOGS_WARN, LOGF_Configure, "Unreadable max delay ratio value at line %d", line_number);
+      break;
+    case CPS_BadMaxdelay:
+      LOG(LOGS_WARN, LOGF_Configure, "Unreadable max delay value at line %d", line_number);
+      break;
+    case CPS_BadKey:
+      LOG(LOGS_WARN, LOGF_Configure, "Unreadable key value at line %d", line_number);
+      break;
+  }
+
+  return;
+
+}
+
+/* ================================================== */
+
+static void
+parse_server(const char *line)
+{
+  parse_source(line, SERVER);
+}
+
+/* ================================================== */
+
+static void
+parse_peer(const char *line)
+{
+  parse_source(line, PEER);
+}
+
+/* ================================================== */
+
+static void
+parse_some_port(const char *line, int *portvar)
+{
+  if (sscanf(line, "%d", portvar) != 1) {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not read port number at line %d in file", line_number);
+  }
+}
+
+/* ================================================== */
+
+static void
+parse_acquisitionport(const char *line)
+{
+  parse_some_port(line, &acquisition_port);
+}
+
+/* ================================================== */
+
+static void
+parse_port(const char *line)
+{
+  parse_some_port(line, &ntp_port);
+}
+
+/* ================================================== */
+
+static void
+parse_maxupdateskew(const char *line)
+{
+  if (sscanf(line, "%lf", &max_update_skew) != 1) {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not read max update skew at line %d in file", line_number);
+  }
+}
+
+/* ================================================== */
+
+static void
+parse_driftfile(const char *line)
+{
+  /* This must allocate enough space! */
+  drift_file = MallocArray(char, 1 + strlen(line));
+  sscanf(line, "%s", drift_file);
+}
+
+/* ================================================== */
+
+static void
+strip_trailing_spaces(char *p)
+{
+  char *q;
+  for (q=p; *q; q++)
+    ;
+  
+  for (q--; isspace((unsigned char)*q); q--)
+    ;
+
+  *++q = 0;
+}
+
+/* ================================================== */
+
+static void
+parse_keyfile(const char *line)
+{
+  /* This must allocate enough space! */
+  keys_file = MallocArray(char, 1 + strlen(line));
+  sscanf(line, "%s", keys_file);
+  strip_trailing_spaces(keys_file);
+}
+
+/* ================================================== */
+
+static void
+parse_rtcfile(const char *line)
+{
+  rtc_file = MallocArray(char, 1 + strlen(line));
+  sscanf(line, "%s", rtc_file);
+  strip_trailing_spaces(rtc_file);
+}  
+
+/* ================================================== */
+
+static void
+parse_rtcdevice(const char *line)
+{
+  rtc_device = MallocArray(char, 1 + strlen(line));
+  sscanf(line, "%s", rtc_device);
+}
+
+/* ================================================== */
+
+static void
+parse_logdir(const char *line)
+{
+  logdir = MallocArray(char, 1 + strlen(line));
+  sscanf(line, "%s", logdir);
+}
+
+/* ================================================== */
+
+static void
+parse_dumpdir(const char *line)
+{
+  dumpdir = MallocArray(char, 1 + strlen(line));
+  sscanf(line, "%s", dumpdir);
+}
+
+/* ================================================== */
+
+static void
+parse_dumponexit(const char *line)
+{
+  do_dump_on_exit = 1;
+}
+
+/* ================================================== */
+
+static void
+parse_log(const char *line)
+{
+  do {
+    while (*line && isspace((unsigned char)*line)) line++;
+    if (*line) {
+      if (!strncmp(line, "measurements", 12)) {
+        do_log_measurements = 1;
+        line += 12;
+      } else if (!strncmp(line, "statistics", 10)) {
+        do_log_statistics = 1;
+        line += 10;
+      } else if (!strncmp(line, "tracking", 8)) {
+        do_log_tracking = 1;
+        line += 8;
+      } else if (!strncmp(line, "rtc", 3)) {
+        do_log_rtc = 1;
+        line += 3;
+      } else {
+        break;
+      }
+    } else {
+      break;
+    }
+  } while (1);
+}
+
+/* ================================================== */
+
+static void
+parse_commandkey(const char *line)
+{
+  if (sscanf(line, "%lu", &command_key_id) != 1) {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not read command key ID at line %d", line_number);
+  }
+}
+
+/* ================================================== */
+
+static void
+parse_local(const char *line)
+{
+  int stratum;
+  enable_local = 1;
+  if (sscanf(line, "%*[ \t]stratum%d", &stratum) == 1) {
+    local_stratum = stratum;
+  } else {
+    local_stratum = DEFAULT_LOCAL_STRATUM;
+  }
+}
+
+/* ================================================== */
+
+static void
+parse_cmdport(const char *line)
+{
+  if (sscanf(line, "%d", &cmd_port) != 1) {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not read command port number at line %d", line_number);
+  }
+}
+
+/* ================================================== */
+
+#define HOSTNAME_LEN 255
+#define SHOSTNAME_LEN "255"
+
+static void
+parse_initstepslew(const char *line)
+{
+  const char *p;
+  char hostname[HOSTNAME_LEN+1];
+  int n;
+  unsigned long ip_addr;
+
+  do_init_stepslew = 1;
+  n_init_srcs = 0;
+  p = line;
+
+  if (sscanf(p, "%d%n", &init_slew_threshold, &n) == 1) {
+    p += n;
+  } else {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not parse initstepslew threshold at line %d", line_number);
+    init_slew_threshold = -1;
+    return;
+  }
+    
+  while (*p) {
+    if (sscanf(p, "%" SHOSTNAME_LEN "s%n", hostname, &n) == 1) {
+      ip_addr = DNS_Name2IPAddress(hostname);
+      if (ip_addr != DNS_Failed_Address) {
+        init_srcs_ip[n_init_srcs] = ip_addr;
+        ++n_init_srcs;
+      }
+      
+      if (n_init_srcs >= MAX_INIT_SRCS) {
+        return;
+      }
+
+    } else {
+      /* If we get invalid trailing syntax, forget it ... */
+      return;
+    }
+
+    p += n;
+  }
+
+}
+
+/* ================================================== */
+
+static void
+parse_manual(const char *line)
+{
+  enable_manual = 1;
+}
+
+/* ================================================== */
+
+static void
+parse_rtconutc(const char *line)
+{
+  rtc_on_utc = 1;
+}
+
+/* ================================================== */
+
+static void
+parse_noclientlog(const char *line)
+{
+  no_client_log = 1;
+}
+
+/* ================================================== */
+
+static void
+parse_logchange(const char *line)
+{
+  if (sscanf(line, "%lf", &log_change_threshold) == 1) {
+    do_log_change = 1;
+  } else {
+    do_log_change = 0;
+    LOG(LOGS_WARN, LOGF_Configure,
+        "Could not read threshold for logging clock changes at line %d\n",
+        line_number);
+  }
+}
+
+
+/* ================================================== */
+
+#define BUFLEN 127
+#define SBUFLEN "127"
+
+static void
+parse_mailonchange(const char *line)
+{
+  char buffer[BUFLEN+1];
+  if (sscanf(line, "%" SBUFLEN "s%lf", buffer, &mail_change_threshold) == 2) {
+    mail_user_on_change = MallocArray(char, strlen(buffer)+1);
+    strcpy(mail_user_on_change, buffer);
+  } else {
+    mail_user_on_change = NULL;
+    LOG(LOGS_WARN, LOGF_Configure,
+        "Could not read user or threshold for clock change mail notify at line %d\n",
+        line_number);
+  }
+}
+
+/* ================================================== */
+
+static void
+parse_allow_deny(const char *line, AllowDeny *list, int allow)
+{
+  const char *p;
+  unsigned long a, b, c, d, n;
+  int all = 0;
+  AllowDeny *new_node = NULL;
+  unsigned long ip_addr;
+
+  p = line;
+
+  while (*p && isspace((unsigned char)*p)) p++;
+
+  if (!strncmp(p, "all", 3)) {
+    all = 1;
+    p += 3;
+  }
+
+  while (*p && isspace((unsigned char)*p)) p++;
+  if (!*p) {
+    /* Empty line applies to all addresses */
+    new_node = MallocNew(AllowDeny);
+    new_node->allow = allow;
+    new_node->all = all;
+    new_node->ip = 0UL;
+    new_node->subnet_bits = 0;
+  } else {
+    char *slashpos;
+    slashpos = strchr(p, '/');
+    if (slashpos) *slashpos = 0;
+
+    n = sscanf(p, "%lu.%lu.%lu.%lu", &a, &b, &c, &d);
+   
+    if (n >= 1) {
+      new_node = MallocNew(AllowDeny);
+      new_node->allow = allow;
+      new_node->all = all;
+
+      a &= 0xff;
+      b &= 0xff;
+      c &= 0xff;
+      d &= 0xff;
+      
+      switch (n) {
+        case 1:
+          new_node->ip = (a<<24);
+          new_node->subnet_bits = 8;
+          break;
+        case 2:
+          new_node->ip = (a<<24) | (b<<16);
+          new_node->subnet_bits = 16;
+          break;
+        case 3:
+          new_node->ip = (a<<24) | (b<<16) | (c<<8);
+          new_node->subnet_bits = 24;
+          break;
+        case 4:
+          new_node->ip = (a<<24) | (b<<16) | (c<<8) | d;
+          new_node->subnet_bits = 32;
+          break;
+        default:
+          assert(0);
+          
+      }
+      
+      if (slashpos) {
+        int specified_subnet_bits, n;
+        n = sscanf(slashpos+1, "%d", &specified_subnet_bits);
+        if (n == 1) {
+          new_node->subnet_bits = specified_subnet_bits;
+        } else {
+          LOG(LOGS_WARN, LOGF_Configure, "Could not read subnet size at line %d", line_number);
+        }
+      }
+
+    } else {
+      ip_addr = DNS_Name2IPAddress(p);
+      if (ip_addr != DNS_Failed_Address) {
+        new_node = MallocNew(AllowDeny);
+        new_node->allow = allow;
+        new_node->all = all;
+        new_node->ip = ip_addr;
+        new_node->subnet_bits = 32;
+      } else {
+        LOG(LOGS_WARN, LOGF_Configure, "Could not read address at line %d", line_number);
+      }      
+    }
+  }
+  
+  if (new_node) {
+    new_node->prev = list->prev;
+    new_node->next = list;
+    list->prev->next = new_node;
+    list->prev = new_node;
+  }
+
+}
+  
+
+/* ================================================== */
+
+static void
+parse_allow(const char *line)
+{
+  parse_allow_deny(line, &ntp_auth_list, 1);
+}
+
+
+/* ================================================== */
+
+static void
+parse_deny(const char *line)
+{
+  parse_allow_deny(line, &ntp_auth_list, 0);
+}
+
+/* ================================================== */
+
+static void
+parse_cmdallow(const char *line)
+{
+  parse_allow_deny(line, &cmd_auth_list, 1);
+}
+
+
+/* ================================================== */
+
+static void
+parse_cmddeny(const char *line)
+{
+  parse_allow_deny(line, &cmd_auth_list, 0);
+}
+
+/* ================================================== */
+
+static unsigned long
+parse_an_address(const char *line, const char *errmsg)
+{
+  unsigned long a, b, c, d;
+  int n;
+  n = sscanf(line, "%lu.%lu.%lu.%lu", &a, &b, &c, &d);
+  if (n == 4) {
+    return (((a&0xff)<<24) | ((b&0xff)<<16) | 
+            ((c&0xff)<<8) | (d&0xff));
+  } else {
+    LOG(LOGS_WARN, LOGF_Configure, errmsg, line_number);
+    return 0UL;
+  }    
+}
+
+/* ================================================== */
+
+static void
+parse_bindaddress(const char *line)
+{
+  bind_address = parse_an_address(line, "Could not read bind address at line %d\n");
+}
+
+/* ================================================== */
+
+static void
+parse_bindcmdaddress(const char *line)
+{
+  bind_cmd_address = parse_an_address(line, "Could not read bind command address at line %d\n");
+}
+
+/* ================================================== */
+
+static void
+parse_pidfile(const char *line)
+{
+  pidfile = MallocArray(char, 1 + strlen(line));
+  sscanf(line, "%s", pidfile);
+  strip_trailing_spaces(pidfile);
+}  
+
+/* ================================================== */
+
+typedef struct {
+  /* Both in host (not necessarily network) order */
+  unsigned long addr;
+  unsigned short port;
+  int interval;
+} NTP_Broadcast_Destination;
+
+static NTP_Broadcast_Destination *broadcasts = NULL;
+static int max_broadcasts = 0;
+static int n_broadcasts = 0;
+
+/* ================================================== */
+
+static void
+parse_broadcast(const char *line)
+{
+  /* Syntax : broadcast <interval> <broadcast-IP-addr> [<port>] */
+  int port;
+  unsigned int a, b, c, d;
+  int n;
+  int interval;
+  unsigned long addr;
+  
+  n = sscanf(line, "%d %u.%u.%u.%u %d", &interval, &a, &b, &c, &d, &port);
+  if (n < 5) {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not parse broadcast directive at line %d", line_number);
+    return;
+  } else if (n == 5) {
+    /* default port */
+    port = 123;
+  } else if (n > 6) {
+    LOG(LOGS_WARN, LOGF_Configure, "Too many fields in broadcast directive at line %d", line_number);
+  }
+
+  addr = ((unsigned long) a << 24) |
+         ((unsigned long) b << 16) |
+         ((unsigned long) c <<  8) |
+         ((unsigned long) d      );
+    
+  if (max_broadcasts == n_broadcasts) {
+    /* Expand array */
+    max_broadcasts += 8;
+    if (broadcasts) {
+      broadcasts = ReallocArray(NTP_Broadcast_Destination, max_broadcasts, broadcasts);
+    } else {
+      broadcasts = MallocArray(NTP_Broadcast_Destination, max_broadcasts);
+    }
+  }
+
+  broadcasts[n_broadcasts].addr = addr;
+  broadcasts[n_broadcasts].port = port;
+  broadcasts[n_broadcasts].interval = interval;
+  ++n_broadcasts;
+}
+
+/* ================================================== */
+
+static void
+parse_linux_hz(const char *line)
+{
+  if (1 == sscanf(line, "%d", &linux_hz)) {
+    set_linux_hz = 1;
+  } else {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not parse linux_hz directive at line %d", line_number);
+  }
+}
+
+/* ================================================== */
+
+static void
+parse_linux_freq_scale(const char *line)
+{
+  if (1 == sscanf(line, "%lf", &linux_freq_scale)) {
+    set_linux_freq_scale = 1;
+  } else {
+    LOG(LOGS_WARN, LOGF_Configure, "Could not parse linux_freq_scale directive at line %d", line_number);
+  }
+}
+
+/* ================================================== */
+
+void
+CNF_ProcessInitStepSlew(void (*after_hook)(void *), void *anything)
+{
+  if (do_init_stepslew) {
+    ACQ_StartAcquisition(n_init_srcs, init_srcs_ip, init_slew_threshold, after_hook, anything);
+  } else {
+    (after_hook)(anything);
+  }
+}
+
+/* ================================================== */
+
+void
+CNF_AddSources(void) {
+  NTP_Remote_Address server;
+  int i;
+
+  for (i=0; i<n_ntp_sources; i++) {
+    server.ip_addr = ntp_sources[i].ip_addr;
+    server.port = ntp_sources[i].port;
+
+    switch (ntp_sources[i].type) {
+      case SERVER:
+        NSR_AddServer(&server, &ntp_sources[i].params);
+        break;
+
+      case PEER:
+        NSR_AddPeer(&server, &ntp_sources[i].params);
+        break;
+    }
+
+  }
+
+  return;
+
+}
+
+/* ================================================== */
+
+void
+CNF_AddBroadcasts(void)
+{
+  int i;
+  for (i=0; i<n_broadcasts; i++) {
+    BRD_AddDestination(broadcasts[i].addr,
+                       broadcasts[i].port,
+                       broadcasts[i].interval);
+  }
+}
+
+/* ================================================== */
+
+unsigned short
+CNF_GetNTPPort(void)
+{
+  return ntp_port;
+}
+
+/* ================================================== */
+
+unsigned short
+CNF_GetAcquisitionPort(void)
+{
+  return acquisition_port;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetDriftFile(void)
+{
+  return drift_file;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetLogDir(void)
+{
+  return logdir;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetDumpDir(void)
+{
+  return dumpdir;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogMeasurements(void)
+{
+  return do_log_measurements;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogStatistics(void)
+{
+  return do_log_statistics;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogTracking(void)
+{
+  return do_log_tracking;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogRtc(void)
+{
+  return do_log_rtc;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetKeysFile(void)
+{
+  return keys_file;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetRtcFile(void)
+{
+  return rtc_file;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetRtcDevice(void)
+{
+  return rtc_device;
+}
+
+/* ================================================== */
+
+unsigned long
+CNF_GetCommandKey(void)
+{
+  return command_key_id;
+}
+
+/* ================================================== */
+
+int
+CNF_GetDumpOnExit(void)
+{
+  return do_dump_on_exit;
+}
+
+/* ================================================== */
+
+double
+CNF_GetMaxUpdateSkew(void)
+{
+  return max_update_skew;
+}
+
+/* ================================================== */
+
+int
+CNF_GetManualEnabled(void)
+{
+  return enable_manual;
+}
+
+/* ================================================== */
+
+int
+CNF_GetCommandPort(void) {
+  return cmd_port;
+}
+
+/* ================================================== */
+
+int
+CNF_AllowLocalReference(int *stratum)
+{
+  if (enable_local) {
+    *stratum = local_stratum;
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+int
+CNF_GetRTCOnUTC(void)
+{
+  return rtc_on_utc;
+}
+
+/* ================================================== */
+
+void
+CNF_GetLogChange(int *enabled, double *threshold)
+{
+  *enabled = do_log_change;
+  *threshold = log_change_threshold;
+}
+
+/* ================================================== */
+
+void
+CNF_GetMailOnChange(int *enabled, double *threshold, char **user)
+{
+  if (mail_user_on_change) {
+    *enabled = 1;
+    *threshold = mail_change_threshold;
+    *user = mail_user_on_change;
+  } else {
+    *enabled = 0;
+    *threshold = 0.0;
+    *user = NULL;
+  }
+}  
+
+/* ================================================== */
+
+void
+CNF_SetupAccessRestrictions(void)
+{
+  AllowDeny *node;
+  int status;
+
+  for (node = ntp_auth_list.next; node != &ntp_auth_list; node = node->next) {
+    status = NCR_AddAccessRestriction(node->ip, node->subnet_bits, node->allow, node->all);
+    if (!status) {
+      LOG(LOGS_WARN, LOGF_Configure, "Bad subnet for %08lx", node->ip);
+    }
+  }
+
+  for (node = cmd_auth_list.next; node != &cmd_auth_list; node = node->next) {
+    status = CAM_AddAccessRestriction(node->ip, node->subnet_bits, node->allow, node->all);
+    if (!status) {
+      LOG(LOGS_WARN, LOGF_Configure, "Bad subnet for %08lx", node->ip);
+    }
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNoClientLog(void)
+{
+  return no_client_log;
+}
+
+/* ================================================== */
+
+void
+CNF_GetBindAddress(unsigned long *addr)
+{
+  *addr = bind_address;
+}
+
+/* ================================================== */
+
+void
+CNF_GetBindCommandAddress(unsigned long *addr)
+{
+  *addr = bind_cmd_address ? bind_cmd_address : bind_address;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetPidFile(void)
+{
+  return pidfile;
+}
+
+/* ================================================== */
+
+void
+CNF_GetLinuxHz(int *set, int *hz)
+{
+  *set = set_linux_hz;
+  *hz = linux_hz;
+}
+
+/* ================================================== */
+
+void
+CNF_GetLinuxFreqScale(int *set, double *freq_scale)
+{
+  *set = set_linux_freq_scale;
+  *freq_scale = linux_freq_scale ;
+}
+
diff --git a/conf.h b/conf.h
new file mode 100644 (file)
index 0000000..1dfe0d0
--- /dev/null
+++ b/conf.h
@@ -0,0 +1,74 @@
+/*
+  $Header: /cvs/src/chrony/conf.h,v 1.24 2003/03/27 23:45:47 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for configuration module
+  */
+
+#ifndef GOT_CONF_H
+#define GOT_CONF_H
+
+extern char *CNF_GetRtcDevice(void);
+
+extern void CNF_ReadFile(const char *filename);
+
+extern void CNF_AddSources(void);
+extern void CNF_AddBroadcasts(void);
+
+extern void CNF_ProcessInitStepSlew(void (*after_hook)(void *), void *anything);
+
+extern unsigned short CNF_GetAcquisitionPort(void);
+extern unsigned short CNF_GetNTPPort(void);
+extern char *CNF_GetDriftFile(void);
+extern char *CNF_GetLogDir(void);
+extern char *CNF_GetDumpDir(void);
+extern int CNF_GetLogMeasurements(void);
+extern int CNF_GetLogStatistics(void);
+extern int CNF_GetLogTracking(void);
+extern int CNF_GetLogRtc(void);
+extern char *CNF_GetKeysFile(void);
+extern char *CNF_GetRtcFile(void);
+extern unsigned long CNF_GetCommandKey(void);
+extern int CNF_GetDumpOnExit(void);
+extern int CNF_GetManualEnabled(void);
+extern int CNF_GetCommandPort(void);
+extern int CNF_GetRTCOnUTC(void);
+extern void CNF_GetLogChange(int *enabled, double *threshold);
+extern void CNF_GetMailOnChange(int *enabled, double *threshold, char **user);
+extern int CNF_GetNoClientLog(void);
+extern void CNF_GetBindAddress(unsigned long *addr);
+extern void CNF_GetBindCommandAddress(unsigned long *addr);
+extern char *CNF_GetPidFile(void);
+extern void CNF_GetLinuxHz(int *set, int *hz);
+extern void CNF_GetLinuxFreqScale(int *set, double *freq_scale);
+
+/* Value returned in ppm, as read from file */
+extern double CNF_GetMaxUpdateSkew(void);
+extern int CNF_AllowLocalReference(int *stratum);
+
+extern void CNF_SetupAccessRestrictions(void);
+
+#endif /* GOT_CONF_H */
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..5672f9a
--- /dev/null
+++ b/configure
@@ -0,0 +1,313 @@
+#!/bin/sh
+#
+# $Header: /cvs/src/chrony/configure,v 1.28 2003/07/01 20:53:00 richard Exp $
+#
+# =======================================================================
+#
+# chronyd/chronyc - Programs for keeping computer clocks accurate.
+#
+# #COPYRIGHT#
+#
+# =======================================================================
+
+# This configure script determines the operating system type and version
+
+if [ "x${CC}" = "x" ]; then
+  MYCC="gcc"
+else
+  MYCC="${CC}"
+fi
+
+if [ "x${CFLAGS}" = "x" ]; then
+  MYCFLAGS="-O2 -g"
+else
+  MYCFLAGS="${CFLAGS}"
+fi
+
+# ======================================================================
+# FUNCTIONS
+
+#{{{ test_for_sqrt
+test_for_sqrt () {
+# 0 : doesn't need -lm
+# 1 : needs -lm
+# 2 : doesn't even link with -lm
+
+  cat >docheck.c <<EOF;
+#include <math.h>
+int main(int argc, char **argv) {
+  return (int) sqrt((double)argc);
+}
+EOF
+
+  ${MYCC} ${MYCFLAGS} -c -o docheck.o docheck.c >/dev/null 2>&1
+  if [ $? -eq 0 ]
+  then
+    ${MYCC} ${MYCFLAGS} -o docheck docheck.o >/dev/null 2>&1
+    if [ $? -eq 0 ]
+    then
+      result=0
+    else
+      ${MYCC} ${MYCFLAGS} -o docheck docheck.o -lm >/dev/null 2>&1
+      if [ $? -eq 0 ]
+      then
+        result=1
+      else
+        result=2
+      fi
+    fi
+  else
+    result=2
+  fi
+
+  rm -f docheck.c docheck.o docheck
+  echo $result
+}
+#}}}
+#{{{ test_for_stdint_h
+test_for_stdint_h () {
+  cat >docheck.c <<EOF;
+#include <stdint.h>
+int main(int argc, char **argv) {
+  return 0;
+}
+EOF
+
+  ${MYCC} ${MYCFLAGS} -c -o docheck.o docheck.c >/dev/null 2>&1
+  if [ $? -eq 0 ]
+  then
+    result=0
+  else
+    result=1
+  fi
+
+  rm -f docheck.c docheck.o
+  echo $result
+}
+#}}}
+#{{{ test_for_inttypes_h
+test_for_inttypes_h () {
+  cat >docheck.c <<EOF;
+#include <inttypes.h>
+int main(int argc, char **argv) {
+  return 0;
+}
+EOF
+
+  ${MYCC} ${MYCFLAGS} -c -o docheck.o docheck.c >/dev/null 2>&1
+  if [ $? -eq 0 ]
+  then
+    result=0
+  else
+    result=1
+  fi
+
+  rm -f docheck.c docheck.o
+  echo $result
+}
+#}}}
+
+# ======================================================================
+
+
+
+OPERATINGSYSTEM=`uname -s`
+VERSION=`uname -r`
+MACHINE=`uname -m`
+
+SYSTEM=${OPERATINGSYSTEM}-${MACHINE}
+
+EXTRA_LIBS=""
+EXTRA_CLI_LIBS=""
+EXTRA_OBJECTS=""
+EXTRA_DEFS=""
+INSTALL_PREFIX=/usr/local
+SYSDEFS=""
+
+# Support for readline (on by default)
+feat_readline=1
+readline_lib=""
+readline_inc=""
+ncurses_lib=""
+
+SETINFODIR=""
+SETMANDIR=""
+
+for option
+do
+    case "$option" in
+    --prefix=* | --install_prefix=* )
+      INSTALL_PREFIX=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+    --trace )
+      EXTRA_DEFS="-DTRACEON"
+    ;;
+    --disable-readline )
+      feat_readline=0
+    ;;
+    --with-readline-library=* )
+      readline_lib=-L`echo $option | sed -e 's/^.*=//;'`
+    ;;
+    --with-readline-includes=* )
+      readline_inc=-I`echo $option | sed -e 's/^.*=//;'`
+    ;;
+    --with-ncurses-library=* )
+      ncurses_lib=-L`echo $option | sed -e 's/^.*=//;'`
+    ;;
+    --infodir=* )
+      SETINFODIR=`echo $option | sed -e 's/^.*=//;'`
+    ;;
+    --mandir=* )
+      SETMANDIR=`echo $option | sed -e 's/^.*=//;'`
+    ;;
+    * )
+    echo "Unrecognized option : " $option
+    esac
+done
+
+case $SYSTEM in
+    SunOS-sun4* )
+    case $VERSION in
+        4.* )
+            EXTRA_OBJECTS="sys_sunos.o strerror.o"
+            EXTRA_LIBS="-lkvm"
+            SYSDEFS="-DSUNOS"
+            echo "Configuring for SunOS (" $SYSTEM "version" $VERSION ")"
+        ;;
+        5.* )
+            EXTRA_OBJECTS="sys_solaris.o"
+            EXTRA_LIBS="-lsocket -lnsl -lkvm -lelf"
+            EXTRA_CLI_LIBS="-lsocket -lnsl"
+            SYSDEFS="-DSOLARIS"
+            echo "Configuring for Solaris (" $SYSTEM "SunOS version" $VERSION ")"
+            if [ $VERSION = "5.3" ]; then
+                SYSDEFS="$SYSDEFS -DHAS_NO_BZERO"
+                echo "Using memset() instead of bzero()"
+            fi
+        ;;
+    esac
+    ;;
+    Linux* )
+        EXTRA_OBJECTS="sys_linux.o wrap_adjtimex.o rtc_linux.o"
+        SYSDEFS="-DLINUX"
+        echo "Configuring for " $SYSTEM
+        if [ -r /usr/include/linux/spinlock.h ]; then
+            SYSDEFS="$SYSDEFS -DHAS_SPINLOCK_H"
+            echo "The system has <spinlock.h>, using that"
+        else
+            echo "The system does not have <spinlock.h>, using private definition for spinlock_t"
+        fi
+       if [ "${MACHINE}" = "alpha" ]; then
+          echo "Enabling -mieee"
+          # FIXME: Should really test for GCC
+          SYSDEFS="$SYSDEFS -mieee -DALPHA"
+       fi
+    ;;
+
+    BSD/386-i[3456]86 )
+        # Antti Jrvinen <costello@iki.fi> reported that this system can
+        # be supported with the SunOS 4.x driver files.
+        EXTRA_OBJECTS="sys_sunos.o strerror.o"
+        EXTRA_LIBS="-lkvm"
+        SYSDEFS="-DSUNOS"
+        echo "Configuring for BSD/386 (using SunOS driver)"
+    ;;
+    NetBSD-* )
+        EXTRA_OBJECTS="sys_netbsd.o"
+        EXTRA_LIBS="-lkvm"
+        SYSDEFS=""
+        echo "Configuring for $SYSTEM"
+    ;;
+    SunOS-i86pc* )                                          
+        # Doug Woodward <dougw@whistler.com> reported that this configuration
+        # works for Solaris 2.8 / SunOS 5.8 on x86 platforms
+        EXTRA_OBJECTS="sys_solaris.o"                
+        EXTRA_LIBS="-lsocket -lnsl -lkvm -lelf"      
+        EXTRA_CLI_LIBS="-lsocket -lnsl"                              
+        SYSDEFS="-DSOLARIS"                               
+        echo "Configuring for Solaris (" $SYSTEM "SunOS version" $VERSION ")" 
+    ;;                                                                        
+    CYGWIN32_NT-i[3456]86 )
+        EXTRA_OBJECTS="sys_winnt.o"
+        EXTRA_LIBS=""
+        SYSDEFS="-DWINNT"
+        echo "Configuring for Windows NT (Cygwin32)"
+    ;;
+    * )
+        echo "Sorry, I don't know how to build this software on your system."
+        exit 1
+    ;;
+esac
+
+printf "Checking if sqrt() needs -lm : "
+case `test_for_sqrt`
+in
+0)
+  printf "No\n"
+  LIBS=""
+;;
+1)
+  printf "Yes\n"
+  LIBS="-lm"
+;;
+*)
+  printf "\nCan't compile/link a program which uses sqrt(), bailing out\n"
+  exit 1
+;;
+esac
+
+printf "Checking for <stdint.h> : "
+if [ `test_for_stdint_h` -eq 0 ]; then
+  printf "Yes\n"
+  SYSDEFS="${SYSDEFS} -DHAS_STDINT_H"
+else
+  printf "No\n"
+fi
+
+printf "Checking for <inttypes.h> : "
+if [ `test_for_inttypes_h` -eq 0 ]; then
+  printf "Yes\n"
+  SYSDEFS="${SYSDEFS} -DHAS_INTTYPES_H"
+else
+  printf "No\n"
+fi
+
+if [ "x${MYCC}" = "xgcc" ]; then
+  CCWARNFLAGS="-Wmissing-prototypes -Wall"
+else
+  CCWARNFLAGS=""
+fi
+
+if [ $feat_readline = "1" ]; then
+  READLINE_COMPILE="-DFEAT_READLINE=1 $readline_inc"
+  READLINE_LINK="$readline_lib $ncurses_lib -lreadline -lncurses"
+else
+  READLINE_COMPILE=""
+  READLINE_LINK=""
+fi
+
+MANDIR=${INSTALL_PREFIX}/man
+INFODIR=${INSTALL_PREFIX}/info
+if [ "x$SETINFODIR" != "x" ]; then
+  INFODIR=$SETINFODIR
+fi
+if [ "x$SETMANDIR" != "x" ]; then
+  MANDIR=$SETMANDIR
+fi
+
+sed -e "s%@EXTRA_OBJECTS@%${EXTRA_OBJECTS}%;\
+       s%@CC@%${MYCC}%;\
+       s%@CFLAGS@%${MYCFLAGS}%;\
+       s%@CCWARNFLAGS@%${CCWARNFLAGS}%;\
+       s%@LIBS@%${LIBS}%;\
+       s%@EXTRA_LIBS@%${EXTRA_LIBS}%;\
+       s%@SYSDEFS@%${SYSDEFS}%;\
+       s%@EXTRA_DEFS@%${EXTRA_DEFS}%;\
+       s%@EXTRA_CLI_LIBS@%${EXTRA_CLI_LIBS}%;\
+       s%@READLINE_COMPILE@%${READLINE_COMPILE}%;\
+       s%@READLINE_LINK@%${READLINE_LINK}%;\
+       s%@INSTALL_PREFIX@%${INSTALL_PREFIX}%;\
+       s%@MANDIR@%${MANDIR}%;\
+       s%@INFODIR@%${INFODIR}%;"\
+       < Makefile.in > Makefile
+
diff --git a/contrib/DNSchrony/COPYING b/contrib/DNSchrony/COPYING
new file mode 100644 (file)
index 0000000..916d1f0
--- /dev/null
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+       Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/contrib/DNSchrony/DNSchrony.pl b/contrib/DNSchrony/DNSchrony.pl
new file mode 100755 (executable)
index 0000000..4c1cb46
--- /dev/null
@@ -0,0 +1,583 @@
+#!/usr/bin/perl
+#                Copyright (C) Paul Elliott 2002
+my($copyrighttext) =  <<'EOF';
+#                Copyright (C) Paul Elliott 2002
+#   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#   SEE COPYING FOR DETAILS
+EOF
+
+#modules we use.
+
+use Socket;
+use Getopt::Std;
+use Net::DNS;
+use Tie::Syslog;
+use File::Temp qw/ :mktemp  /;
+use File::Copy;
+
+local($res) = new Net::DNS::Resolver;
+
+#dns lookup of IP address.
+#returns ip or errorstring.
+sub gethostaddr($)                                 #get ip address from host
+{
+  my($host) = shift;
+  $query = $res->search($host);
+  if ($query) {
+    foreach $rr ($query->answer) {
+      next unless $rr->type eq "A";
+      print $rr->address, "\n" if  $pedebug;
+      return $rr->address;
+    }
+  } else {
+    print "query failed: ", $res->errorstring, "\n" if $pedebug;
+    return $res->errorstring;
+  }    
+
+}
+
+#send messages to syslog
+
+sub Log($$)
+  {
+    if ($log) {
+      my($level) = shift;
+      my($mess) =shift;
+
+      tie *MYLOG, 'Tie::Syslog',$level,$0,'pid','unix';
+      print MYLOG $mess;
+
+      untie *MYLOG;
+    }
+  }
+
+#send message to output or syslog
+#and die.
+
+sub BadDie($)
+{
+  my($myerr) =$!;
+  my($mess)=shift;
+
+  if($log){
+      tie *MYLOG, 'Tie::Syslog','local0.err',$0,'pid','unix';
+      print MYLOG $mess;
+      print MYLOG $myerr;
+
+      untie *MYLOG;
+
+  } else {
+    print "$mess\n$myerr\n";
+  }
+  die $mess;
+}
+
+sub isIpAddr($)                     #return true if looks like ip address
+{
+  my($ip) = shift;
+  return 1 if ( $ip =~ m/$ipOnlyPAT/ );
+  return 0;
+}
+sub isHostname($)                     #return true if looks like ip address
+{
+  my($ip) = shift;
+  return 1 if ( $ip =~ m/$hostnameOnlyPAT/ );
+  return 0;
+}
+
+#send commands to chronyc by piping.
+sub chronyc($)                              #send commands to chronyc
+{
+  my($command) = shift;
+  my($err) = "/var/tmp/chronyc.log";
+  my($chronyP) = "/usr/local/bin/chronyc";
+  open(CHRONY, "| $chronyP 1>$err 2>&1");
+
+  print CHRONY "$passwd$command\n";
+
+  close(CHRONY);
+
+  Log('local0.info',"chronyc command issued=$command");
+                                   #look at status lines till return bad.
+  open( IN, "<$err");
+  my($status);
+  while (<IN>) {
+  $status = $_;
+
+    unless ( m/\A200 OK/ ) {
+      last;
+    }
+
+  }
+
+  $status ="" if ( $status =~ m/\A200 OK/ );
+  close(IN);
+  unlink $err;
+  Log('local0.info',"chronyc results=$status");
+  return $status;
+
+}
+
+#common patterns
+
+# an ip address patern
+local($ipPAT) = qr/\d{1,3}(?:\.\d{1,3}){3}/;
+# an hostname pattern
+local($hostnamePAT) = qr/\w+(?:\.\w+)*/;
+#line with hostname only
+local($hostnameOnlyPAT) = qr/\A$hostnamePAT\Z/;
+#line with ip address only
+local($ipOnlyPAT) =qr/\A$ipPAT\Z/;
+
+#options hash
+my(%opts);
+
+
+getopts('nuadslPSC', \%opts);
+
+local($log) = ( $opts{'l'} ) ? 1 : 0;
+
+my($offline) = !( $opts{'n'} ) ;
+my($offlineS) = ( $opts{'n'} ) ? " " : " offline" ;
+
+# paul elliotts secret debug var. no one will ever find out about it.
+local($pedebug)=( ($ENV{"PAULELLIOTTDEBUG"}) or ($opts{P}) );
+
+if ($opts{C}) {
+
+  print $copyrighttext;
+  exit 0;
+}
+
+
+print <<"EOF" unless $opts{'S'};
+$0, Copyright (C) 2002 Paul Elliott
+$0 comes with ABSOLUTELY NO WARRANTY; for details
+invoke $0 -C.  This is free software, and you are welcome
+to redistribute it under certain conditions; invoke $0 -C
+for details.
+EOF
+
+
+
+local($passwd);
+
+# password to send to chronyc
+my($pl) = $ENV{"CHRONYPASSWORD"};
+
+#password comand to send to chronyc
+if ( $pl ) {
+  $passwd = "password $pl\n";
+} else {
+  $passwd = "";
+}
+print "passwd=$passwd\n" if ($pedebug);
+
+my(%host2ip);
+
+# hash of arrays. host2ip{$host}[0] is ip address for this host
+# host2ip{$host}[1]   is rest of paramenters for this host exc offline.
+
+#if debuging do chrony.conf in current directory.
+my($listfile) =( ($pedebug) ? "./chrony.conf" : "/etc/chrony.conf") ;
+
+# This section reads in the old data about
+# hostnames IP addresses and server parameters
+# data is stored as it would be in chrony.conf
+# file i.e.:
+#># HOSTNAME
+#>server IPADDR minpoll 5 maxpoll 10 maxdelay 0.4 offline 
+#
+# the parameter offline is omitted if the -n switch is specified.
+# first parameter is the filename of the file usually
+# is /etc/DNSchrony.conf
+# this is where we store the list of DNS hosts.
+# hosts with static IP address shold be kept in chrony.conf
+
+# this is header that marks dnyamic host section
+my($noedithead)=<<'EOF';
+## DNSchrony dynamic dns server section. DO NOT EDIT
+## per entry FORMAT:
+##        |--------------------------------------------|
+##        |#HOSTNAME                                   |
+##        |server IP-ADDRESS extra-params [ offline ]  |
+##        |--------------------------------------------|
+EOF
+#patern that recognizes above.
+my($noeditheadPAT) = 
+qr/\#\#\s+DNSchrony\s+dynamic\s+dns\s+server\s+section\.\s+DO\s+NOT\s+EDIT\s*/;
+
+#end of header marker.
+my($noeditheadend)=<<'EOF';
+## END OF DNSchrony dynamic dns server section.
+EOF
+
+#pattern that matches above.
+my($noeditheadendPAT)=
+qr/\#\#\s+END\s+OF\s+DNSchrony\s+dynamic\s+dns\s+server\s+section.\s*/;
+
+#array to hold non dns portion of chrony.conf
+my(@chronyDconf);
+
+
+my($ip);
+my($rest);
+my($host);
+
+# for each entry in the list of hosts....
+open(READIN, "<$listfile") or  BadDie("Can not open $listfile");
+
+# read till dynamic patern read save in @chronyDconf
+
+while ( <READIN> ) {
+  
+  my($line) = $_;
+
+  last if ( m/\A$noeditheadPAT\Z/ );
+
+  push(@chronyDconf,$line);
+
+}
+
+while ( <READIN> ) {
+   #end loop when end of header encountered
+   last if ( m/\A$noeditheadendPAT/ );
+
+   # parse the line giving ip address, extra pamamters, and host
+   #do host comment line first
+   ($host) = m{
+              \A\#\s*
+              ($hostnamePAT)
+               \s*\z
+              }xio;
+
+  #no match skip this line.
+  next unless (  $host );
+
+  # read next line
+  $_ = <READIN>;
+
+  # parse out ip address extra parameters.
+  ($ip,$rest) =
+  m{
+    \A
+    \s*
+    server                                       #server comand
+    \s+
+    ($ipPAT)                                     #ip address
+    (?ixo: \s )
+    \s*
+    (
+    (?(?!
+            (?iox: offline )?                    #skip to offline #
+            \s*                                  #or #
+            \Z
+    ).)*
+    )
+    (?ixo:
+     \s*
+     (?ixo: offline )?                          #consume to #
+     \s*
+     \Z
+    )
+    }xio ;
+
+  #if failure again.
+  next unless (  $ip );
+
+  $rest =~ s/\s*\z//;                          #remove trail blanks
+                                               #from parameters
+  # store the data in the list
+  # key is host name value is
+  # array [0] is ip address
+  #       [1] is other parameters
+  $host2ip{$host} = [$ip,$rest] ;
+  print "ip=$ip rest=$rest host=$host<\n" if $pedebug;
+
+}
+#read trailing line into @chronyDconf
+while ( <READIN> ) {
+
+  push(@chronyDconf,$_);
+
+}
+
+close(READIN) or BadDie("can not close $listfile");
+
+#if the add command:
+# command can be HOST=IPADDRESS OTHER_PARAMETERS
+# means add the server trust the ip address geven with out a dns lookup
+# good for when dns is down but we know the ip addres
+# or
+#    HOST OTHER_PARAMETERS
+#we lookup the ip address with dns.
+
+if ($opts{'a'}) {
+  my($param)= shift;
+
+
+  # parse the param is it hostname
+  if ( ($host,$ip) = $param =~ m/\A($hostnamePAT)=($ipPAT)\Z/ ) {
+    printf "ip=$ip host=$host\n" if ($pedebug);
+  } else {
+
+    $host = $param;
+
+    # get the ip address
+    $ip = gethostaddr($host);
+
+    if ( ! isIpAddr($ip) or ! isHostname($host) ) {
+      print "query failed: ", $ip, "host=$host\n" if $pedebug;
+      exit 1;
+    } 
+  }
+  printf "ip=$ip host=$host\n" if ($pedebug);
+
+  # add the server using chronyc
+  my($status) = chronyc("add server $ip $rest");
+  if ($status) {               #chronyc error
+    print "chronyc failed, status=$status\n";
+    exit 1;
+  }
+
+  # get rest of arguements
+  $rest = join( ' ', @ARGV);
+  print "rest=$rest\n" if ($pedebug);
+
+  #save node in hash
+  $host2ip{$host} = [$ip,$rest] ;
+  print "ip=$ip rest=$rest host=$host<\n" if $pedebug;
+
+}
+
+#delete command if arguement is ip address
+#just delete it
+#if a hostname look it up
+#then delete it.
+
+if ($opts{'d'}) {
+  $host = shift;
+
+  #get host name is it ap address
+  if ( isIpAddr($host) ) {                           # if ip address
+    my($hostIT);
+    my($found) =0;
+    foreach $hostIT (keys(%host2ip) ) {               #search for match
+      if ( $host2ip{$hostIT}[0] eq $host) {
+       $found=1;                                     #record match
+      }
+    }                                                 #end of search
+    if ($found) {                                     #if match found
+      my($status) = chronyc("delete $host");          #chronyc
+      if ($status) {                                  #chronyc error
+       print "chronyc failed, status=$status\n";
+       exit 1;
+      } else {                                        #reiterate
+       foreach $hostIT (keys(%host2ip) ) {
+         if ( $host2ip{$hostIT}[0] eq $host) {
+           delete $host2ip{$hostIT};                 #deleting match hosts
+         }
+       }
+
+      }
+
+    }
+  } else {                                          #else not ip address
+                                                    #must be hostname
+    if ( ! $host2ip{$host} ) {
+      print "No such host as $host listed\n";
+      exit 1;
+    }
+                                                    #get ip address
+    $ip=gethostaddr($host);
+    if ( ! isIpAddr($ip) ) {                        #no ip address
+      print "query failed: ", $ip, "\n" if $pedebug;
+      exit 1;
+    } 
+
+    printf "ip=$ip host=$host\n" if ($pedebug);
+
+    my($listed_host_ip) = $host2ip{$host}[0];       # get the ip address saved
+
+    if ( $ip ne $listed_host_ip) {
+      print 
+       "Info: listed host ip=>$listed_host_ip".
+        "< is different from DNS ip=>$ip<\n";
+      $ip = $listed_host_ip;
+    }
+
+    # delete the server
+    my($status) = chronyc("delete $listed_host_ip\n");
+
+    if ($status) {
+      print "chronyc failed, status=$status\n";
+      exit 1;
+    }
+    #delete table entry
+    delete$host2ip{$host};
+  }
+
+}
+
+#update for each host who's dns ip address has changed
+#delete the old server and add the new. update the record.
+if ($opts{'u'}) {
+  my($command);
+
+  my(%prospective);                        # store new IP address we
+                                           #are thinking of changing.
+
+  Log('local0.info',
+      "Now searching for modified DNS entries.");
+
+  foreach $host (keys(%host2ip)) {         #for each listed host
+    my($old_ip) = $host2ip{$host}[0];      #get old ip
+    $rest       = $host2ip{$host}[1];      #extra params
+
+    $ip         = gethostaddr($host);      #get new ip from dns
+                                           #if error
+    if ( ! isIpAddr($ip) or ! isHostname($host) ) {
+      print "query failed: ", $ip, "host=$host\n";
+
+      Log('local0.err',"query failed: ". $ip . "host=$host");
+      
+      exit 1;
+    } 
+
+    next if($ip eq $old_ip);                #if ip not changed, skip
+
+    Log('local0.info',"Ip address for $host has changed. Old IP address=".
+                      "$old_ip, new IP address=$ip");
+    # add command to delete old host, add the new.
+    $command = $command . "delete $old_ip\n" .
+               "add server $ip $rest\n";
+
+    # we are now thinking about changing this host ip
+    $prospective{$host} = [$ip,$rest];
+  }
+  # submit all the accumulated chronyc commands if any.
+  if ($command) {
+    $status = chronyc($command);
+    if ($status) {
+      print "chronyc failed, status=$status\n";
+      Log('local0.err',"query failed: ". $ip . "host=$host");
+      exit 1;
+    }
+  } else {                                  #if no commands exit
+    exit 0;                                 #because no rewrite of file needed
+  }
+
+  #copy prospective modifications back into main table.
+  #we now know that all these mods were done with chronyc
+  foreach $host (keys(%prospective)) {
+    my($ip) = $prospective{$host}[0];
+    $rest       = $prospective{$host}[1];
+    $host2ip{$host} = [$ip,$rest];
+  }
+}
+
+#starting for each entry we have read in from the old list
+# add the server in chronyc
+# this option is seldom used.
+
+if ($opts{'s'}) {
+  my($command)="";
+
+  foreach $host (keys(%host2ip)) {
+    $command = $command . "add server $host2ip{$host}[0] ".
+                          "$host2ip{$host}[1]\n";
+  }
+  my($status) = chronyc($command);
+  if ($status) {
+    print "chronyc failed, status=$status\n";
+    exit 1;
+  }
+
+}
+# write out the data file in format
+#># HOSTNAME
+#>server IPADDRESS extra parameters [offline] 
+# offline is omitted if -n switch is specified.
+
+my(@value);
+my($such);
+{
+  # to start out we write to temporary file.
+  (my($writeout) , my($outname)) = mkstemp( "${listfile}.outXXXXXXX");
+
+  $outname or BadDie("can not open for $listfile");
+
+
+  # save the chrony.conf part!
+  # and write the DYNAMIC header
+  print $writeout @chronyDconf, $noedithead;
+
+
+  # for each entry
+  foreach $host (keys(%host2ip) ){
+
+    #write the record
+
+    # write the comment that indicates the hostname
+    # and the server command.
+    print $writeout 
+     "\# $host\nserver $host2ip{$host}[0] $host2ip{$host}[1]${offlineS}\n" ;
+
+    print 
+     "server $host2ip{$host}[0] $host2ip{$host}[1]${offlineS}\# $host\n" 
+     if $pedebug;
+
+  }
+
+  #WRITE THE end of dnyamic marker comment
+  print $writeout $noeditheadend;
+
+  # close the output file which was a temporary file.
+  close($writeout) or BadDie("can not close $outname");
+
+  # we now begin a intracate dance to make the the temporary
+  # the main chrony.conf
+  #
+  # if there is a chrony.conf.BAK save it to a temporary.
+  # rename chrony.conf to chrony.conf.BAK
+  # rename the temporary to chrony.conf
+  # if there already was a chrony.conf.BAK, unlink the copy of this.
+
+  my($backname) = "$listfile\.BAK";
+  my($backplain)  = ( -f $backname );
+  my($saveback);
+  #if chrony.conf.BAK exists rename to a temporary.
+  if ($backplain ) {
+
+    $saveback = mktemp("${backname}.bakXXXXXXX");
+    move($backname,$saveback) or 
+         BadDie "unable to move $backname to $savename";
+
+  }
+
+  # rename old chrony.conf to chrony.conf.BAK
+  move($listfile,$backname) or
+       BadDie "unable to move $listfile to $backname";
+
+  # rename our output to chrony.conf
+  move($outname,$listfile) or
+       BadDie "unable to move $outname to $listfile";
+
+  #if there was a temporary chrony.conf.BAK that we saved to temp
+  #unlink it
+  unlink($saveback) or BadDie "unable to unlink $saveback" if($backplain);
+  
+}
diff --git a/contrib/DNSchrony/DNSchronyADD b/contrib/DNSchrony/DNSchronyADD
new file mode 100755 (executable)
index 0000000..fc9858b
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/bash
+
+# $1 is chrony password.
+# $2 is hostname to add or hostname=ipaddres
+# $3-$9 is rest of extra server parameters
+
+FIRST="$1"
+HOST="$2"
+shift 2
+
+#remaining parameters a the other paramaters to server command
+#excluding "offline"
+ARGS="$*"
+
+#if none use default taken from chrony documentation.
+DEF="minpoll 5 maxpoll 10 maxdelay 0.4"
+
+DARGS=${ARGS:-$DEF}
+
+CHRONYPASSWORD=$FIRST \
+/usr/local/bin/DNSchrony.pl -a  "$HOST" "$DARGS"
diff --git a/contrib/DNSchrony/DNSchronyDELETE b/contrib/DNSchrony/DNSchronyDELETE
new file mode 100755 (executable)
index 0000000..e443c9b
--- /dev/null
@@ -0,0 +1,7 @@
+#!/usr/bin/bash
+
+# $1 is chrony password.
+# $2 host to be deleted if ip nn.n.n.n then no DNS used
+
+CHRONYPASSWORD=$1 \
+/usr/local/bin/DNSchrony.pl -d  $2
diff --git a/contrib/DNSchrony/DNSchronyUPDATE b/contrib/DNSchrony/DNSchronyUPDATE
new file mode 100755 (executable)
index 0000000..14cbceb
--- /dev/null
@@ -0,0 +1,7 @@
+#!/usr/bin/bash
+
+# $1 is chrony password.
+
+
+CHRONYPASSWORD=$1 \
+/usr/local/bin/DNSchrony.pl -ulS
diff --git a/contrib/DNSchrony/README b/contrib/DNSchrony/README
new file mode 100644 (file)
index 0000000..8c35348
--- /dev/null
@@ -0,0 +1,166 @@
+                Copyright (C) Paul Elliott 2002
+
+
+DNSchrony.pl version -2.0
+
+Problem: If you look at the list of secondary NTP servers:
+http://www.eecis.udel.edu/~mills/ntp/clock2.htm
+
+you will find statements like this:
+
+"Note: IP addresses are subject to change; please use DNS"
+
+These servers represent a problem for chrony. Chrony is a program
+designed to work on hosts with an intermittent connection to the
+internet. Often no DNS is available when chrony starts. As chrony
+is currently designed, chronyd never sees a DNS host name. If a
+user specifies one when using chronyc's "add server" command, the
+DNS lookup is done by chronyc and an IP address is passed to chronyd.
+
+One can imagine I suppose, a redesign to chrony in which chronyd
+keeps track of DNS changes. But this has problems, all the time
+chronyd is fooling around with DNS, it would not be keeping track
+of its prime function, what the clocks and NTP servers are saying.
+This could result in poorer performance. Or perhaps you say that
+chronyd should be multi threaded. One thread to fool with DNS
+and another to keep track of time. But this introduces a great
+deal of complexity, and complexity is the enemy of elegant robust
+code. Besides, Richard probably has better things to do.
+
+I have attempted to address this problem with a humble perl script,
+which I now release under the GPL: DNSchrony.pl
+
+PLEA FOR HELP FROM EXPERIENCED PERL HACKERS.
+
+Please go thru the code and find errors and improvements.
+I am not quite an polished perl hacker. Please fix bugs and
+make improvements. It needs better documentation. Someone
+who knows how, put in some POD.
+
+END OF PLEA
+
+Philosophy of DNSchrony.pl: keep a list of servers that use
+DNS. From time to time, hopefully when DNS is up, go thru
+the list lookup all the hostnames and see if any ip addresses have
+changed. If any have changed, update our list and do chronyc
+"delete" and "add server" commands so that chronyd now talks to
+the right NTP server.
+
+Additional nuance: keep the list in /etc/chrony.conf in the
+form of comments starting with "#" and "server" commands
+legal in a chrony.conf file. Format of a list entry:
+
+# hostname
+server IP-ADDRESS extra server parameters
+
+These entries are delimited by special comments that allow
+DNSchrony.pl to find them and also tell humans not to mess with them.
+
+Example of such a section of a chrony.conf file:
+
+dumpdir /var/log/chrony
+rtcfile /etc/chrony.rtc
+
+## DNSchrony dynamic dns server section. DO NOT EDIT
+## per entry FORMAT:
+##        |--------------------------------------------|
+##        |#HOSTNAME                                   |
+##        |server IP-ADDRESS extra-params [ offline ]  |
+##        |--------------------------------------------|
+# tock.greyware.com
+server 208.14.208.44 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+# tick.greyware.com
+server 208.14.208.19 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+# ntppub.tamu.edu
+server 128.194.254.9 minpoll 5 maxpoll 10 maxdelay 0.4 offline
+## END OF DNSchrony dynamic dns server section.
+
+This allows the list of dynamic DNS servers to be preserved
+when chronyd is stoped/started.
+
+All servers that do not have ip addresses subject to change
+should be put in the regular part of chrony.conf as described
+in the chrony documentation.
+
+Security philosophy: DNSchrony does no security checking but
+relies on other security factors.
+
+Users without the privilege to modify /etc/chrony.conf and the
+directory /etc will be unable to use DNSchrony to do so, because
+of file protections. DNSchrony passes thru passwords to chronyc.
+Users that do not know the correct chronyc  password will be
+unable to get chronyd do do anything. Thus, DNSchrony passes
+the buck to these other security features.
+
+INSTALLATION:
+
+copy the files:  DNSchronyADD DNSchronyUPDATE DNSchronyDELETE DNSchrony.pl
+to /usr/local/bin. Backup the file /etc/chrony.conf leave hosts
+with static ip addresses in this file.
+
+DNSchrony uses the following perl modules. See that they are installed.
+Get them from CPAN if needed.
+
+Net::DNS, Tie::Syslog, Getopt::Std, Socket, File.
+
+Cause DNSchronyUPDATE bash script to run from time to time when DNS
+is working. If you have a dialup, one way to do this would be to
+modify your /etc/ppp/ip-up.local file as follows:
+
+cat <<EOF | /usr/local/bin/chronyc
+password mysecret
+online
+EOF
+# update all of the dynamic servers and save the result.
+# do not wait for response
+
+nohup /usr/local/bin/DNSchronyUPDATE mysecret >/dev/null 2>&1 &
+
+Since this file contains the chronyc password you will want to set the
+file permissions so that just everybody will not be able to read
+it. But you already did that when you put in the chronyc command.  Any
+other way to make DNSchronyUPDATE run perodicly when DNS is up will
+also work.
+
+To add a server with a varying IP address one could run:
+/usr/local/bin/DNSchronyADD mysecret tock.greyware.com
+
+or if you want to specify different server parameters you
+could say:
+
+/usr/local/bin/DNSchronyADD mysecret tock.greyware.com "minpoll 10 maxpoll 20 maxdelay 0.8"
+
+The DNSchronyADD's default for these parameters is:
+"minpoll 5 maxpoll 10 maxdelay 0.4" values that are often shown
+as examples in the chrony documentation.
+
+If DNS is not running now but you know the IP address, you can say:
+/usr/local/bin/DNSchronyADD mysecret tock.greyware.com=208.14.208.44
+
+Of course, the IP address will be checked next time DNSchronyUPDATE
+runs.
+
+To delete dynamic DNS a server:
+/usr/local/bin/DNSchronyDELETE mysecret tock.greyware.com
+
+To change parameters delete and re-add.
+
+Of course, in all of the above "mysecret" is your chronyc password
+which SHOULD NOT BE "mysecret".
+----------------------------------------------
+DNSchrony.pl is covered by the GPL
+#                Copyright (C) Paul Elliott 2002
+#   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#   SEE COPYING FOR DETAILS
\ No newline at end of file
diff --git a/contrib/DNSchrony/ip-up.local b/contrib/DNSchrony/ip-up.local
new file mode 100644 (file)
index 0000000..34f6e76
--- /dev/null
@@ -0,0 +1,22 @@
+#example file /etc/ppp/ip-up.local
+#originally from SuSE distribution
+#modified for chrony
+cat <<EOF | /usr/local/bin/chronyc
+password mysecret
+online
+EOF
+# update all of the dynamic servers and save the result.
+# do not wait for response
+
+nohup /usr/local/bin/DNSchronyUPDATE mysecret >/dev/null 2>&1 &
+#other stuff who knows?
+
+# The following lines added for Linux-HA support             # Heartbeat
+DEVFILE=`echo $DEVICE | sed -e 's!^/dev/!!' -e 's!/!.!g'` # Heartbeat
+OUTFILE=/var/run/ppp.d/$DEVFILE                             # Heartbeat
+(                    # Heartbeat
+echo "$IPREMOTE"    # Heartbeat
+echo "$IFNAME"      # Heartbeat
+echo "$PPPD_PID"    # Heartbeat
+echo "$IPLOCAL"     # Heartbeat
+) > $OUTFILE        # Heartbeat
diff --git a/contrib/andrew_bishop_1 b/contrib/andrew_bishop_1
new file mode 100644 (file)
index 0000000..4c0b437
--- /dev/null
@@ -0,0 +1,114 @@
+From amb@gedanken.demon.co.uk Tue Aug 17 22:14:00 1999
+Date: Fri, 6 Aug 1999 19:00:24 +0100
+From: Andrew M. Bishop <amb@gedanken.demon.co.uk>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: Re: Chrony and laptop configuration
+
+Hi,
+
+Attached is the apmd_proxy script from the apmd-3.0beta9 distribution.
+
+The changes that I would make are the following:
+
+Replace the update_clock function (line 122) with 
+
+update_clock () {
+
+chronyd -f /etc/chrony.conf
+
+}
+
+Around line 171 (in the suspend actions section) I would kill chronyd.
+
+begin 644 apmd_proxy.gz
+M'XL("+L@JS<``V%P;61?<')O>'D`I5K[;]M&$OY9^BLV2JZQ6TF.`Q2XUDA1
+MQU8<]>('_&A[.!R,-;D2MR&Y+)?TXZ[WO]\W,\N'%-E)[X08CJ2=V7G/-T,_
+M?[9S8_,=GPR?*_S3119?%Z6[?U`3A=_+4F<JMK[059284BU<J?;/CE6L3>9R
+M$!R4VB[5L2X_WNDRKM161!]D/Z:)T9FVZ73I%]$TUUY/E^YV6[W>Q>$'M?O=
+M=]^!^E#?VEB]+=U=;M)4;<4W/Q;.5]-$E[=@-S5QO:V^4S_5N0DD(+I,K%<^
+M(0(?E;:H%-Y'.DU-K&X>5)68GHAJBU3:5G>)R?DK7^G*@(M;*)T_J,+=0:U,
+MYWII,I-7:E'G465!F&AP372^-/%4X5*CS+V.*F5N<<R#EZ[`IBKM<@D.Q)ID
+ML/F267=VC$UA\E@11W?'7Z@[8NWRA5W6I8G)[!7>9X5-C:IL9J:LYR^V2BP)
+M3>J*HJS`@Z],IG2<V=SZJM05?.(35Z>Q*NJ*M0(SJ!1[TK-4FA7RL(&-DN;H
+MC5&%*>'/#&:K"XC'EH%".O>6"42,GBJ=G>\@F_*%B>S"1DJ7RSIKK0*-25Q<
+M0.*RN<A0]`W9U$51799LU#F[KJ?@2Z]*XR&,MS<VM16\Z=3OM2G9K>""P(@1
+M:(;4:ASM37EK(Z.V;JU6.Y`TVH',VQRKF<-9FY.:FG0:DT+P!MA6^J-(J`O0
+M%*4E[<54HO@[T,/E69&:<?";1<R1!=2H9Q5?>_:P.&:D?JM]!?/B3L-ZMRYC
+M-1/$-%D0$BP=1,/OAD'F8A-";<6[X)(A9^ZL3X@LN$W!2V;2^2`X.;50J\Y3
+MIV.*Q;BTMZ:$U8D+$EG?<(B25+FI[ESYD60`1QW1W;^T:=**#(?4%",I[%..
+M0T`\8@HYJW(R=SH:4UQ+T%%4@Z_SC8DI-+M(+`T)B9A@P\\7+,)'4Z(J2"ZS
+M%B0-G:/LW)WNOJ(@2.R2ZM)6[<69),T(/$(&D+T6EM*OKK;'_5B.=(YKJ[K,
+M$1#*E"6817``AXT$6^,7XH/DR&-4%\G^,8P6VP@A!5NBM%1)*`%TGCXC5ZYI
+M^)N)*H[Z3^(J2$0^CN%-R&`78W6C81`E1?;LBG1M'$86O*7DP&?0&W;)$2FU
+MV!4L6\$10A6%-=7%$(IDS;?STXM-HK'Q\7KV3)V<7L[HMY)JBW^YJT*VM`+7
+M(B"41<`?FAL+0TK<^&>!U_GL\NK\1/V\_^%J]KUP5Z_06G*'^-9I\,#>$Z96
+ME.TZBDQ1<:FDURXXB-0]9:D*!LJMXZN+2_5^_^<9ZWMX/O]Y=BXA<WJNWL^/
+MWL_.MUG"]X:XEV:E?GN#DI-'QG,L=`I_OUX/<5]9J977I-][J-SQ&2GR?4)7
+MJ"<(N<'5%8=7C.:X1KU2<'K4X0.ZMB0=/+F^7UTV\T'IK1#,Z3J?$..P0WNB
+M(=GZ>OL34[#IOU"F<)ID4IN%XL!N3'-%;[Y8K<#\LQQZ0JQR"&6LN4(X7/05
+MD9*X*%WVA"`-FW#/9]A\3IJ>EYY@L^ZI55:"9U!:JHIZZB=>*DWA*%I5"J@2
+M3FWD(+!)/<%A_V"GN49H-O+AOO`DG]R0:E23Z6A=Q-2F/PF_P"[2A0[`X1%V
+M`KNF#<'6-U('P'`U`ZD8HS;"&^BNTJZYVZ%.H-.T%8)(OV'26X!=-%>JW1LZ
+MEZ\+$@"5%!`Y?(:?Z5H?$V$NYL=G'^;OYK-#=7!Z\FY^='6^?SD_/5$JX%_`
+M'X``!C0$-OL(D=H:Y(::5>D8JAG+W8ERTTA%D>ZVP-?NCMX#:EL2W?>A4/HP
+M)KFH#L>V(6L!=^5-NI"&<6$J=75YP)"JK%G_!U>CF:8N^DB5K&ED<HK@;>@J
+M)`]Q!A.\<[!':>,8``27$HH-\L)HN=HQ5;0#9XK_=ICY=`B.;Q8Z]::5Y.+J
+MXFQV<GA]>G*]SS+QUT&H%D+I6V?C34T'7$+;X8&!%<ET!!QN&/VZ/.=>24S`
+MG],`ZKRESKW0=5IUL,&S+QCMDO*-&T).T!S5T`]7A'Y#5F1]S@Z.#^;[8%,&
+M9AR0`G[A+;@+H)MCCE3*)=Z#3JS%5,`4:9[H6S0ZYB50'/RHI=^T90Y*D?--
+MF:*!^AJ@#;5%JXN#BSGA*1`"0%5D%<1PAQY&`3V,`N15AN`DM]$^K`UQ*[I0
+MPZ7+B^3!4['"C8'+GD)8PTYC05-<96A$`$C@'NH6"PZT!#E-<)\A'S&-JE1X
+MC!KH-V:_,H!EP,0XB`<=\)&:.1V*B:]G/\T.+LG\P1-=4-&0.GM[=70T/SGB
+M=U<Y\:=)L1FR@+9CB!D<?#R[?']Z"(CBVO^_YOH5FYMZN91PS^`]#)P^Z-)4
+MJ=2A,JD36*8T<DL<BM_2Y$CXE'K:&,`?I8>2IN(RU6@GT0J,_U&B!&[.N7RV
+M`ZP'5W((\*H3"`\NN%1196O5(;,)'F[%1*US-?E51NRN\$H):'6>J`]NZ5OP
+MG5+:=,/AZESL\O1!KJ?YN??%BZ^I&W1L7S=LP<1"K[+.UUC!BCM55NSTY`);
+MRN4H<>KEUVNOE^J''YXB&+UX!2%&?73V*`&WHPVOIV]X.5E[/272O8DV7/#Z
+M40*4336Y9\?L4\5");#4K*@0)ZK$)!2J&<(HK6,I9IZ6$_33#MQ->2*.E:[J
+ML`Y8-^;_\!H.A\SY&L+1\&ZVMM6_AP,IX//#&4_Y$LB^``@%``X#I".\GZ"1
+M\GLB.>215L(RE&#(.=CQM-5*XD)C4)[\KB87:O>O:B<VM_A0#P:@Q/LW;]!_
+MOU4816J@W.&`*N_>\#^->&'0UK!=(V&X[A$I_YQ`KUIY>C<_Y]'K>]4-:LT$
+MCR88<,7KZ6M.4I[(I;(CU6/>*J"O#@4D74L#;B0/R"FP@"4S6NEU"Q4^/1R@
+M4?Y#318;^ZWZYQXW@.&``G"Z\8Q\AT*=4D]]2(U\P&Q'+PX^G![\[?CT<#92
+M;]31\67'<4"MG#L?GU_8X8!^F.X%`8<WH9.OR/#NP_[1F]%H.#!4KGL?3>J1
+M,(`R]TJ,'W107WT5L,G$JQ=T7/WQATKN5C\C7Q"DX`D/U2;3L#_MEDQ<EX1@
+MYDWRD/'".,\ESCM$:F9I&Q?2I[==!(>%I2%S')8QJX&",HVX+B<"F:0K]!%%
+M#ZFTB*`9W\G&=S+,HL=UX.+Y<!B!':R_.T(0#8?DGO7Z$UX-?.*++B[W3P[?
+M_OV1L\SF/8>=BFH,M)G]%REC$(_6=9OB9JYAT?O@Q#,'M$RRYJ%I-B<NH"Y1
+M?LS@I;_]"HB'K>P6S*-*6@^T:S#&.`)QIZ3R*-P]^F,4)!IM#RDO]ION*2C:
+M+-E/:W@Q^"HLMMK9:O`)6OS$_"W4%;[4]!0M<9#/"Z)G7;NE#'MYM+Y1;#8A
+M"\IW>-W&*1<_%"CKJ+GOI]X%4\GRD9ID3N;4Z@.JVWUO)"'";@+9N$RC#LN+
+MM)7UV12434:NPNPF-R=:O7B-=P'.=,D-(I=?Z^A:$-T/7/CR.DW;$P-S#SBS
+M2^X\E\W.%N^@@MQA?FK5DRB(MT$92D4CV2[='V*N$X"YOY*2P%G=WS!5#T5`
+MH;%C7+ITJM"^DI`HG,VKL;JI&3,3>0B.!@?"-K(9N\$(PY@'K?)/AU8`^V&9
+M[:5DTU9^)6<"D`N'@QO#,HTBBR#=H+>YY"4M0>X`S@D+)S+,K:EFR@HECHC;
+M>>.)^0!?06J,B#0>"`7'!TGG&YC?.`LW1V91DX#>+2IJ-Y-N'2H#"NO;0FN6
+MCK:Q8>)@*4H!``CUE9.<"9$KY<%!+`L\,;4`\_/9Q=7Q##<=A/U>,S#T+#7B
+M%2=C9:Y<88V)O*:5=3`W!IF:I^22[]3MR$\&)&,ANR::U.M=1B"]1>..8)AE
+MY])Y--\,=NDE5M>LPDP30EA)$B&^'YE8$/4\?7<A/UAEQ`:G3.!.N?YM6]&Z
+M=.J/.AL"&#-)J8O$1EZ&RM@961++:,5YDG=G>-/7Q8\UM$)7OR)B"3DA>$*A
+ME/K-+N4-C6_6#H8>3#@,?655:YI6<N^H(M?9#7R\.PX/&L2XM:\Y'S`@FONJ
+M.8P[KBBE1,)Q2$.D".^8*M?="J@,+(#K6-%?V]O0R9FQ>.RY:GU6^Y*?HT;)
+M;=7YX+D:K'ZSNZ=\:DRA=NG+8&;'<R.9&K*%_@4`*,]PX%57TVZ&^EV`@D0T
+M^W7_^.P#@.+I!=H&AK.8NR0Y(/AR)^P-&Z,3E:Q"`^.D9<A=6)Z+4:R5A@_4
+MG:T"K5)GF,[YHEM=/M#BHTO,T)A'+(S+:1_`S7?P7(P`>*53>=C,)Q;H?WM[
+M3\*1%I9("C]U;"B=@S6>3J>K.U6**^#JLS%@`CU(D&T_XSV!Q5-""*(J8`&5
+ML;8ET%%IH:&K"1V%#(_OC,`P<D'1/N[^?`/H%B<K>Q[N(DTI;8!A;]]G^=9&
+MV"[L>;%M2ZE?7-D^4\+6*E:ON7,+#QT$+?VQFO1_ER1+T]_C-:D)O7Y)>B17
+M0L1^+E?"L2YEPJ'I>F`_&K3YE\<LX//YY9.GF,U!>*!.B<=/5A>V1-ODQT9^
+MRD?F?6#?+!`9287V2!]E%-8(C&:F'C7XG[`O,4-@*_4%2.R3V;>9K@:K0[N`
+MJ2^WQNG9TX<V6X/"W92$;_@))9TY##F1,3Y8.8JB,@X9<D//_=6HI\<H;"+9
+M(*Z@1-^@Z]Z>>EHC=?!^_^1H1MSAXLNKBT=/TDW23^@N'L*06S;,KB.^G+ZA
+M9+VS]`<VU(5VN'0U`\18Z1B@!,4\0UV(&_>CDE71EP'K#3JV";?N478I^50D
+MI"5+$%`FGU0ZC/P1"#Y?J7@]PB!EH&U"ELIPI4;H"B.5`L^F:DLV'&9!H!EV
+M5&:ZG-+>^=M7?Q%:P-"7%>'RC#;2\D<[%2/8VA.F[-_:/?T)%PLWLA_J]5VW
+>Z]AB9"M2/=L.+(S7D<0S_V\81H;_`M>*^#$A)0``
+`
+end
+
+-- 
+Andrew.
+----------------------------------------------------------------------
+Andrew M. Bishop                             amb@gedanken.demon.co.uk
+                                      http://www.gedanken.demon.co.uk/
+
diff --git a/contrib/andrew_bishop_2 b/contrib/andrew_bishop_2
new file mode 100644 (file)
index 0000000..d3ede74
--- /dev/null
@@ -0,0 +1,95 @@
+From amb@gedanken.demon.co.uk Wed Sep  1 22:26:59 1999
+Date: Thu, 19 Aug 1999 17:30:14 +0100
+From: Andrew M. Bishop <amb@gedanken.demon.co.uk>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: [amb@gedanken.demon.co.uk: Chrony and laptop configuration]
+
+Hi,
+
+What you need to do is replace 10.0.0.0 with the network of the
+freeserve nameservers in the two scripts below.
+
+Other than that you can use it as is.
+
+------- Start of forwarded message -------
+From: "Andrew M. Bishop" <amb@gedanken.demon.co.uk>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: Chrony and laptop configuration
+Date: Sat, 31 Jul 1999 11:02:04 +0100
+
+Attached are the ip-up and ip-down files that I use for chrony.
+(Actually because of the way that debian works they are separate file
+in the /etc/ppp/ip-up.d directory that are run in a SysV init style).
+
+They rely on the presence of an 'ipparam demon' or 'ipparam freeserve'
+line in the PPP options file.
+
+-------------------- /etc/ppp/ip-up --------------------
+#!/bin/sh -f
+#
+# A script to start chrony
+#
+
+PPP_IPPARAM="$6"
+
+if [ $PPP_IPPARAM = "demon" ]; then
+
+   /usr/local/bin/chronyc << EOF
+password xxxxxxx
+online 255.255.255.0/158.152.1.0
+online 255.255.255.0/194.159.253.0
+EOF
+
+fi
+
+if [ $PPP_IPPARAM = "freeserve" ]; then
+
+   /usr/local/bin/chronyc << EOF
+password xxxxxxx
+online 255.255.255.0/10.0.0.0
+EOF
+
+fi
+-------------------- /etc/ppp/ip-up --------------------
+
+-------------------- /etc/ppp/ip-down --------------------
+#!/bin/sh -f
+#
+# A script to stop chrony
+#
+
+PPP_IPPARAM="$6"
+
+if [ $PPP_IPPARAM = "demon" ]; then
+
+   /usr/local/bin/chronyc << EOF
+password xxxxxxx
+offline 255.255.255.0/158.152.1.0
+offline 255.255.255.0/194.159.253.0
+EOF
+
+fi
+
+if [ $PPP_IPPARAM = "freeserve" ]; then
+
+   /usr/local/bin/chronyc << EOF
+password xxxxxxx
+offline 255.255.255.0/10.0.0.0
+EOF
+
+fi
+-------------------- /etc/ppp/ip-down --------------------
+
+-- 
+Andrew.
+----------------------------------------------------------------------
+Andrew M. Bishop                             amb@gedanken.demon.co.uk
+                                      http://www.gedanken.demon.co.uk/
+------- End of forwarded message -------
+
+-- 
+Andrew.
+----------------------------------------------------------------------
+Andrew M. Bishop                             amb@gedanken.demon.co.uk
+                                      http://www.gedanken.demon.co.uk/
+
diff --git a/contrib/erik_bryer_1 b/contrib/erik_bryer_1
new file mode 100644 (file)
index 0000000..c551dfe
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# chrony        Start time synchronization. This script
+#               starts chronyd.
+#
+# Hacked by:    Erik Bryer <ebryer@spots.ab.ca> using inet as a template
+#
+# chkconfig: 2345 02 82
+# description: chronyd helps keep the system time accurate by calculating \
+#              and applying correction factors to compensate for the drift \
+#              in the clock. chronyd can also correct the hardware clock \
+#              (RTC) on some systems.
+# processname: chronyd
+# config: /etc/chrony.conf
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Set path to include chronyd in /usr/local/sbin
+PATH="$PATH:/usr/local/sbin"
+
+[ -f /usr/local/sbin/chronyd ] || exit 0
+
+[ -f /etc/chrony.conf ] || exit 0
+
+RETVAL=0
+
+# See how we were called.
+case "$1" in
+  start)
+        # Start daemons.
+        echo -n "Starting chronyd: "
+        daemon chronyd
+       RETVAL=$?
+       [ $RETVAL -eq 0 ] && touch /var/lock/subsys/chrony
+       echo
+        ;;
+  stop)
+        # Stop daemons.
+        echo -n "Shutting down chronyd: "
+# If not dead killproc automatically sleeps for 4.1 seconds then does 
+# kill -9. "chrony.txt" prefers a 5 second delay, but this should be ok.
+        killproc chronyd -15
+       RETVAL=$?
+       [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/chrony
+        echo
+        ;;
+  status)
+       status chronyd
+       exit $?
+       ;;
+  restart)
+       $0 stop
+       $0 start
+       ;;
+  *)
+        echo "Usage: named {start|stop|status|restart}"
+        exit 1
+esac
+
+exit $RETVAL
+
diff --git a/contrib/ken_gillett_1 b/contrib/ken_gillett_1
new file mode 100644 (file)
index 0000000..48b7999
--- /dev/null
@@ -0,0 +1,100 @@
+#!/bin/sh
+#
+# chronyd      This shell script takes care of starting and stopping
+#              chronyd (NTP daemon).
+#
+# chkconfig: 45 80 20
+# description: chronyd is the NTP daemon.
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Check that networking is up.
+[ ${NETWORKING} = "no" ] && exit 0
+
+PREDIR="/usr/local"
+CHRONYD=$PREDIR"/sbin/chronyd"
+CHRONYC=$PREDIR"/bin/chronyc"
+
+[ -x $CHRONYD -a -x $CHRONYC -a -f /etc/chrony.conf ] || exit 0
+
+dochrony() {
+       if [ -z "$(pidofproc chronyd)" ]; then
+               echo -e "\n\tchronyd not running\n\n"
+               exit 2
+       fi
+       KEY=`awk '$1 == "commandkey" {print $2; exit}' /etc/chrony.conf`
+       PASSWORD=`awk '$1 == '$KEY' {print $2; exit}' /etc/chrony/keys`
+
+       $CHRONYC <<- EOF
+               password $PASSWORD
+               $@
+               quit
+       EOF
+}
+
+# make the first parameter' lower case
+set - `echo $1 | awk '{print tolower($1)}';shift;echo "$@"`
+
+# Expand any shortcuts.
+case "$1" in
+   on|1)
+       set - "online"
+       ;;
+   off|0)
+       set - "offline"
+esac
+
+# See how we were called.
+case "$1" in
+   start)
+         # Start daemons.
+         echo -n "Starting chronyd: "
+         daemon $CHRONYD
+       if [ $? -eq 0 ]; then
+               echo $(pidofproc chronyd) > /var/run/chronyd.pid
+               touch /var/lock/subsys/chronyd
+       fi
+         echo
+         ;;
+   stop)
+         # Stop daemons.
+         echo -n "Shutting down chronyd: "
+       killproc chronyd
+         echo
+         rm -f /var/lock/subsys/chronyd
+         ;;
+   status)
+       status chronyd
+       ;;
+   restart|reload)
+       $0 stop
+       $0 start
+       ;;
+   condrestart)
+       if [ -f /var/lock/subsys/chronyd ]; then
+               $0 stop
+               $0 start
+       fi
+       ;;
+   "")
+       echo "Usage: chronyd 
+{start|stop|restart|reload|condrestart|status|[on|off]line etc}"
+       exit 1
+       ;;
+accheck|cmdaccheck|clients|manual|rtcdata|sources|sourcestats|tracking|clients)
+       dochrony "$@"
+       ;;
+   *)
+         echo -n "Chrony $1: "
+       dochrony "$@" > /dev/null
+       [ $? -eq 0 ] && echo_success || echo_failure
+       echo
+esac
+
+exit 0
+
diff --git a/contrib/stephan_boettcher_1 b/contrib/stephan_boettcher_1
new file mode 100644 (file)
index 0000000..e5eda11
--- /dev/null
@@ -0,0 +1,162 @@
+From stephan@nevis1.nevis.columbia.edu Mon Jun  7 20:51:57 1999
+Date: 04 Jun 1999 00:17:25 -0400
+From: Stephan I. Boettcher <stephan@nevis1.nevis.columbia.edu>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: chrony 1.1 sysV startup script for notebooks
+
+
+Dear Richard,
+
+I installed chrony on my notebook, running RedHat 5.1 Linux.
+It looks like it works.  No problems.
+
+Thank you!
+
+I like to donate my sysV startup script, appended below.
+
+Special feature:  the `online' command scans the config file to
+selectively turn some servers online, depending on the pcmcia SCHEME.
+
+booting:                 /etc/rc.d/init.d/chrony start
+/etc/ppp/ip-up:          /etc/rc.d/init.d/chrony online
+/etc/ppp/ip-down:        /etc/rc.d/init.d/chrony offline
+logrotate cron:          /etc/rc.d/init.d/chrony cyclelogs
+a user:                  /etc/rc.d/init.d/chrony status
+a sysadmin:              /etc/rc.d/init.d/chrony restart
+shutdown:                /etc/rc.d/init.d/chrony stop
+
+Best regards
+Stephan
+
+-- 
+
+------------------------------------------------------------------------
+Stephan Boettcher                                   FAX: +1-914-591-4540
+Columbia University, Nevis Labs                     Tel: +1-914-591-2863
+P.O. Box 137, 136 South Broadway      mailto:stephan@nevis1.columbia.edu
+Irvington, NY 10533, USA          http://www.nevis.columbia.edu/~stephan
+------------------------------------------------------------------------
+
+########################### cut here ###################################
+#! /bin/bash
+#
+#  /etc/rc.d/init.d/chrony
+#
+#  SYS V startup script for  
+#  chrony ntp daemon 
+#  on Linux 2.0.3x notebooks with pcmcia scheme support
+#  $Id: stephan_boettcher_1,v 1.1 2000/04/24 21:36:04 richard Exp $
+#
+#  1999-06-02 SiB <stephan@nevis1.columbia.edu>
+#
+# For PCMCIA users:
+# In /etc/chrony.conf, precede the server commands for each SCHEME 
+# with a comment line that contains the word SCHEME and the name of
+# the scheme(s) that should use the servers, up to the next line that
+# contains the word SCHEME.  The servers must be `offline' and 
+# specified by their IP address.  The hostname will not do.
+#
+# Like:
+#
+#      # SCHEME nevisppp nevislan
+#      #       stephanpc.nevis.columbia.edu
+#      server  192.12.82.222   offline
+#
+#      # SCHEME desyppp desylan
+#
+#      #       dsygw2.desy.de
+#      server  131.169.30.15   offline
+#      #       dscomsa.desy.de
+#      server  131.169.197.35  offline
+
+CONF=/etc/chrony.conf
+CHRONYD=/usr/local/sbin/chronyd
+CHRONYC=/usr/local/bin/chronyc
+KEYS=/etc/chrony.keys
+
+# See if we got all we need:
+
+[ -f $CHRONYD -a -f $CHRONYC -a -r $CONF ] || exit
+
+
+[ -r $KEYS ]                                                   \
+&& CMDKEY=`awk '/^commandkey/{print $2}' $CONF`                        \
+&& PASSWORD=`awk -v KEY=$CMDKEY '$1==KEY{print $2}' $KEYS`
+
+
+case "$1" in
+
+  start)
+       echo -n "Starting chronyd "
+        $CHRONYD -r -s -f $CONF
+        echo
+        ;;
+
+  stop)
+        echo -n "Shutting down chronyd "
+       /usr/bin/killall chronyd
+        echo
+        ;;
+
+  restart)
+       $0 stop
+       $0 start
+       ;;
+
+  on*)
+
+       [ -f /var/run/pcmcia-scheme ] && SCHEME=`cat /var/run/pcmcia-scheme`
+
+       awk  -v SCHEME=${SCHEME:-default}  -v PASSWORD=$PASSWORD  \
+               '
+               BEGIN { 
+                       SEL=1;
+                       print "password", PASSWORD; 
+               }
+               /SCHEME/ { 
+                       SEL=match($0, SCHEME); 
+               }
+               SEL && /^server[ \t]*[0-9.]+[ \t].*offline/ { 
+                       print "online 255.255.255.255/"  $2; 
+               }
+               '       \
+               $CONF   \
+       | $CHRONYC
+
+       ;;
+
+  off*)
+       cat <<-EOF | $CHRONYC
+               password $PASSWORD
+               offline
+               trimrtc
+               dump
+               EOF
+       ;;
+
+  *log*)
+       cat <<-EOF | $CHRONYC
+               password $PASSWORD
+               cyclelogs
+               EOF
+       ;;
+
+  stat*)
+       cat <<-EOF | $CHRONYC
+               sources
+               sourcestats
+               tracking
+               rtcdata
+               EOF
+       ;;
+
+  *)
+        echo "Usage: chronyd {start|stop|restart|status|online|offline|cyclelogs}"
+        exit 1
+       ;;
+
+esac
+
+exit 0
+
+
diff --git a/contrib/wolfgang_weisselberg1 b/contrib/wolfgang_weisselberg1
new file mode 100644 (file)
index 0000000..2c41752
--- /dev/null
@@ -0,0 +1,118 @@
+
+> Is it possible to limit chronyc to only those commands that
+> are readonly plus those necessary to bring a dialup connection up
+> and down? That is: online offline dump writertc and password.
+
+This is trivial on the same host and workable for non-local
+hosts: use a wrapper program or script.  An *untested*
+sample follows.  To use it, best create a special user (say
+chronyc) and a special group (say chronyg).  Make the script
+chronyc:chronyg, and 4750 (suid, rwxr-x---).  Add all users
+who may run the script to the group chronyg.
+
+Make a chrony password file e.g.
+/usr/local/etc/chrony_password.  It should be owned by chronyc
+and readable only for the owner, containing only the chrony
+password (and maybe a newline) in the first line.
+
+In this way only the script (call it run_chrony, for example)
+can read the password.  It will allow only those commands you
+explicitely allow.  You can add a password check -- especially
+if you add an internet port so you can access it over the
+internet this is advisable.  You really want to add logging
+to this untested script as well.
+
+
+BTW, if you use some sort of PPP, you probably can use
+/etc/ppp/ip-up and /etc/ppp/ip-down to transparently set chrony
+on- and offline as the ip connection goes up and comes down.
+This is _far_ more user friendly, IMHO, and a DOS by switching
+chrony offline all the time is avoided as well.
+
+
+#! /usr/bin/perl -T
+use v5.6.1;
+use warnings;
+use strict;
+
+sub laundered_command();
+sub order_chrony($$);
+sub read_password();
+sub usage($);
+
+our $CHRONY = "/usr/local/bin/chronyc";
+
+# NOTE: select the file system protection wisely for the
+# PASSWORDFILE!
+our $PASSWORDFILE = "/usr/local/etc/chrony_password";
+
+our @ALLOWED_COMMANDS = (
+    'online',                 # switch online mode on
+    'offline',                # switch online mode off
+    'dump',                   # save measurements to file
+    'writerc',                # save RTC accumulated data
+
+    'clients',                # which clients are served by us?
+    'rtcdata',                # Quality of RTC measurements
+    'sources(?: -v)?',        # Show our sources (verbose)
+    'sourcestats(?: -v)?',    # How good are our sources (verbose)?
+    'tracking',               # whom do we adjust to?
+
+    # 'burst \d+/\d+',          # allow them to send bursts?
+);
+
+usage("No command given.") unless $ARGV[0];
+
+%ENV = ();    # nuke all environment variables.  Rather
+              # drastic, but better safe than sorry!
+              # Add whatever you really need to get it
+              # working (again).
+$ENV{'PATH'} = '/usr/local/bin:/bin:/usr/bin';
+
+order_chrony(laundered_command(), read_password());
+
+exit 0; # command succeeded
+                        
+############################################################
+
+sub usage($) {
+    print STDERR "Error: ", shift, "\n";
+
+    # OK, this eats the -v...
+    print STDERR "Legal commands are:\n\t", join "\n",
+      map { $_ =~ m:(\w+):; $1 } @ALLOWED_COMMANDS;
+    exit 1;     # error
+}
+
+############################################################
+
+sub laundered_command() {
+    my $regexp = "^(" . join ( "|", @ALLOWED_COMMANDS ) . ")\$";
+    my $parameters = join " ", @ARGV;
+    $parameters =~ m:$regexp: or usage("Command $parameters not allowed.");
+
+    return $1;    # this value, then, is untainted.
+};
+
+############################################################
+
+sub read_password() {
+    open PASS, $PASSWORDFILE
+      or die "Could not read protected password file: $!";
+    my $password = <PASS>;
+    chomp $password;
+    return $password;
+};
+
+############################################################
+
+sub order_chrony($$) {
+    my ($clean_command, $password) = @_;
+    open CHRONY, "| $CHRONY &> /dev/null" or die "could not run $CHRONY: $!\n";
+    print CHRONY "password $password\n";
+    print CHRONY "$clean_command\n";
+    close CHRONY
+      or die "Error running command $clean_command\n", "\ton $CHRONY: $!\n";
+}
+
+############################################################
diff --git a/examples/chrony.conf.example b/examples/chrony.conf.example
new file mode 100644 (file)
index 0000000..5488e72
--- /dev/null
@@ -0,0 +1,289 @@
+#######################################################################
+# $Header: /cvs/src/chrony/examples/chrony.conf.example,v 1.2 2002/02/03 21:46:29 richard Exp $
+#
+# This is an example chrony configuration file.  You should copy it to
+# /etc/chrony.conf after uncommenting and editing the options that you
+# want to enable.  I have not included the more obscure options.  Refer
+# to the documentation for these.
+#
+# Copyright 2002 Richard P. Curnow
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+# 
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+# 
+#
+#######################################################################
+### COMMENTS
+# Any of the following lines are comments (you have a choice of
+# comment start character):
+# a comment
+% a comment
+! a comment
+; a comment
+#
+# Below, the '!' form is used for lines that you might want to
+# uncomment and edit to make your own chrony.conf file.
+#
+#######################################################################
+#######################################################################
+### SPECIFY YOUR NTP SERVERS
+# Most computers using chrony will send measurement requests to one or
+# more 'NTP servers'.  You will probably find that your Internet Service
+# Provider or company have one or more NTP servers that you can specify.
+# Failing that, there are a lot of public NTP servers.  There is a list
+# you can access at
+# http://www.eecis.udel.edu/~mills/ntp/servers.htm.
+
+! server ntp0.your-isp.com
+! server ntp1.your-isp.com
+! server ntp.public-server.org
+# However, for dial-up use you probably want these instead.  The word
+# 'offline' means that the server is not visible at boot time.  Use
+# chronyc's 'online' command to tell chronyd that these servers have
+# become visible after you go on-line.
+
+! server ntp0.your-isp.com offline
+! server ntp1.your-isp.com offline
+! server ntp.public-server.org offline
+
+# You may want to specify NTP 'peers' instead.  If you run a network
+# with a lot of computers and want several computers running chrony to
+# have the 'front-line' interface to the public NTP servers, you can
+# 'peer' these machines together to increase robustness.
+
+! peer ntp0.my-company.com
+
+# There are other options to the 'server' and 'peer' directives that you
+# might want to use.  For example, you can ignore measurements whose
+# round-trip-time is too large (indicating that the measurement is
+# probably useless, because you don't know which way the measurement
+# message got held up.)  Consult the full documentation for details.
+
+#######################################################################
+### AVOIDING POTENTIALLY BOGUS CHANGES TO YOUR CLOCK
+#
+# To avoid changes being made to your computer's gain/loss compensation
+# when the measurement history is too erratic, you might want to enable
+# one of the following lines.  The first seems good for dial-up (or
+# other high-latency connections like slow leased lines), the second
+# seems OK for a LAN environment.
+
+! maxupdateskew 100
+! maxupdateskew 5
+
+#######################################################################
+### FILENAMES ETC
+# Chrony likes to keep information about your computer's clock in files.
+# The 'driftfile' stores the computer's clock gain/loss rate in parts
+# per million.  When chronyd starts, the system clock can be tuned
+# immediately so that it doesn't gain or lose any more time.  You
+# generally want this, so it is uncommented.
+
+driftfile /etc/chrony.drift
+
+# If you want to use the program called chronyc to configure aspects of
+# chronyd's operation once it is running (e.g. tell it the Internet link
+# has gone up or down), you need a password.  This is stored in the
+# following keys file.  (You also need keys to support authenticated NTP
+# exchanges between cooperating machines.)  Again, this option is
+# assumed by default.
+
+keyfile /etc/chrony.keys
+
+# Tell chronyd which numbered key in the file is used as the password
+# for chronyc. (You can pick any integer up to 2**32-1.  '1' is just a
+# default.  Using another value will _NOT_ increase security.)
+
+commandkey 1
+
+# chronyd can save the measurement history for the servers to files when
+# it it exits.  This is useful in 2 situations:
+#
+# 1. On Linux, if you stop chronyd and restart it with '-r' (e.g. after
+# an upgrade), the old measurements will still be relevant when chronyd
+# is restarted.  This will reduce the time needed to get accurate
+# gain/loss measurements, especially with a dial-up link.
+#
+# 2. Again on Linux, if you use the RTC support and start chronyd with
+# '-r -s' on bootup, measurements from the last boot will still be
+# useful (the real time clock is used to 'flywheel' chronyd between
+# boots).
+#
+# Enable these two options to use this.
+
+! dumponexit
+! dumpdir /var/log/chrony
+
+# chronyd writes its process ID to a file.  If you try to start a second
+# copy of chronyd, it will detect that the process named in the file is
+# still running and bail out.  If you want to change the path to the PID
+# file, uncomment this line and edit it.  The default path is shown.
+
+! pidfile /var/run/chronyd.pid
+
+#######################################################################
+### INITIAL CLOCK CORRECTION
+# This option is only useful if your NTP servers are visible at boot
+# time.  This probably means you are on a LAN.  If so, the following
+# option will choose the best-looking of the servers and correct the
+# system time to that.  The value '10' means that if the error is less
+# than 10 seconds, it will be gradually removed by speeding up or
+# slowing down your computer's clock until it is correct.  If the error
+# is above 10 seconds, an immediate time jump will be applied to correct
+# it.  Some software can get upset if the system clock jumps (especially
+# backwards), so be careful!
+
+! initstepslew 10 ntp0.your-company.com ntp1.your-company.com ntp2.your-company.com
+
+#######################################################################
+### LOGGING
+# If you want to log information about the time measurements chronyd has
+# gathered, you might want to enable the following lines.  You probably
+# only need this if you really enjoy looking at the logs, you want to
+# produce some graphs of your system's timekeeping performance, or you
+# need help in debugging a problem.
+
+! logdir /var/log/chrony
+! log measurements statistics tracking
+
+If you have real time clock support enabled (see below), you might want
+this line instead:
+
+! log measurements statistics tracking rtc
+
+#######################################################################
+### ACTING AS AN NTP SERVER
+# You might want the computer to be an NTP server for other computers.
+# e.g.  you might be running chronyd on a dial-up machine that has a LAN
+# sitting behind it with several 'satellite' computers on it.
+#
+# By default, chronyd does not allow any clients to access it.  You need
+# to explicitly enable access using 'allow' and 'deny' directives.
+#
+# e.g. to enable client access from the 192.168.*.* class B subnet,
+
+! allow 192.168/16
+
+# .. but disallow the 192.168.100.* subnet of that,
+
+! deny 192.168.100/24
+
+# You can have as many allow and deny directives as you need.  The order
+# is unimportant.
+
+# If you want chronyd to act as an NTP broadcast server, enable and edit
+# (and maybe copy) the following line.  This means that a broadcast
+# packet is sent to the address 192.168.1.255 every 60 seconds.  The
+# address MUST correspond to the broadcast address of one of the network
+# interfaces on your machine.  If you have multiple network interfaces,
+# add a broadcast line for each.
+
+! broadcast 60 192.168.1.255
+
+# If you want to present your computer's time for others to synchronise
+# with, even if you don't seem to be synchronised to any NTP servers
+# yourself, enable the following line.  The value 10 may be varied
+# between 1 and 15.  You should avoid small values because you will look
+# like a real NTP server.  The value 10 means that you appear to be 10
+# NTP 'hops' away from an authoritative source (atomic clock, GPS
+# receiver, radio clock etc).
+
+! local stratum 10
+
+# Normally, chronyd will keep track of how many times each client
+# machine accesses it.  The information can be accessed by the 'clients'
+# command of chronyc.  You can disable this facility by uncommenting the
+# following line.  This will save a bit of memory if you have many
+# clients.
+
+! noclientlog
+
+#######################################################################
+### REPORTING BIG CLOCK CHANGES
+# Perhaps you want to know if chronyd suddenly detects any large error
+# in your computer's clock.  This might indicate a fault or a problem
+# with the server(s) you are using, for example.
+#
+# The next option causes a message to be written to syslog when chronyd
+# has to correct an error above 0.5 seconds (you can use any amount you
+# like).
+
+! logchange 0.5
+
+# The next option will send email to the named person when chronyd has
+# to correct an error above 0.5 seconds.  (If you need to send mail to
+# several people, you need to set up a mailing list or sendmail alias
+# for them and use the address of that.)
+
+! mailonchange wibble@foobar.org 0.5
+
+#######################################################################
+### COMMAND ACCESS
+# The program chronyc is used to show the current operation of chronyd
+# and to change parts of its configuration whilst it is running.
+
+# Normally, chronyd will only allow connections from chronyc on the same
+# machine as itself.  This is for security.  If you have a subnet
+# 192.168.*.* and you want to be able to use chronyc from any machine on
+# it, you could uncomment the following line.  (Edit this to your own
+# situation.)
+
+! cmdallow 192.168/16
+
+# You can add as many 'cmdallow' and 'cmddeny' lines as you like.  The
+# syntax and meaning is the same as for 'allow' and 'deny', except that
+# 'cmdallow' and 'cmddeny' control access to the chronyd's command port.
+
+# NOTE, even if the host where you run chronyc is granted access, you
+# still need a command key set up and you have to know the password to
+# put into chronyc to allow you to modify chronyd's parameters.  By
+# default all you can do is view information about chronyd's operation.
+
+# Some people have reported that the need the following line to allow
+# chronyc to work even on the same machine.  This should not be
+# necessary, and the problem is being investigated.  You can leave this
+# line enabled, as it's benign otherwise.
+
+cmdallow 127.0.0.1
+
+#######################################################################
+### REAL TIME CLOCK
+# chronyd can characterise the system's real-time clock.  This is the
+# clock that keeps running when the power is turned off, so that the
+# machine knows the approximate time when it boots again.  The error at
+# a particular epoch and gain/loss rate can be written to a file and
+# used later by chronyd when it is started with the '-s' option.
+#
+# You need to have 'enhanced RTC support' compiled into your Linux
+# kernel.  (Note, these options apply only to Linux.)
+
+! rtcfile /etc/chrony.rtc
+
+# Your RTC can be set to keep Universal Coordinated Time (UTC) or local
+# time.  (Local time means UTC +/- the effect of your timezone.)  If you
+# use UTC, chronyd will function correctly even if the computer is off
+# at the epoch when you enter or leave summer time (aka daylight saving
+# time).  However, if you dual boot your system with Microsoft Windows,
+# that will work better if your RTC maintains local time.  You take your
+# pick!
+
+! rtconutc
+
+# By default chronyd assumes that the enhanced RTC device is accessed as
+# /dev/rtc.  If it's accessed somewhere else on your system (e.g. you're
+# using devfs), uncomment and edit the following line.
+
+! rtcdevice /dev/misc/rtc
+
+#######################################################################
diff --git a/examples/chrony.keys.example b/examples/chrony.keys.example
new file mode 100644 (file)
index 0000000..181e96e
--- /dev/null
@@ -0,0 +1,27 @@
+#######################################################################
+# $Header: /cvs/src/chrony/examples/chrony.keys.example,v 1.1 2002/01/31 00:00:08 richard Exp $
+#
+# This is an example chrony keys file.  You should copy it to /etc/chrony.keys
+# after editing it to set up the key(s) you want to use.  In most situations,
+# you will require a single key (the 'commandkey') so that you can supply a
+# password to chronyc to enable you to modify chronyd's operation whilst it is
+# running.
+#
+# Copyright 2002 Richard P. Curnow
+#
+#######################################################################
+# A valid key line looks like this
+
+1 a_key
+
+# It must consist of an integer, followed by whitespace, followed by a block of
+# text with no spaces in it.  (You cannot put a space in a key).  If you wanted
+# to use the above line as your commandkey (i.e. chronyc password), you would
+# put the following line into chrony.conf (remove the # from the start):
+
+# commandkey 1
+
+# You might want to define more keys if you use the MD5 authentication facility
+# in the network time protocol to authenticate request/response packets between
+# trusted clients and servers.
+
diff --git a/faq.txt b/faq.txt
new file mode 100644 (file)
index 0000000..659519f
--- /dev/null
+++ b/faq.txt
@@ -0,0 +1,384 @@
+@@PROLOGUE
+<html>
+<head>
+<title>Frequently asked questions</title>
+<meta name="description" content="Chrony FAQ (frequently asked questions)">
+<meta name="keywords" content="chrony,network time protocol,NTP,RFC 1305,dial-up connection,real time clock,RTC,Linux,FAQ,frequently asked questns">
+<?php
+  $root = ".";
+  include "$root/styles.php";
+?>
+</head>
+
+<body>
+<?php
+  include 'main_banner.php';
+  include 'header.php';
+?>
+<?php pretty_h1("Introduction") ?>
+<p>
+This is a set of questions and answers to common problems and issues.
+<p>
+As I receive more emails about the software, I will add new questions
+to this page.
+
+<hr>
+<p>
+The author can be reached by email
+<a href="mailto:rc@rc0.org.uk">
+</a>
+<p>
+<b>PLEASE</b>
+include the word &quot;chrony&quot; in your subject line if possible (so that my
+mail reader can keep my mail sorted by topic)!
+<hr>
+
+<br clear=all>
+@@ENDPROLOGUE
+S: Administrative issues
+
+Q: Where can I get chrony source code?
+Via the home page, see below.
+
+Q: Are there any packaged versions of chrony?
+I am aware of packages for Debian, Mandrake and Redhat.  I am not personally
+involved with how these are built or distributed.
+
+Q: Where is the home page?
+It is currently at <a href="http://chrony.sunsite.dk/">http://chrony.sunsite.dk/</a>. 
+
+Q: Is there a mailing list?
+Yes, it's currently at chrony-users@sunsite.dk. There is a low-volume
+list called chrony-announce which is just for announcements of new releases or
+similar matters of high importance.  You can join the lists by sending a
+message to <a href="mailto:chrony-users-subscribe@sunsite.dk">chrony-users-subscribe@sunsite.dk</a> or
+<a href="mailto:chrony-announce-subscribe@sunsite.dk">chrony-announce-subscribe@sunsite.dk</a> respectively.
+
+For those who want to contribute to the development of chrony, there is a
+developers' mailing list.  You can subscribe by sending mail to
+<a href="mailto:chrony-dev-subscribe@sunsite.dk">chrony-dev-subscribe@sunsite.dk</a>.
+
+Q: What licence is applied to chrony?
+Starting from version 1.15, chrony is licensed under the GNU General Public
+License.  Versions prior to 1.15 were licensed under a custom BSD-like
+license.
+
+If you want to use parts of chrony in non-free software, you will need to use
+older versions of the source code.  Alternatively, contact me - I may be
+prepared to licence parts of the source code to suit your purposes.  I am quite
+sympathetic to projects licensed under other free/open-source (but non-GPL)
+licences, as well as to commercial projects which are of a single-customer
+"turnkey" nature (as opposed to mass-market "shrink-wrap" or "floating-licence"
+products).
+
+S: Chrony compared to other programs
+Q: How does chrony compare to xntpd?
+If your computer is permenently connected, or connected for long periods (that
+is, for the several hours it takes xntpd to settle down), or you need to
+support hardware reference clocks to your computer, then xntpd will work fine.
+Apart from not supporting hardware clocks, chrony will work fine too.
+
+If your computer connects to the 'net for 5 minutes once a day (or something
+like that), or you turn your (Linux v2.0) computer off when you're not using
+it, or you want to use NTP on an isolated network with no hardware clocks in
+sight, chrony will work much better for you.
+
+The reason I wrote chrony was that I could not get xntpd to do
+anything sensible on my PC at home, which is connected to the 'net for
+about 5 minutes once or twice a day, mainly to upload/download email
+and news.  Nowadays it is also turned off for 22-23 hours a day, when
+not in use.  I wanted a program which would :
+
+- slew the time to correct it when I go online and NTP servers become visible
+
+- determine the rate at which the computer gains or loses time and use this
+  information to keep it reasonably correct between connects to the 'net.  This
+  has to be done using a method that does not care about the intermittent
+  availability of the references or the fact the computer is turned off between
+  groups of measurements..
+
+- maintain the time across reboots, by working out the error and drift rate of
+  the computer's real-time clock and using this information to set the system
+  clock correctly at boot up. (In the last few months, it became impossible for
+  me to leave my computer powered permanently.)
+
+Also, when working with isolated networks with no true time references
+at all, I found xntpd gave me no help with managing the local clock's
+gain/loss rate on the NTP master node (which I set from my watch).  I
+added some automated support in chrony to deal with this.
+
+S: Compilation issues
+Q:How do I apply source patches?
+Sometimes I release source patches rather than a full version when I need to
+provide a fix for small problems.  Supposing you have chrony-1.X.tar.gz and a
+source patch chrony-1.X-1.X.1.gz.  The steps required are:
+
+    tar xzvf ../chrony-1.X.tar.gz
+    cd chrony-1.X
+    gunzip < ../../chrony-1.X-1.X.1.gz  | patch -p1
+    ./configure
+    make
+    make install
+
+Q:Can I compile chrony with an ANSI-C compiler that is not GCC v2.x?
+I have had reports that chrony can be compiled with GCC v1.42, by using the
+following trick when running make
+
+   make CC='gcc -D__FUNCTION__=\"function_not_available\"'
+
+(this gets around the lack of a __FUNCTION__ macro in GCC v1.)
+
+The same trick may be enough to allow other compilers to be used.
+
+Q: I get errors like 'client.c:44: readline/readline.h: file not found'
+Read the section about 'readline' in the INSTALL file or in chrony.txt.  You
+may need to disable readline support (e.g. if you haven't got readline
+installed at all, or just don't want it), or specify the location of the
+readline files (e.g. if you've installed them in a non-standard place).
+
+Q: I have RedHat 7.3 and can't compile rtc_linux.c (error in spinlock.h)
+The following solution has been found for this.  Enter the following 3 commands
+(as root):
+
+  cd  /usr/include/                                                                                                               
+  mv linux linux.rh                                                                                                               
+  ln -s /usr/src/linux/include/linux ./linux                                                                                    
+
+The problem seems to be that RedHat provide their own kernel header files in
+/usr/include/linux.  Besides differing from those used by your current kernel,
+if you compiled it yourself, they also seem to have been changed in a way that
+causes a problem compiling chrony.  Chrony compiles fine with standard kernel
+header files.
+
+There have also been reports that just replacing the file
+/usr/src/linux/spinlock.h by the equivalent file from a vanilla kernel source
+tree is sufficient to fix the problem.
+
+S: Selection of NTP servers
+Q: I have several computers on a LAN.  Should I make one the master, or make them all clients of an external server?
+I think the best configuration is to make one computer the master, with the
+others as clients of it.  Add a 'local' directive to the master's chrony.conf
+file.  This configuration will be better because
+
+* the load on the external connection is less
+* the load on the external NTP server(s) is less
+* if your external connection goes down, the computers on the LAN will maintain
+  a common time with each other.
+
+S: Addressing issues
+Q: I get the following error message : "Could not get IP adress for localhost"
+Add a line like the following to your /etc/hosts file
+    127.0.0.1   localhost
+
+Q: I have problems if I put the names of my NTP servers in the chrony.conf file.
+If you have no connection to the Internet at boot time, chrony won't be able to
+turn the names into IP addresses when it starts.  There seem to be 2 solutions:
+
+1. Put the numeric IP addresses in the chrony.conf file
+or
+2. Put the server->IP address mappings in your /etc/hosts file and ensure that
+   /etc/host.conf reads 'order hosts,bind'.
+
+The problem is that chronyd (currently) isn't designed in a way that allows
+hostname->IP address lookups during normal operation.  I hope to work on this
+problem very soon.
+
+S: My computer is not synchronising.
+This is the most common problem.  There are a number of reasons, see the
+following questions.
+
+Q: Behind a firewall?
+If there is a firewall between you and the NTP server you're trying to use,
+the packets may be blocked.  Try using a tool like etherfind or tcpdump to see
+if you're getting responses from the server.  If you have an external modem,
+see if the receive light blinks straight after the transmit light (when the
+link is quiet apart from the NTP traffic.)  Try adding 'log measurements' to
+the chrony.conf file and look in the measurements.log file after chrony has
+been running for a short period.  See if any measurements appear.
+
+Most people run chronyd on the firewall itself, to avoid all issues of UDP
+packet forwarding and/or masquerading.
+
+Q: Do you have a non-permanant (i.e. intermittent) Internet connection?
+Check that you're using chronyc's 'online' and 'offline' commands
+appropriately.  Again, check in measurements.log to see if you're getting any
+data back from the server.
+
+Q: In measurements.log, do the '7' and '8' flag columns always show zero?
+Do you have a 'local stratum X' directive in the chrony.conf file?  If X is
+lower than the stratum of the server you're trying to use, this situation will
+arise.  You should always make X quite high (e.g. 10) in this directive.
+
+S: Issues with chronyd
+
+Q: chronyd crashes after a syslog message "adjtimex failed for set frequency"
+The usual cause is that the kernel is running with a different value of 'HZ'
+(the timer interrupt rate) than the value that was found in the kernel header
+files when chrony was compiled.  The chrony.conf file can include options to
+modify the HZ value (see the discussion of linux_hz and linux_freq_scale in the
+documentation), however the problem is to find the value of HZ being used.
+
+At the end of the chrony v1.18 section of the <a href="./download.php">download page</a>
+you'll find instructions on how to do this.
+
+This will be fixed in version 1.19, by getting chronyd to auto-detect the
+kernel's value rather than relying on the compiled-in default.
+
+S: Issues with chronyc
+
+Q: I keep getting the error '510 No command access from this host --- Reply not authenticated'.
+Make sure that the chrony.conf file (on the computer where chronyd is running)
+has a 'cmdallow' entry for the computer you are running chronyc on.  This
+shouldn't be necessary for localhost, but some people still seem to need an
+entry like 'cmdallow 127.0.0.1'.  (It would be good to understand why problem
+only affects some people).
+
+Q: I cannot log in from chronyc to carry out privileged tasks.
+This is the second most common problem.
+
+Perhaps your /etc/chrony.keys file is badly formatted.  Make sure that the
+final line has a line feed at the end, otherwise the key on that line will work
+as though the last character is missing.  (Note, this bug was fixed in version
+1.16.)
+
+Q: When I enter a command and hit &lt;Return&gt;, chronyc hangs
+This probably means that chronyc cannot communicate with chronyd.
+
+Perhaps chronyd is not running.  Try using the ps command (e.g. on Linux, 'ps
+-auxw') to see if it's running.  Or try 'netstat -a' and see if the ports
+123/udp and 323/udp are listening.  If chronyd is not running, you may have a
+problem with the way you are trying to start it (e.g. at boot time).
+
+Perhaps you have a firewall set up in a way that blocks packets on port
+323/udp.  You need to amend the firewall configuration in this case.
+
+Q: Is the chronyc&lt;-&gt;chronyd protocol documented anywhere?
+Only by the source code :-)  See cmdmon.c (chronyd side) and client.c (chronyc
+side).
+
+S: Real-time clock issues.
+Q: What is the real-time clock (RTC)?
+This is the clock which keeps the time even when your computer is turned off.
+It works with 1 second resolution.   chronyd can monitor the rate at which the
+real-time clock gains or loses time, and compensate for it when you set the
+system time from it at the next reboot.  See the documentation for details.
+
+Q: I want to use chronyd's real-time clock support.  Must I disable hwclock?
+The hwclock program is often set-up by default in the boot and shutdown scripts
+with many Linux installations.  If you want to use chronyd's real-time clock
+support, the important thing is to disable hwclock in the <b>shutdown</b>
+procedure.  If you don't, it will over-write the RTC with a new value, unknown
+to chronyd.  At the next reboot, chronyd will compensate this (wrong) time with
+its estimate of how far the RTC has drifted whilst the power was off, giving a
+meaningless initial system time.
+
+There is no need to remove hwclock from the boot process, as long as chronyd is
+started after it has run.
+
+Q: I just keep getting the '513 RTC driver not running' message
+For the real time clock support to work, you need the following three things:
+                                                                                                                                    
+* a kernel that is supported (e.g. 2.2 onwards)                                                                                     
+* enhanced RTC support compiled into the kernel                                                                                     
+* an 'rtcfile' directive in your chrony.conf file.                                                                                  
+
+S: Problems with isolated networks.
+
+Q: When I use the 'settime' command, chronyd crashes.
+If you enter times that are too far away from the real time, chronyd will
+think the system clock runs fast or slow by an excessive amount.  The required
+compensation factor will be outside the bounds for the adjtimex() system call.
+chronyd will crash when it tries to apply such an excessive adjustment.
+
+S: Microsoft Windows
+
+Q: Does chrony support Windows?
+No.  The chronyc program (the command-line client used for configuring
+chronyd while it is running) has been successfully built and run under Cygwin
+in the past.  chronyd is not portable, because part of it is very
+system-dependent.  It needs adapting to work with Windows' equivalent of the
+adjtimex() call, and it needs to be made to work as an NT service.
+
+Q: Are there any plans to support Windows?
+I have no personal plans to do this.  I have neither the time nor the
+Windows programming expertise.  Some time ago I did start work on a port which
+I was developing under Cygwin.  Anyone is welcome to pick this work up and
+contribute it back to the project.
+
+Q: What alternative NTP clients are there for Windows?
+Some of the names I've seen mentioned are 
+   - Automachron
+   - NetTime (nettime.sourceforge.net)
+
+S: NTP-specific issues
+Q: Can chrony be driven from broadcast NTP servers?
+No.  I remember looking at how they worked when I was first writing chrony.
+Since the 'target market' then was dial-up systems, broadcast packets were not
+relevant so I didn't bother working out how to deal with the complexities of
+doing the delay estimation.
+
+I no longer have root access to a LAN environment to develop and test broadcast
+server support.  Neither have I the time to work on this.  I would be very
+happy to accept a patch from anyone who can develop, test and debug the
+necessary changes!
+
+Q: Can chronyd transmit broadcast NTP packets (e.g. to synchronise other computers on a private LAN)?
+Yes.  Starting from version 1.17, chrony has this capability.
+
+Q: Can chrony keep the system clock a fixed offset away from real time?
+I have not experimented much, but I don't believe this would be possible as
+the program currently stands.
+
+Q: What happens if the network connection is dropped without using chronyc's 'offline' command first?
+In this case chronyd will keep trying to access the server(s) that it thinks
+are online.  Eventually it will decide that they are unreachable and no longer
+consider itself synchronised to them.  If you have other computers on your LAN
+accessing the computer that is affected this way, they too will become
+'unsynchronised', unless you have the 'local' directive set up on the master
+computer.
+
+The 'auto_offline' option to the 'server' entry in the chrony.conf file may be
+useful to avoid this situation.
+
+S: Development
+
+Q: Can I get the source via CVS from anywhere?
+Yes.  See <a href="http://chrony.sunsite.dk/cvs.php">http://chrony.sunsite.dk/cvs.php</a> for information.  Currently there is
+only anonymous read-only access.  I keep the master copy on my own PC, which is
+more convenient for me because I don't have to connect to the Internet to do
+CVS operations on the files.  So for now, there is no read-write access for
+other developers.  Please email me your patches + documentation instead.
+
+S: Linux-specific issues
+
+Q: Why does the source code include kernel header files?
+The program needs to see the definitions of structures used to interact with
+the real time clock (via /dev/rtc) and with the adjtimex() system call.  Sadly
+this has led to a number of compilation problems with newer kernels which have
+been increasingly hard to fix in a way that makes the code compilable on all
+Linux kernel versions (from 2.0 up anyway, I doubt 1.x still works.)  Hopefully
+the situation will not deteriorate further with future kernel versions.
+
+Q: I get "Could not open /dev/rtc, Device or resource busy" in my syslog file.
+Check that you haven't accidentally got two copies of chronyd running (perhaps
+defined in different start-up scripts.)
+
+S: Solaris-specific issues
+Q: On Solaris 2.8, I get an error message about not being able to open kvm to change dosynctodr.
+(The dosynctodr variable controls whether Solaris couples the equivalent of its
+BIOS clock into its system clock at regular intervals).  The Solaris port of
+chrony was developed in the Solaris 2.5 era.  Some aspect of the Solaris kernel
+has changed which prevents the same technique working.  I no longer have root
+access to any Solaris machines to work on this, and am reliant on somebody
+developing the patch and testing it.  A good starting point would be to see if
+xntpd has been modified to work for Solaris 2.8.
+
+@@EPILOGUE
+<hr>
+
+Back to
+<a href="mailto:rc@rc0.org.uk?subject=chrony">the author</a>'s
+<a href="http://www.rc0.org.uk/">main page</a>
+</body>
+</html>
+@@ENDEPILOGUE
diff --git a/faqgen.pl b/faqgen.pl
new file mode 100644 (file)
index 0000000..8151feb
--- /dev/null
+++ b/faqgen.pl
@@ -0,0 +1,140 @@
+#!/usr/bin/env perl
+
+# $Header
+
+# Copyright 2001 Richard P. Curnow
+# LICENCE
+
+# A script to generate an HTML FAQ page from a text input file.  The input is assumed to consist of the following:
+# Lines starting with 'S:'.  These introduce sections.
+# Lines starting with 'Q:'.  These are the topics of questions.
+# Body text (either as an introduction to the sections, or as answers to the questions.
+# The body text is set as pre-formatted.
+
+$| = 1;
+
+@prologue = ();
+@epilogue = ();
+
+@sections=();  # section titles
+@sect_text=(); # introductory text in sections
+
+@questions=(); # questions in sections
+@answers=();   # answers to questions
+
+$sn = -1;
+$had_q = 0;
+
+#{{{ Parse input 
+while (<>) {
+    if (m/\@\@PROLOG/o) {
+        while (<>) {
+            last if (m/^\@\@ENDPROLOG/);
+            push (@prologue, $_);
+        }
+    } elsif (m/\@\@EPILOG/o) {
+        while (<>) {
+            last if (m/^\@\@ENDEPILOG/);
+            push (@epilogue, $_);
+        }
+    } elsif (m/^[sS]:[ \t]*(.*)$/) {
+        chomp;
+        $qn = -1;
+        ++$sn;
+        $sections[$sn] = &guard($1);
+        $sect_text[$sn] = "";
+        $questions[$sn] = [ ];
+        $answers[$sn] = [ ];
+        $had_q = 0;
+    } elsif (/^[qQ]:[ \t]*(.*)$/) {
+        chomp;
+        die unless ($sn >= 0);
+        ++$qn;
+        $questions[$sn]->[$qn] = &guard($1);
+        $had_q = 1;
+    } else {
+        if ($had_q) {
+            if ($qn >= 0) {
+                $answers[$sn]->[$qn] .= $_;
+            }
+        } else {
+            if ($sect_text[$sn] ne "" || $_ !~ /^\s*$/) {
+                $sect_text[$sn] .= $_;
+            }
+        }
+    }
+}
+#}}}
+
+# Emit file header
+if ($#prologue >= 0) {
+    print @prologue;
+} else {
+print <<EOF;
+<html>
+<head>
+<title>
+Chrony Frequently Asked Questions
+</title>
+</head>
+<body>
+<font face=\"arial,helvetica\" size=+4><b>Table of contents</b></font>
+EOF
+}
+
+# Emit table of contents
+print "<ul>\n";
+for $sn (0 .. $#sections) {
+    print "<b><li> <a href=\"#section_".($sn+1)."\">".($sn+1).".</a> ".$sections[$sn]."</b>\n";
+    print "  <ul>\n";
+    for $qn (0 .. $#{$questions[$sn]}) {
+        $sq = ($sn+1).".".($qn+1);
+        print "  <li> <a href=\"#question_".$sq."\">".$sq.".</a> ".$questions[$sn]->[$qn]."\n";
+        #print "  <li> ".$sq.". ".$questions[$sn]->[$qn]."\n";
+    }
+    print "  </ul>\n";
+}
+print "</ul>\n";
+
+# Emit main sections
+for $sn (0 .. $#sections) {
+    print "<hr>\n";
+    print "<a name=section_".($sn+1).">\n";
+    #print "<b><font size=+2 face=\"arial,helvetica\">".($sn+1).". ".$sections[$sn]."</font></b>\n";
+    print "<?php pretty_h2(\"".($sn+1).". ".$sections[$sn]."\"); ?>\n";
+    if ($sect_text[$sn] ne "") {
+        print "<pre>\n";
+        print $sect_text[$sn];
+        print "</pre>\n";
+    }
+    for $qn (0 .. $#{$questions[$sn]}) {
+        $sq = ($sn+1).".".($qn+1);
+        print "<p>\n";
+        print "<a name=question_".$sq.">\n";
+        print "<font size=+1 face=\"arial,helvetica\">".$sq.". ".$questions[$sn]->[$qn]."</font>\n";
+        print "<pre>\n";
+        print $answers[$sn]->[$qn];
+        print "</pre>\n";
+    }
+}
+
+# Print footer
+if ($#epilogue >= 0) {
+    print @epilogue;
+} else {
+print <<EOF;
+</body>
+</html>
+EOF
+}
+
+
+#{{{  sub guard {
+sub guard {
+# Hide wierd tags etc
+    my ($x) = @_;
+    return $x;
+}
+#}}}
+
+
diff --git a/getdate.c b/getdate.c
new file mode 100644 (file)
index 0000000..e4819bd
--- /dev/null
+++ b/getdate.c
@@ -0,0 +1,2019 @@
+
+/*  A Bison parser, made from getdate.y
+ by  GNU Bison version 1.25
+  */
+
+#define YYBISON 1  /* Identify Bison output.  */
+
+#define        tAGO    258
+#define        tDAY    259
+#define        tDAY_UNIT       260
+#define        tDAYZONE        261
+#define        tDST    262
+#define        tHOUR_UNIT      263
+#define        tID     264
+#define        tMERIDIAN       265
+#define        tMINUTE_UNIT    266
+#define        tMONTH  267
+#define        tMONTH_UNIT     268
+#define        tSEC_UNIT       269
+#define        tSNUMBER        270
+#define        tUNUMBER        271
+#define        tYEAR_UNIT      272
+#define        tZONE   273
+
+#line 1 "getdate.y"
+
+/*
+**  Originally written by Steven M. Bellovin <smb@research.att.com> while
+**  at the University of North Carolina at Chapel Hill.  Later tweaked by
+**  a couple of people on Usenet.  Completely overhauled by Rich $alz
+**  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
+**
+**  This grammar has 13 shift/reduce conflicts.
+**
+**  This code is in the public domain and has no copyright.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+# ifdef FORCE_ALLOCA_H
+#  include <alloca.h>
+# endif
+#endif
+
+/* Since the code of getdate.y is not included in the Emacs executable
+   itself, there is no need to #define static in this file.  Even if
+   the code were included in the Emacs executable, it probably
+   wouldn't do any harm to #undef it here; this will only cause
+   problems if we try to write to a static variable, which I don't
+   think this code needs to do.  */
+#ifdef emacs
+# undef static
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII))
+# define IN_CTYPE_DOMAIN(c) 1
+#else
+# define IN_CTYPE_DOMAIN(c) isascii(c)
+#endif
+
+#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
+#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
+#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c))
+#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
+
+/* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
+   - Its arg may be any int or unsigned int; it need not be an unsigned char.
+   - It's guaranteed to evaluate its argument exactly once.
+   - It's typically faster.
+   Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
+   only '0' through '9' are digits.  Prefer ISDIGIT to ISDIGIT_LOCALE unless
+   it's important to use the locale's definition of `digit' even when the
+   host does not conform to Posix.  */
+#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
+
+#include "getdate.h"
+
+#if defined (STDC_HEADERS) || defined (USG)
+# include <string.h>
+#endif
+
+/* Some old versions of bison generate parsers that use bcopy.
+   That loses on systems that don't provide the function, so we have
+   to redefine it here.  */
+#if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
+# define bcopy(from, to, len) memcpy ((to), (from), (len))
+#endif
+
+extern struct tm       *gmtime ();
+extern struct tm       *localtime ();
+extern time_t          mktime ();
+
+/* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc),
+   as well as gratuitiously global symbol names, so we can have multiple
+   yacc generated parsers in the same program.  Note that these are only
+   the variables produced by yacc.  If other parser generators (bison,
+   byacc, etc) produce additional global names that conflict at link time,
+   then those parser generators need to be fixed instead of adding those
+   names to this list. */
+
+#define yymaxdepth gd_maxdepth
+#define yyparse gd_parse
+#define yylex   gd_lex
+#define yyerror gd_error
+#define yylval  gd_lval
+#define yychar  gd_char
+#define yydebug gd_debug
+#define yypact  gd_pact
+#define yyr1    gd_r1
+#define yyr2    gd_r2
+#define yydef   gd_def
+#define yychk   gd_chk
+#define yypgo   gd_pgo
+#define yyact   gd_act
+#define yyexca  gd_exca
+#define yyerrflag gd_errflag
+#define yynerrs gd_nerrs
+#define yyps    gd_ps
+#define yypv    gd_pv
+#define yys     gd_s
+#define yy_yys  gd_yys
+#define yystate gd_state
+#define yytmp   gd_tmp
+#define yyv     gd_v
+#define yy_yyv  gd_yyv
+#define yyval   gd_val
+#define yylloc  gd_lloc
+#define yyreds  gd_reds          /* With YYDEBUG defined */
+#define yytoks  gd_toks          /* With YYDEBUG defined */
+#define yylhs   gd_yylhs
+#define yylen   gd_yylen
+#define yydefred gd_yydefred
+#define yydgoto gd_yydgoto
+#define yysindex gd_yysindex
+#define yyrindex gd_yyrindex
+#define yygindex gd_yygindex
+#define yytable  gd_yytable
+#define yycheck  gd_yycheck
+
+static int yylex ();
+static int yyerror ();
+
+#define EPOCH          1970
+#define HOUR(x)                ((x) * 60)
+
+#define MAX_BUFF_LEN    128   /* size of buffer to read the date into */
+
+/*
+**  An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+    const char *name;
+    int                type;
+    int                value;
+} TABLE;
+
+
+/*
+**  Meridian:  am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+    MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+**  Global variables.  We could get rid of most of these by using a good
+**  union as the yacc stack.  (This routine was originally written before
+**  yacc had the %union construct.)  Maybe someday; right now we only use
+**  the %union very rarely.
+*/
+static const char      *yyInput;
+static int     yyDayOrdinal;
+static int     yyDayNumber;
+static int     yyHaveDate;
+static int     yyHaveDay;
+static int     yyHaveRel;
+static int     yyHaveTime;
+static int     yyHaveZone;
+static int     yyTimezone;
+static int     yyDay;
+static int     yyHour;
+static int     yyMinutes;
+static int     yyMonth;
+static int     yySeconds;
+static int     yyYear;
+static MERIDIAN        yyMeridian;
+static int     yyRelDay;
+static int     yyRelHour;
+static int     yyRelMinutes;
+static int     yyRelMonth;
+static int     yyRelSeconds;
+static int     yyRelYear;
+
+
+#line 175 "getdate.y"
+typedef union {
+    int                        Number;
+    enum _MERIDIAN     Meridian;
+} YYSTYPE;
+#include <stdio.h>
+
+#ifndef __cplusplus
+#ifndef __STDC__
+#define const
+#endif
+#endif
+
+
+
+#define        YYFINAL         61
+#define        YYFLAG          -32768
+#define        YYNTBASE        22
+
+#define YYTRANSLATE(x) ((unsigned)(x) <= 273 ? yytranslate[x] : 32)
+
+static const char yytranslate[] = {     0,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,    20,     2,     2,    21,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,    19,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     1,     2,     3,     4,     5,
+     6,     7,     8,     9,    10,    11,    12,    13,    14,    15,
+    16,    17,    18
+};
+
+#if YYDEBUG != 0
+static const short yyprhs[] = {     0,
+     0,     1,     4,     6,     8,    10,    12,    14,    16,    19,
+    24,    29,    36,    43,    45,    47,    50,    52,    55,    58,
+    62,    68,    72,    76,    79,    84,    87,    91,    94,    96,
+    99,   102,   104,   107,   110,   112,   115,   118,   120,   123,
+   126,   128,   131,   134,   136,   139,   142,   144,   146,   147
+};
+
+static const short yyrhs[] = {    -1,
+    22,    23,     0,    24,     0,    25,     0,    27,     0,    26,
+     0,    28,     0,    30,     0,    16,    10,     0,    16,    19,
+    16,    31,     0,    16,    19,    16,    15,     0,    16,    19,
+    16,    19,    16,    31,     0,    16,    19,    16,    19,    16,
+    15,     0,    18,     0,     6,     0,    18,     7,     0,     4,
+     0,     4,    20,     0,    16,     4,     0,    16,    21,    16,
+     0,    16,    21,    16,    21,    16,     0,    16,    15,    15,
+     0,    16,    12,    15,     0,    12,    16,     0,    12,    16,
+    20,    16,     0,    16,    12,     0,    16,    12,    16,     0,
+    29,     3,     0,    29,     0,    16,    17,     0,    15,    17,
+     0,    17,     0,    16,    13,     0,    15,    13,     0,    13,
+     0,    16,     5,     0,    15,     5,     0,     5,     0,    16,
+     8,     0,    15,     8,     0,     8,     0,    16,    11,     0,
+    15,    11,     0,    11,     0,    16,    14,     0,    15,    14,
+     0,    14,     0,    16,     0,     0,    10,     0
+};
+
+#endif
+
+#if YYDEBUG != 0
+static const short yyrline[] = { 0,
+   191,   192,   195,   198,   201,   204,   207,   210,   213,   219,
+   225,   234,   240,   252,   255,   258,   264,   268,   272,   278,
+   282,   300,   306,   312,   316,   321,   325,   332,   340,   343,
+   346,   349,   352,   355,   358,   361,   364,   367,   370,   373,
+   376,   379,   382,   385,   388,   391,   394,   399,   432,   436
+};
+#endif
+
+
+#if YYDEBUG != 0 || defined (YYERROR_VERBOSE)
+
+static const char * const yytname[] = {   "$","error","$undefined.","tAGO","tDAY",
+"tDAY_UNIT","tDAYZONE","tDST","tHOUR_UNIT","tID","tMERIDIAN","tMINUTE_UNIT",
+"tMONTH","tMONTH_UNIT","tSEC_UNIT","tSNUMBER","tUNUMBER","tYEAR_UNIT","tZONE",
+"':'","','","'/'","spec","item","time","zone","day","date","rel","relunit","number",
+"o_merid", NULL
+};
+#endif
+
+static const short yyr1[] = {     0,
+    22,    22,    23,    23,    23,    23,    23,    23,    24,    24,
+    24,    24,    24,    25,    25,    25,    26,    26,    26,    27,
+    27,    27,    27,    27,    27,    27,    27,    28,    28,    29,
+    29,    29,    29,    29,    29,    29,    29,    29,    29,    29,
+    29,    29,    29,    29,    29,    29,    29,    30,    31,    31
+};
+
+static const short yyr2[] = {     0,
+     0,     2,     1,     1,     1,     1,     1,     1,     2,     4,
+     4,     6,     6,     1,     1,     2,     1,     2,     2,     3,
+     5,     3,     3,     2,     4,     2,     3,     2,     1,     2,
+     2,     1,     2,     2,     1,     2,     2,     1,     2,     2,
+     1,     2,     2,     1,     2,     2,     1,     1,     0,     1
+};
+
+static const short yydefact[] = {     1,
+     0,    17,    38,    15,    41,    44,     0,    35,    47,     0,
+    48,    32,    14,     2,     3,     4,     6,     5,     7,    29,
+     8,    18,    24,    37,    40,    43,    34,    46,    31,    19,
+    36,    39,     9,    42,    26,    33,    45,     0,    30,     0,
+     0,    16,    28,     0,    23,    27,    22,    49,    20,    25,
+    50,    11,     0,    10,     0,    49,    21,    13,    12,     0,
+     0
+};
+
+static const short yydefgoto[] = {     1,
+    14,    15,    16,    17,    18,    19,    20,    21,    54
+};
+
+static const short yypact[] = {-32768,
+     0,   -19,-32768,-32768,-32768,-32768,   -13,-32768,-32768,    30,
+    15,-32768,    14,-32768,-32768,-32768,-32768,-32768,-32768,    19,
+-32768,-32768,     4,-32768,-32768,-32768,-32768,-32768,-32768,-32768,
+-32768,-32768,-32768,-32768,    -6,-32768,-32768,    16,-32768,    17,
+    23,-32768,-32768,    24,-32768,-32768,-32768,    27,    28,-32768,
+-32768,-32768,    29,-32768,    32,    -8,-32768,-32768,-32768,    50,
+-32768
+};
+
+static const short yypgoto[] = {-32768,
+-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,    -5
+};
+
+
+#define        YYLAST          51
+
+
+static const short yytable[] = {    60,
+    22,    51,    23,     2,     3,     4,    58,     5,    45,    46,
+     6,     7,     8,     9,    10,    11,    12,    13,    30,    31,
+    42,    43,    32,    44,    33,    34,    35,    36,    37,    38,
+    47,    39,    48,    40,    24,    41,    51,    25,    49,    50,
+    26,    52,    27,    28,    56,    53,    29,    57,    55,    61,
+    59
+};
+
+static const short yycheck[] = {     0,
+    20,    10,    16,     4,     5,     6,    15,     8,    15,    16,
+    11,    12,    13,    14,    15,    16,    17,    18,     4,     5,
+     7,     3,     8,    20,    10,    11,    12,    13,    14,    15,
+    15,    17,    16,    19,     5,    21,    10,     8,    16,    16,
+    11,    15,    13,    14,    16,    19,    17,    16,    21,     0,
+    56
+};
+/* -*-C-*-  Note some compilers choke on comments on `#line' lines.  */
+#line 3 "/usr/local/share/bison.simple"
+
+/* Skeleton output parser for bison,
+   Copyright (C) 1984, 1989, 1990 Free Software Foundation, Inc.
+
+   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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* As a special exception, when this file is copied by Bison into a
+   Bison output file, you may use that output file without restriction.
+   This special exception was added by the Free Software Foundation
+   in version 1.24 of Bison.  */
+
+#ifndef alloca
+#ifdef __GNUC__
+#define alloca __builtin_alloca
+#else /* not GNU C.  */
+#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi)
+#include <alloca.h>
+#else /* not sparc */
+#if defined (MSDOS) && !defined (__TURBOC__)
+#include <malloc.h>
+#else /* not MSDOS, or __TURBOC__ */
+#if defined(_AIX)
+#include <malloc.h>
+ #pragma alloca
+#else /* not MSDOS, __TURBOC__, or _AIX */
+#ifdef __hpux
+#include <sys/types.h>
+#ifdef _ULONG_T /* defined in <sys/types.h> on HP-UX 10 */
+#include <alloca.h>
+#else /* earlier HP-UX versions have alloca as a library function */
+#ifdef __cplusplus
+extern "C" {
+void *alloca (unsigned int);
+};
+#else /* not __cplusplus */
+#define alloca __builtin_alloca
+#endif /* not __cplusplus */
+#endif /* HP-UX <= 9 */
+#endif /* __hpux */
+#endif /* not _AIX */
+#endif /* not MSDOS, or __TURBOC__ */
+#endif /* not sparc.  */
+#endif /* not GNU C.  */
+#endif /* alloca not defined.  */
+
+/* This is the parser code that is written into each bison parser
+  when the %semantic_parser declaration is not specified in the grammar.
+  It was written by Richard Stallman by simplifying the hairy parser
+  used when %semantic_parser is specified.  */
+
+/* Note: there must be only one dollar sign in this file.
+   It is replaced by the list of actions, each action
+   as one case of the switch.  */
+
+#define yyerrok                (yyerrstatus = 0)
+#define yyclearin      (yychar = YYEMPTY)
+#define YYEMPTY                -2
+#define YYEOF          0
+#define YYACCEPT       return(0)
+#define YYABORT        return(1)
+#define YYERROR                goto yyerrlab1
+/* Like YYERROR except do call yyerror.
+   This remains here temporarily to ease the
+   transition to the new meaning of YYERROR, for GCC.
+   Once GCC version 2 has supplanted version 1, this can go.  */
+#define YYFAIL         goto yyerrlab
+#define YYRECOVERING()  (!!yyerrstatus)
+#define YYBACKUP(token, value) \
+do                                                             \
+  if (yychar == YYEMPTY && yylen == 1)                         \
+    { yychar = (token), yylval = (value);                      \
+      yychar1 = YYTRANSLATE (yychar);                          \
+      YYPOPSTACK;                                              \
+      goto yybackup;                                           \
+    }                                                          \
+  else                                                         \
+    { yyerror ("syntax error: cannot back up"); YYERROR; }     \
+while (0)
+
+#define YYTERROR       1
+#define YYERRCODE      256
+
+#ifndef YYPURE
+#define YYLEX          yylex()
+#endif
+
+#ifdef YYPURE
+#ifdef YYLSP_NEEDED
+#ifdef YYLEX_PARAM
+#define YYLEX          yylex(&yylval, &yylloc, YYLEX_PARAM)
+#else
+#define YYLEX          yylex(&yylval, &yylloc)
+#endif
+#else /* not YYLSP_NEEDED */
+#ifdef YYLEX_PARAM
+#define YYLEX          yylex(&yylval, YYLEX_PARAM)
+#else
+#define YYLEX          yylex(&yylval)
+#endif
+#endif /* not YYLSP_NEEDED */
+#endif
+
+/* If nonreentrant, generate the variables here */
+
+#ifndef YYPURE
+
+int    yychar;                 /*  the lookahead symbol                */
+YYSTYPE        yylval;                 /*  the semantic value of the           */
+                               /*  lookahead symbol                    */
+
+#ifdef YYLSP_NEEDED
+YYLTYPE yylloc;                        /*  location data for the lookahead     */
+                               /*  symbol                              */
+#endif
+
+int yynerrs;                   /*  number of parse errors so far       */
+#endif  /* not YYPURE */
+
+#if YYDEBUG != 0
+int yydebug;                   /*  nonzero means print parse trace     */
+/* Since this is uninitialized, it does not stop multiple parsers
+   from coexisting.  */
+#endif
+
+/*  YYINITDEPTH indicates the initial size of the parser's stacks      */
+
+#ifndef        YYINITDEPTH
+#define YYINITDEPTH 200
+#endif
+
+/*  YYMAXDEPTH is the maximum size the stacks can grow to
+    (effective only if the built-in stack extension method is used).  */
+
+#if YYMAXDEPTH == 0
+#undef YYMAXDEPTH
+#endif
+
+#ifndef YYMAXDEPTH
+#define YYMAXDEPTH 10000
+#endif
+
+/* Prevent warning if -Wstrict-prototypes.  */
+#ifdef __GNUC__
+int yyparse (void);
+#endif
+\f
+/* Define __yy_memcpy.  Note that the size argument
+   should be passed with type unsigned int, because that is what the non-GCC
+   definitions require.  With GCC, __builtin_memcpy takes an arg
+   of type size_t, but it can handle unsigned int.  */
+
+#if __GNUC__ > 1               /* GNU C and GNU C++ define this.  */
+#define __yy_memcpy(TO,FROM,COUNT)     __builtin_memcpy(TO,FROM,COUNT)
+#else                          /* not GNU C or C++ */
+#ifndef __cplusplus
+
+/* This is the most reliable way to avoid incompatibilities
+   in available built-in functions on various systems.  */
+static void
+__yy_memcpy (to, from, count)
+     char *to;
+     char *from;
+     unsigned int count;
+{
+  register char *f = from;
+  register char *t = to;
+  register int i = count;
+
+  while (i-- > 0)
+    *t++ = *f++;
+}
+
+#else /* __cplusplus */
+
+/* This is the most reliable way to avoid incompatibilities
+   in available built-in functions on various systems.  */
+static void
+__yy_memcpy (char *to, char *from, unsigned int count)
+{
+  register char *f = from;
+  register char *t = to;
+  register int i = count;
+
+  while (i-- > 0)
+    *t++ = *f++;
+}
+
+#endif
+#endif
+\f
+#line 196 "/usr/local/share/bison.simple"
+
+/* The user can define YYPARSE_PARAM as the name of an argument to be passed
+   into yyparse.  The argument should have type void *.
+   It should actually point to an object.
+   Grammar actions can access the variable by casting it
+   to the proper pointer type.  */
+
+#ifdef YYPARSE_PARAM
+#ifdef __cplusplus
+#define YYPARSE_PARAM_ARG void *YYPARSE_PARAM
+#define YYPARSE_PARAM_DECL
+#else /* not __cplusplus */
+#define YYPARSE_PARAM_ARG YYPARSE_PARAM
+#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM;
+#endif /* not __cplusplus */
+#else /* not YYPARSE_PARAM */
+#define YYPARSE_PARAM_ARG
+#define YYPARSE_PARAM_DECL
+#endif /* not YYPARSE_PARAM */
+
+int
+yyparse(YYPARSE_PARAM_ARG)
+     YYPARSE_PARAM_DECL
+{
+  register int yystate;
+  register int yyn;
+  register short *yyssp;
+  register YYSTYPE *yyvsp;
+  int yyerrstatus;     /*  number of tokens to shift before error messages enabled */
+  int yychar1 = 0;             /*  lookahead token as an internal (translated) token number */
+
+  short        yyssa[YYINITDEPTH];     /*  the state stack                     */
+  YYSTYPE yyvsa[YYINITDEPTH];  /*  the semantic value stack            */
+
+  short *yyss = yyssa;         /*  refer to the stacks thru separate pointers */
+  YYSTYPE *yyvs = yyvsa;       /*  to allow yyoverflow to reallocate them elsewhere */
+
+#ifdef YYLSP_NEEDED
+  YYLTYPE yylsa[YYINITDEPTH];  /*  the location stack                  */
+  YYLTYPE *yyls = yylsa;
+  YYLTYPE *yylsp;
+
+#define YYPOPSTACK   (yyvsp--, yyssp--, yylsp--)
+#else
+#define YYPOPSTACK   (yyvsp--, yyssp--)
+#endif
+
+  int yystacksize = YYINITDEPTH;
+
+#ifdef YYPURE
+  int yychar;
+  YYSTYPE yylval;
+  int yynerrs;
+#ifdef YYLSP_NEEDED
+  YYLTYPE yylloc;
+#endif
+#endif
+
+  YYSTYPE yyval;               /*  the variable used to return         */
+                               /*  semantic values from the action     */
+                               /*  routines                            */
+
+  int yylen;
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Starting parse\n");
+#endif
+
+  yystate = 0;
+  yyerrstatus = 0;
+  yynerrs = 0;
+  yychar = YYEMPTY;            /* Cause a token to be read.  */
+
+  /* Initialize stack pointers.
+     Waste one element of value and location stack
+     so that they stay on the same level as the state stack.
+     The wasted elements are never initialized.  */
+
+  yyssp = yyss - 1;
+  yyvsp = yyvs;
+#ifdef YYLSP_NEEDED
+  yylsp = yyls;
+#endif
+
+/* Push a new state, which is found in  yystate  .  */
+/* In all cases, when you get here, the value and location stacks
+   have just been pushed. so pushing a state here evens the stacks.  */
+yynewstate:
+
+  *++yyssp = yystate;
+
+  if (yyssp >= yyss + yystacksize - 1)
+    {
+      /* Give user a chance to reallocate the stack */
+      /* Use copies of these so that the &'s don't force the real ones into memory. */
+      YYSTYPE *yyvs1 = yyvs;
+      short *yyss1 = yyss;
+#ifdef YYLSP_NEEDED
+      YYLTYPE *yyls1 = yyls;
+#endif
+
+      /* Get the current used size of the three stacks, in elements.  */
+      int size = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+      /* Each stack pointer address is followed by the size of
+        the data in use in that stack, in bytes.  */
+#ifdef YYLSP_NEEDED
+      /* This used to be a conditional around just the two extra args,
+        but that might be undefined if yyoverflow is a macro.  */
+      yyoverflow("parser stack overflow",
+                &yyss1, size * sizeof (*yyssp),
+                &yyvs1, size * sizeof (*yyvsp),
+                &yyls1, size * sizeof (*yylsp),
+                &yystacksize);
+#else
+      yyoverflow("parser stack overflow",
+                &yyss1, size * sizeof (*yyssp),
+                &yyvs1, size * sizeof (*yyvsp),
+                &yystacksize);
+#endif
+
+      yyss = yyss1; yyvs = yyvs1;
+#ifdef YYLSP_NEEDED
+      yyls = yyls1;
+#endif
+#else /* no yyoverflow */
+      /* Extend the stack our own way.  */
+      if (yystacksize >= YYMAXDEPTH)
+       {
+         yyerror("parser stack overflow");
+         return 2;
+       }
+      yystacksize *= 2;
+      if (yystacksize > YYMAXDEPTH)
+       yystacksize = YYMAXDEPTH;
+      yyss = (short *) alloca (yystacksize * sizeof (*yyssp));
+      __yy_memcpy ((char *)yyss, (char *)yyss1,
+                  (unsigned int) size * sizeof (*yyssp));
+      yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp));
+      __yy_memcpy ((char *)yyvs, (char *)yyvs1,
+                  (unsigned int) size * sizeof (*yyvsp));
+#ifdef YYLSP_NEEDED
+      yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp));
+      __yy_memcpy ((char *)yyls, (char *)yyls1,
+                  (unsigned int) size * sizeof (*yylsp));
+#endif
+#endif /* no yyoverflow */
+
+      yyssp = yyss + size - 1;
+      yyvsp = yyvs + size - 1;
+#ifdef YYLSP_NEEDED
+      yylsp = yyls + size - 1;
+#endif
+
+#if YYDEBUG != 0
+      if (yydebug)
+       fprintf(stderr, "Stack size increased to %d\n", yystacksize);
+#endif
+
+      if (yyssp >= yyss + yystacksize - 1)
+       YYABORT;
+    }
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Entering state %d\n", yystate);
+#endif
+
+  goto yybackup;
+ yybackup:
+
+/* Do appropriate processing given the current state.  */
+/* Read a lookahead token if we need one and don't already have one.  */
+/* yyresume: */
+
+  /* First try to decide what to do without reference to lookahead token.  */
+
+  yyn = yypact[yystate];
+  if (yyn == YYFLAG)
+    goto yydefault;
+
+  /* Not known => get a lookahead token if don't already have one.  */
+
+  /* yychar is either YYEMPTY or YYEOF
+     or a valid token in external form.  */
+
+  if (yychar == YYEMPTY)
+    {
+#if YYDEBUG != 0
+      if (yydebug)
+       fprintf(stderr, "Reading a token: ");
+#endif
+      yychar = YYLEX;
+    }
+
+  /* Convert token to internal form (in yychar1) for indexing tables with */
+
+  if (yychar <= 0)             /* This means end of input. */
+    {
+      yychar1 = 0;
+      yychar = YYEOF;          /* Don't call YYLEX any more */
+
+#if YYDEBUG != 0
+      if (yydebug)
+       fprintf(stderr, "Now at end of input.\n");
+#endif
+    }
+  else
+    {
+      yychar1 = YYTRANSLATE(yychar);
+
+#if YYDEBUG != 0
+      if (yydebug)
+       {
+         fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]);
+         /* Give the individual parser a way to print the precise meaning
+            of a token, for further debugging info.  */
+#ifdef YYPRINT
+         YYPRINT (stderr, yychar, yylval);
+#endif
+         fprintf (stderr, ")\n");
+       }
+#endif
+    }
+
+  yyn += yychar1;
+  if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1)
+    goto yydefault;
+
+  yyn = yytable[yyn];
+
+  /* yyn is what to do for this token type in this state.
+     Negative => reduce, -yyn is rule number.
+     Positive => shift, yyn is new state.
+       New state is final state => don't bother to shift,
+       just return success.
+     0, or most negative number => error.  */
+
+  if (yyn < 0)
+    {
+      if (yyn == YYFLAG)
+       goto yyerrlab;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+  else if (yyn == 0)
+    goto yyerrlab;
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+  /* Shift the lookahead token.  */
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]);
+#endif
+
+  /* Discard the token being shifted unless it is eof.  */
+  if (yychar != YYEOF)
+    yychar = YYEMPTY;
+
+  *++yyvsp = yylval;
+#ifdef YYLSP_NEEDED
+  *++yylsp = yylloc;
+#endif
+
+  /* count tokens shifted since error; after three, turn off error status.  */
+  if (yyerrstatus) yyerrstatus--;
+
+  yystate = yyn;
+  goto yynewstate;
+
+/* Do the default action for the current state.  */
+yydefault:
+
+  yyn = yydefact[yystate];
+  if (yyn == 0)
+    goto yyerrlab;
+
+/* Do a reduction.  yyn is the number of a rule to reduce with.  */
+yyreduce:
+  yylen = yyr2[yyn];
+  if (yylen > 0)
+    yyval = yyvsp[1-yylen]; /* implement default value of the action */
+
+#if YYDEBUG != 0
+  if (yydebug)
+    {
+      int i;
+
+      fprintf (stderr, "Reducing via rule %d (line %d), ",
+              yyn, yyrline[yyn]);
+
+      /* Print the symbols being reduced, and their result.  */
+      for (i = yyprhs[yyn]; yyrhs[i] > 0; i++)
+       fprintf (stderr, "%s ", yytname[yyrhs[i]]);
+      fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]);
+    }
+#endif
+
+
+  switch (yyn) {
+
+case 3:
+#line 195 "getdate.y"
+{
+           yyHaveTime++;
+       ;
+    break;}
+case 4:
+#line 198 "getdate.y"
+{
+           yyHaveZone++;
+       ;
+    break;}
+case 5:
+#line 201 "getdate.y"
+{
+           yyHaveDate++;
+       ;
+    break;}
+case 6:
+#line 204 "getdate.y"
+{
+           yyHaveDay++;
+       ;
+    break;}
+case 7:
+#line 207 "getdate.y"
+{
+           yyHaveRel++;
+       ;
+    break;}
+case 9:
+#line 213 "getdate.y"
+{
+           yyHour = yyvsp[-1].Number;
+           yyMinutes = 0;
+           yySeconds = 0;
+           yyMeridian = yyvsp[0].Meridian;
+       ;
+    break;}
+case 10:
+#line 219 "getdate.y"
+{
+           yyHour = yyvsp[-3].Number;
+           yyMinutes = yyvsp[-1].Number;
+           yySeconds = 0;
+           yyMeridian = yyvsp[0].Meridian;
+       ;
+    break;}
+case 11:
+#line 225 "getdate.y"
+{
+           yyHour = yyvsp[-3].Number;
+           yyMinutes = yyvsp[-1].Number;
+           yyMeridian = MER24;
+           yyHaveZone++;
+           yyTimezone = (yyvsp[0].Number < 0
+                         ? -yyvsp[0].Number % 100 + (-yyvsp[0].Number / 100) * 60
+                         : - (yyvsp[0].Number % 100 + (yyvsp[0].Number / 100) * 60));
+       ;
+    break;}
+case 12:
+#line 234 "getdate.y"
+{
+           yyHour = yyvsp[-5].Number;
+           yyMinutes = yyvsp[-3].Number;
+           yySeconds = yyvsp[-1].Number;
+           yyMeridian = yyvsp[0].Meridian;
+       ;
+    break;}
+case 13:
+#line 240 "getdate.y"
+{
+           yyHour = yyvsp[-5].Number;
+           yyMinutes = yyvsp[-3].Number;
+           yySeconds = yyvsp[-1].Number;
+           yyMeridian = MER24;
+           yyHaveZone++;
+           yyTimezone = (yyvsp[0].Number < 0
+                         ? -yyvsp[0].Number % 100 + (-yyvsp[0].Number / 100) * 60
+                         : - (yyvsp[0].Number % 100 + (yyvsp[0].Number / 100) * 60));
+       ;
+    break;}
+case 14:
+#line 252 "getdate.y"
+{
+           yyTimezone = yyvsp[0].Number;
+       ;
+    break;}
+case 15:
+#line 255 "getdate.y"
+{
+           yyTimezone = yyvsp[0].Number - 60;
+       ;
+    break;}
+case 16:
+#line 259 "getdate.y"
+{
+           yyTimezone = yyvsp[-1].Number - 60;
+       ;
+    break;}
+case 17:
+#line 264 "getdate.y"
+{
+           yyDayOrdinal = 1;
+           yyDayNumber = yyvsp[0].Number;
+       ;
+    break;}
+case 18:
+#line 268 "getdate.y"
+{
+           yyDayOrdinal = 1;
+           yyDayNumber = yyvsp[-1].Number;
+       ;
+    break;}
+case 19:
+#line 272 "getdate.y"
+{
+           yyDayOrdinal = yyvsp[-1].Number;
+           yyDayNumber = yyvsp[0].Number;
+       ;
+    break;}
+case 20:
+#line 278 "getdate.y"
+{
+           yyMonth = yyvsp[-2].Number;
+           yyDay = yyvsp[0].Number;
+       ;
+    break;}
+case 21:
+#line 282 "getdate.y"
+{
+         /* Interpret as YYYY/MM/DD if $1 >= 1000, otherwise as MM/DD/YY.
+            The goal in recognizing YYYY/MM/DD is solely to support legacy
+            machine-generated dates like those in an RCS log listing.  If
+            you want portability, use the ISO 8601 format.  */
+         if (yyvsp[-4].Number >= 1000)
+           {
+             yyYear = yyvsp[-4].Number;
+             yyMonth = yyvsp[-2].Number;
+             yyDay = yyvsp[0].Number;
+           }
+         else
+           {
+             yyMonth = yyvsp[-4].Number;
+             yyDay = yyvsp[-2].Number;
+             yyYear = yyvsp[0].Number;
+           }
+       ;
+    break;}
+case 22:
+#line 300 "getdate.y"
+{
+           /* ISO 8601 format.  yyyy-mm-dd.  */
+           yyYear = yyvsp[-2].Number;
+           yyMonth = -yyvsp[-1].Number;
+           yyDay = -yyvsp[0].Number;
+       ;
+    break;}
+case 23:
+#line 306 "getdate.y"
+{
+           /* e.g. 17-JUN-1992.  */
+           yyDay = yyvsp[-2].Number;
+           yyMonth = yyvsp[-1].Number;
+           yyYear = -yyvsp[0].Number;
+       ;
+    break;}
+case 24:
+#line 312 "getdate.y"
+{
+           yyMonth = yyvsp[-1].Number;
+           yyDay = yyvsp[0].Number;
+       ;
+    break;}
+case 25:
+#line 316 "getdate.y"
+{
+           yyMonth = yyvsp[-3].Number;
+           yyDay = yyvsp[-2].Number;
+           yyYear = yyvsp[0].Number;
+       ;
+    break;}
+case 26:
+#line 321 "getdate.y"
+{
+           yyMonth = yyvsp[0].Number;
+           yyDay = yyvsp[-1].Number;
+       ;
+    break;}
+case 27:
+#line 325 "getdate.y"
+{
+           yyMonth = yyvsp[-1].Number;
+           yyDay = yyvsp[-2].Number;
+           yyYear = yyvsp[0].Number;
+       ;
+    break;}
+case 28:
+#line 332 "getdate.y"
+{
+           yyRelSeconds = -yyRelSeconds;
+           yyRelMinutes = -yyRelMinutes;
+           yyRelHour = -yyRelHour;
+           yyRelDay = -yyRelDay;
+           yyRelMonth = -yyRelMonth;
+           yyRelYear = -yyRelYear;
+       ;
+    break;}
+case 30:
+#line 343 "getdate.y"
+{
+           yyRelYear += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 31:
+#line 346 "getdate.y"
+{
+           yyRelYear += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 32:
+#line 349 "getdate.y"
+{
+           yyRelYear += yyvsp[0].Number;
+       ;
+    break;}
+case 33:
+#line 352 "getdate.y"
+{
+           yyRelMonth += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 34:
+#line 355 "getdate.y"
+{
+           yyRelMonth += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 35:
+#line 358 "getdate.y"
+{
+           yyRelMonth += yyvsp[0].Number;
+       ;
+    break;}
+case 36:
+#line 361 "getdate.y"
+{
+           yyRelDay += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 37:
+#line 364 "getdate.y"
+{
+           yyRelDay += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 38:
+#line 367 "getdate.y"
+{
+           yyRelDay += yyvsp[0].Number;
+       ;
+    break;}
+case 39:
+#line 370 "getdate.y"
+{
+           yyRelHour += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 40:
+#line 373 "getdate.y"
+{
+           yyRelHour += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 41:
+#line 376 "getdate.y"
+{
+           yyRelHour += yyvsp[0].Number;
+       ;
+    break;}
+case 42:
+#line 379 "getdate.y"
+{
+           yyRelMinutes += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 43:
+#line 382 "getdate.y"
+{
+           yyRelMinutes += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 44:
+#line 385 "getdate.y"
+{
+           yyRelMinutes += yyvsp[0].Number;
+       ;
+    break;}
+case 45:
+#line 388 "getdate.y"
+{
+           yyRelSeconds += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 46:
+#line 391 "getdate.y"
+{
+           yyRelSeconds += yyvsp[-1].Number * yyvsp[0].Number;
+       ;
+    break;}
+case 47:
+#line 394 "getdate.y"
+{
+           yyRelSeconds += yyvsp[0].Number;
+       ;
+    break;}
+case 48:
+#line 400 "getdate.y"
+{
+           if (yyHaveTime && yyHaveDate && !yyHaveRel)
+             yyYear = yyvsp[0].Number;
+           else
+             {
+               if (yyvsp[0].Number>10000)
+                 {
+                   yyHaveDate++;
+                   yyDay= (yyvsp[0].Number)%100;
+                   yyMonth= (yyvsp[0].Number/100)%100;
+                   yyYear = yyvsp[0].Number/10000;
+                 }
+               else
+                 {
+                   yyHaveTime++;
+                   if (yyvsp[0].Number < 100)
+                     {
+                       yyHour = yyvsp[0].Number;
+                       yyMinutes = 0;
+                     }
+                   else
+                     {
+                       yyHour = yyvsp[0].Number / 100;
+                       yyMinutes = yyvsp[0].Number % 100;
+                     }
+                   yySeconds = 0;
+                   yyMeridian = MER24;
+                 }
+             }
+         ;
+    break;}
+case 49:
+#line 433 "getdate.y"
+{
+           yyval.Meridian = MER24;
+         ;
+    break;}
+case 50:
+#line 437 "getdate.y"
+{
+           yyval.Meridian = yyvsp[0].Meridian;
+         ;
+    break;}
+}
+   /* the action file gets copied in in place of this dollarsign */
+#line 498 "/usr/local/share/bison.simple"
+\f
+  yyvsp -= yylen;
+  yyssp -= yylen;
+#ifdef YYLSP_NEEDED
+  yylsp -= yylen;
+#endif
+
+#if YYDEBUG != 0
+  if (yydebug)
+    {
+      short *ssp1 = yyss - 1;
+      fprintf (stderr, "state stack now");
+      while (ssp1 != yyssp)
+       fprintf (stderr, " %d", *++ssp1);
+      fprintf (stderr, "\n");
+    }
+#endif
+
+  *++yyvsp = yyval;
+
+#ifdef YYLSP_NEEDED
+  yylsp++;
+  if (yylen == 0)
+    {
+      yylsp->first_line = yylloc.first_line;
+      yylsp->first_column = yylloc.first_column;
+      yylsp->last_line = (yylsp-1)->last_line;
+      yylsp->last_column = (yylsp-1)->last_column;
+      yylsp->text = 0;
+    }
+  else
+    {
+      yylsp->last_line = (yylsp+yylen-1)->last_line;
+      yylsp->last_column = (yylsp+yylen-1)->last_column;
+    }
+#endif
+
+  /* Now "shift" the result of the reduction.
+     Determine what state that goes to,
+     based on the state we popped back to
+     and the rule number reduced by.  */
+
+  yyn = yyr1[yyn];
+
+  yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
+  if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+    yystate = yytable[yystate];
+  else
+    yystate = yydefgoto[yyn - YYNTBASE];
+
+  goto yynewstate;
+
+yyerrlab:   /* here on detecting error */
+
+  if (! yyerrstatus)
+    /* If not already recovering from an error, report this error.  */
+    {
+      ++yynerrs;
+
+#ifdef YYERROR_VERBOSE
+      yyn = yypact[yystate];
+
+      if (yyn > YYFLAG && yyn < YYLAST)
+       {
+         int size = 0;
+         char *msg;
+         int x, count;
+
+         count = 0;
+         /* Start X at -yyn if nec to avoid negative indexes in yycheck.  */
+         for (x = (yyn < 0 ? -yyn : 0);
+              x < (sizeof(yytname) / sizeof(char *)); x++)
+           if (yycheck[x + yyn] == x)
+             size += strlen(yytname[x]) + 15, count++;
+         msg = (char *) malloc(size + 15);
+         if (msg != 0)
+           {
+             strcpy(msg, "parse error");
+
+             if (count < 5)
+               {
+                 count = 0;
+                 for (x = (yyn < 0 ? -yyn : 0);
+                      x < (sizeof(yytname) / sizeof(char *)); x++)
+                   if (yycheck[x + yyn] == x)
+                     {
+                       strcat(msg, count == 0 ? ", expecting `" : " or `");
+                       strcat(msg, yytname[x]);
+                       strcat(msg, "'");
+                       count++;
+                     }
+               }
+             yyerror(msg);
+             free(msg);
+           }
+         else
+           yyerror ("parse error; also virtual memory exceeded");
+       }
+      else
+#endif /* YYERROR_VERBOSE */
+       yyerror("parse error");
+    }
+
+  goto yyerrlab1;
+yyerrlab1:   /* here on error raised explicitly by an action */
+
+  if (yyerrstatus == 3)
+    {
+      /* if just tried and failed to reuse lookahead token after an error, discard it.  */
+
+      /* return failure if at end of input */
+      if (yychar == YYEOF)
+       YYABORT;
+
+#if YYDEBUG != 0
+      if (yydebug)
+       fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]);
+#endif
+
+      yychar = YYEMPTY;
+    }
+
+  /* Else will try to reuse lookahead token
+     after shifting the error token.  */
+
+  yyerrstatus = 3;             /* Each real token shifted decrements this */
+
+  goto yyerrhandle;
+
+yyerrdefault:  /* current state does not do anything special for the error token. */
+
+#if 0
+  /* This is wrong; only states that explicitly want error tokens
+     should shift them.  */
+  yyn = yydefact[yystate];  /* If its default is to accept any token, ok.  Otherwise pop it.*/
+  if (yyn) goto yydefault;
+#endif
+
+yyerrpop:   /* pop the current state because it cannot handle the error token */
+
+  if (yyssp == yyss) YYABORT;
+  yyvsp--;
+  yystate = *--yyssp;
+#ifdef YYLSP_NEEDED
+  yylsp--;
+#endif
+
+#if YYDEBUG != 0
+  if (yydebug)
+    {
+      short *ssp1 = yyss - 1;
+      fprintf (stderr, "Error: state stack now");
+      while (ssp1 != yyssp)
+       fprintf (stderr, " %d", *++ssp1);
+      fprintf (stderr, "\n");
+    }
+#endif
+
+yyerrhandle:
+
+  yyn = yypact[yystate];
+  if (yyn == YYFLAG)
+    goto yyerrdefault;
+
+  yyn += YYTERROR;
+  if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR)
+    goto yyerrdefault;
+
+  yyn = yytable[yyn];
+  if (yyn < 0)
+    {
+      if (yyn == YYFLAG)
+       goto yyerrpop;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+  else if (yyn == 0)
+    goto yyerrpop;
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Shifting error token, ");
+#endif
+
+  *++yyvsp = yylval;
+#ifdef YYLSP_NEEDED
+  *++yylsp = yylloc;
+#endif
+
+  yystate = yyn;
+  goto yynewstate;
+}
+#line 442 "getdate.y"
+
+
+/* Month and day table. */
+static TABLE const MonthDayTable[] = {
+    { "january",       tMONTH,  1 },
+    { "february",      tMONTH,  2 },
+    { "march",         tMONTH,  3 },
+    { "april",         tMONTH,  4 },
+    { "may",           tMONTH,  5 },
+    { "june",          tMONTH,  6 },
+    { "july",          tMONTH,  7 },
+    { "august",                tMONTH,  8 },
+    { "september",     tMONTH,  9 },
+    { "sept",          tMONTH,  9 },
+    { "october",       tMONTH, 10 },
+    { "november",      tMONTH, 11 },
+    { "december",      tMONTH, 12 },
+    { "sunday",                tDAY, 0 },
+    { "monday",                tDAY, 1 },
+    { "tuesday",       tDAY, 2 },
+    { "tues",          tDAY, 2 },
+    { "wednesday",     tDAY, 3 },
+    { "wednes",                tDAY, 3 },
+    { "thursday",      tDAY, 4 },
+    { "thur",          tDAY, 4 },
+    { "thurs",         tDAY, 4 },
+    { "friday",                tDAY, 5 },
+    { "saturday",      tDAY, 6 },
+    { NULL }
+};
+
+/* Time units table. */
+static TABLE const UnitsTable[] = {
+    { "year",          tYEAR_UNIT,     1 },
+    { "month",         tMONTH_UNIT,    1 },
+    { "fortnight",     tDAY_UNIT,      14 },
+    { "week",          tDAY_UNIT,      7 },
+    { "day",           tDAY_UNIT,      1 },
+    { "hour",          tHOUR_UNIT,     1 },
+    { "minute",                tMINUTE_UNIT,   1 },
+    { "min",           tMINUTE_UNIT,   1 },
+    { "second",                tSEC_UNIT,      1 },
+    { "sec",           tSEC_UNIT,      1 },
+    { NULL }
+};
+
+/* Assorted relative-time words. */
+static TABLE const OtherTable[] = {
+    { "tomorrow",      tMINUTE_UNIT,   1 * 24 * 60 },
+    { "yesterday",     tMINUTE_UNIT,   -1 * 24 * 60 },
+    { "today",         tMINUTE_UNIT,   0 },
+    { "now",           tMINUTE_UNIT,   0 },
+    { "last",          tUNUMBER,       -1 },
+    { "this",          tMINUTE_UNIT,   0 },
+    { "next",          tUNUMBER,       2 },
+    { "first",         tUNUMBER,       1 },
+/*  { "second",                tUNUMBER,       2 }, */
+    { "third",         tUNUMBER,       3 },
+    { "fourth",                tUNUMBER,       4 },
+    { "fifth",         tUNUMBER,       5 },
+    { "sixth",         tUNUMBER,       6 },
+    { "seventh",       tUNUMBER,       7 },
+    { "eighth",                tUNUMBER,       8 },
+    { "ninth",         tUNUMBER,       9 },
+    { "tenth",         tUNUMBER,       10 },
+    { "eleventh",      tUNUMBER,       11 },
+    { "twelfth",       tUNUMBER,       12 },
+    { "ago",           tAGO,   1 },
+    { NULL }
+};
+
+/* The timezone table. */
+static TABLE const TimezoneTable[] = {
+    { "gmt",   tZONE,     HOUR ( 0) }, /* Greenwich Mean */
+    { "ut",    tZONE,     HOUR ( 0) }, /* Universal (Coordinated) */
+    { "utc",   tZONE,     HOUR ( 0) },
+    { "wet",   tZONE,     HOUR ( 0) }, /* Western European */
+    { "bst",   tDAYZONE,  HOUR ( 0) }, /* British Summer */
+    { "wat",   tZONE,     HOUR ( 1) }, /* West Africa */
+    { "at",    tZONE,     HOUR ( 2) }, /* Azores */
+#if    0
+    /* For completeness.  BST is also British Summer, and GST is
+     * also Guam Standard. */
+    { "bst",   tZONE,     HOUR ( 3) }, /* Brazil Standard */
+    { "gst",   tZONE,     HOUR ( 3) }, /* Greenland Standard */
+#endif
+#if 0
+    { "nft",   tZONE,     HOUR (3.5) },        /* Newfoundland */
+    { "nst",   tZONE,     HOUR (3.5) },        /* Newfoundland Standard */
+    { "ndt",   tDAYZONE,  HOUR (3.5) },        /* Newfoundland Daylight */
+#endif
+    { "ast",   tZONE,     HOUR ( 4) }, /* Atlantic Standard */
+    { "adt",   tDAYZONE,  HOUR ( 4) }, /* Atlantic Daylight */
+    { "est",   tZONE,     HOUR ( 5) }, /* Eastern Standard */
+    { "edt",   tDAYZONE,  HOUR ( 5) }, /* Eastern Daylight */
+    { "cst",   tZONE,     HOUR ( 6) }, /* Central Standard */
+    { "cdt",   tDAYZONE,  HOUR ( 6) }, /* Central Daylight */
+    { "mst",   tZONE,     HOUR ( 7) }, /* Mountain Standard */
+    { "mdt",   tDAYZONE,  HOUR ( 7) }, /* Mountain Daylight */
+    { "pst",   tZONE,     HOUR ( 8) }, /* Pacific Standard */
+    { "pdt",   tDAYZONE,  HOUR ( 8) }, /* Pacific Daylight */
+    { "yst",   tZONE,     HOUR ( 9) }, /* Yukon Standard */
+    { "ydt",   tDAYZONE,  HOUR ( 9) }, /* Yukon Daylight */
+    { "hst",   tZONE,     HOUR (10) }, /* Hawaii Standard */
+    { "hdt",   tDAYZONE,  HOUR (10) }, /* Hawaii Daylight */
+    { "cat",   tZONE,     HOUR (10) }, /* Central Alaska */
+    { "ahst",  tZONE,     HOUR (10) }, /* Alaska-Hawaii Standard */
+    { "nt",    tZONE,     HOUR (11) }, /* Nome */
+    { "idlw",  tZONE,     HOUR (12) }, /* International Date Line West */
+    { "cet",   tZONE,     -HOUR (1) }, /* Central European */
+    { "met",   tZONE,     -HOUR (1) }, /* Middle European */
+    { "mewt",  tZONE,     -HOUR (1) }, /* Middle European Winter */
+    { "mest",  tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
+    { "mesz",  tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
+    { "swt",   tZONE,     -HOUR (1) }, /* Swedish Winter */
+    { "sst",   tDAYZONE,  -HOUR (1) }, /* Swedish Summer */
+    { "fwt",   tZONE,     -HOUR (1) }, /* French Winter */
+    { "fst",   tDAYZONE,  -HOUR (1) }, /* French Summer */
+    { "eet",   tZONE,     -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
+    { "bt",    tZONE,     -HOUR (3) }, /* Baghdad, USSR Zone 2 */
+#if 0
+    { "it",    tZONE,     -HOUR (3.5) },/* Iran */
+#endif
+    { "zp4",   tZONE,     -HOUR (4) }, /* USSR Zone 3 */
+    { "zp5",   tZONE,     -HOUR (5) }, /* USSR Zone 4 */
+#if 0
+    { "ist",   tZONE,     -HOUR (5.5) },/* Indian Standard */
+#endif
+    { "zp6",   tZONE,     -HOUR (6) }, /* USSR Zone 5 */
+#if    0
+    /* For completeness.  NST is also Newfoundland Standard, and SST is
+     * also Swedish Summer. */
+    { "nst",   tZONE,     -HOUR (6.5) },/* North Sumatra */
+    { "sst",   tZONE,     -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
+#endif /* 0 */
+    { "wast",  tZONE,     -HOUR (7) }, /* West Australian Standard */
+    { "wadt",  tDAYZONE,  -HOUR (7) }, /* West Australian Daylight */
+#if 0
+    { "jt",    tZONE,     -HOUR (7.5) },/* Java (3pm in Cronusland!) */
+#endif
+    { "cct",   tZONE,     -HOUR (8) }, /* China Coast, USSR Zone 7 */
+    { "jst",   tZONE,     -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
+#if 0
+    { "cast",  tZONE,     -HOUR (9.5) },/* Central Australian Standard */
+    { "cadt",  tDAYZONE,  -HOUR (9.5) },/* Central Australian Daylight */
+#endif
+    { "east",  tZONE,     -HOUR (10) },        /* Eastern Australian Standard */
+    { "eadt",  tDAYZONE,  -HOUR (10) },        /* Eastern Australian Daylight */
+    { "gst",   tZONE,     -HOUR (10) },        /* Guam Standard, USSR Zone 9 */
+    { "nzt",   tZONE,     -HOUR (12) },        /* New Zealand */
+    { "nzst",  tZONE,     -HOUR (12) },        /* New Zealand Standard */
+    { "nzdt",  tDAYZONE,  -HOUR (12) },        /* New Zealand Daylight */
+    { "idle",  tZONE,     -HOUR (12) },        /* International Date Line East */
+    {  NULL  }
+};
+
+/* Military timezone table. */
+static TABLE const MilitaryTable[] = {
+    { "a",     tZONE,  HOUR (  1) },
+    { "b",     tZONE,  HOUR (  2) },
+    { "c",     tZONE,  HOUR (  3) },
+    { "d",     tZONE,  HOUR (  4) },
+    { "e",     tZONE,  HOUR (  5) },
+    { "f",     tZONE,  HOUR (  6) },
+    { "g",     tZONE,  HOUR (  7) },
+    { "h",     tZONE,  HOUR (  8) },
+    { "i",     tZONE,  HOUR (  9) },
+    { "k",     tZONE,  HOUR ( 10) },
+    { "l",     tZONE,  HOUR ( 11) },
+    { "m",     tZONE,  HOUR ( 12) },
+    { "n",     tZONE,  HOUR (- 1) },
+    { "o",     tZONE,  HOUR (- 2) },
+    { "p",     tZONE,  HOUR (- 3) },
+    { "q",     tZONE,  HOUR (- 4) },
+    { "r",     tZONE,  HOUR (- 5) },
+    { "s",     tZONE,  HOUR (- 6) },
+    { "t",     tZONE,  HOUR (- 7) },
+    { "u",     tZONE,  HOUR (- 8) },
+    { "v",     tZONE,  HOUR (- 9) },
+    { "w",     tZONE,  HOUR (-10) },
+    { "x",     tZONE,  HOUR (-11) },
+    { "y",     tZONE,  HOUR (-12) },
+    { "z",     tZONE,  HOUR (  0) },
+    { NULL }
+};
+
+\f
+
+
+/* ARGSUSED */
+static int
+yyerror (s)
+     char *s;
+{
+  return 0;
+}
+
+static int
+ToHour (Hours, Meridian)
+     int Hours;
+     MERIDIAN Meridian;
+{
+  switch (Meridian)
+    {
+    case MER24:
+      if (Hours < 0 || Hours > 23)
+       return -1;
+      return Hours;
+    case MERam:
+      if (Hours < 1 || Hours > 12)
+       return -1;
+      if (Hours == 12)
+       Hours = 0;
+      return Hours;
+    case MERpm:
+      if (Hours < 1 || Hours > 12)
+       return -1;
+      if (Hours == 12)
+       Hours = 0;
+      return Hours + 12;
+    default:
+      abort ();
+    }
+  /* NOTREACHED */
+}
+
+static int
+ToYear (Year)
+     int Year;
+{
+  if (Year < 0)
+    Year = -Year;
+
+  /* XPG4 suggests that years 00-68 map to 2000-2068, and
+     years 69-99 map to 1969-1999.  */
+  if (Year < 69)
+    Year += 2000;
+  else if (Year < 100)
+    Year += 1900;
+
+  return Year;
+}
+
+static int
+LookupWord (buff)
+     char *buff;
+{
+  register char *p;
+  register char *q;
+  register const TABLE *tp;
+  int i;
+  int abbrev;
+
+  /* Make it lowercase. */
+  for (p = buff; *p; p++)
+    if (ISUPPER (*p))
+      *p = tolower (*p);
+
+  if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0)
+    {
+      yylval.Meridian = MERam;
+      return tMERIDIAN;
+    }
+  if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0)
+    {
+      yylval.Meridian = MERpm;
+      return tMERIDIAN;
+    }
+
+  /* See if we have an abbreviation for a month. */
+  if (strlen (buff) == 3)
+    abbrev = 1;
+  else if (strlen (buff) == 4 && buff[3] == '.')
+    {
+      abbrev = 1;
+      buff[3] = '\0';
+    }
+  else
+    abbrev = 0;
+
+  for (tp = MonthDayTable; tp->name; tp++)
+    {
+      if (abbrev)
+       {
+         if (strncmp (buff, tp->name, 3) == 0)
+           {
+             yylval.Number = tp->value;
+             return tp->type;
+           }
+       }
+      else if (strcmp (buff, tp->name) == 0)
+       {
+         yylval.Number = tp->value;
+         return tp->type;
+       }
+    }
+
+  for (tp = TimezoneTable; tp->name; tp++)
+    if (strcmp (buff, tp->name) == 0)
+      {
+       yylval.Number = tp->value;
+       return tp->type;
+      }
+
+  if (strcmp (buff, "dst") == 0)
+    return tDST;
+
+  for (tp = UnitsTable; tp->name; tp++)
+    if (strcmp (buff, tp->name) == 0)
+      {
+       yylval.Number = tp->value;
+       return tp->type;
+      }
+
+  /* Strip off any plural and try the units table again. */
+  i = strlen (buff) - 1;
+  if (buff[i] == 's')
+    {
+      buff[i] = '\0';
+      for (tp = UnitsTable; tp->name; tp++)
+       if (strcmp (buff, tp->name) == 0)
+         {
+           yylval.Number = tp->value;
+           return tp->type;
+         }
+      buff[i] = 's';           /* Put back for "this" in OtherTable. */
+    }
+
+  for (tp = OtherTable; tp->name; tp++)
+    if (strcmp (buff, tp->name) == 0)
+      {
+       yylval.Number = tp->value;
+       return tp->type;
+      }
+
+  /* Military timezones. */
+  if (buff[1] == '\0' && ISALPHA (*buff))
+    {
+      for (tp = MilitaryTable; tp->name; tp++)
+       if (strcmp (buff, tp->name) == 0)
+         {
+           yylval.Number = tp->value;
+           return tp->type;
+         }
+    }
+
+  /* Drop out any periods and try the timezone table again. */
+  for (i = 0, p = q = buff; *q; q++)
+    if (*q != '.')
+      *p++ = *q;
+    else
+      i++;
+  *p = '\0';
+  if (i)
+    for (tp = TimezoneTable; tp->name; tp++)
+      if (strcmp (buff, tp->name) == 0)
+       {
+         yylval.Number = tp->value;
+         return tp->type;
+       }
+
+  return tID;
+}
+
+static int
+yylex ()
+{
+  register char c;
+  register char *p;
+  char buff[20];
+  int Count;
+  int sign;
+
+  for (;;)
+    {
+      while (ISSPACE (*yyInput))
+       yyInput++;
+
+      if (ISDIGIT (c = *yyInput) || c == '-' || c == '+')
+       {
+         if (c == '-' || c == '+')
+           {
+             sign = c == '-' ? -1 : 1;
+             if (!ISDIGIT (*++yyInput))
+               /* skip the '-' sign */
+               continue;
+           }
+         else
+           sign = 0;
+         for (yylval.Number = 0; ISDIGIT (c = *yyInput++);)
+           yylval.Number = 10 * yylval.Number + c - '0';
+         yyInput--;
+         if (sign < 0)
+           yylval.Number = -yylval.Number;
+         return sign ? tSNUMBER : tUNUMBER;
+       }
+      if (ISALPHA (c))
+       {
+         for (p = buff; (c = *yyInput++, ISALPHA (c)) || c == '.';)
+           if (p < &buff[sizeof buff - 1])
+             *p++ = c;
+         *p = '\0';
+         yyInput--;
+         return LookupWord (buff);
+       }
+      if (c != '(')
+       return *yyInput++;
+      Count = 0;
+      do
+       {
+         c = *yyInput++;
+         if (c == '\0')
+           return c;
+         if (c == '(')
+           Count++;
+         else if (c == ')')
+           Count--;
+       }
+      while (Count > 0);
+    }
+}
+
+#define TM_YEAR_ORIGIN 1900
+
+/* Yield A - B, measured in seconds.  */
+static long
+difftm (a, b)
+     struct tm *a, *b;
+{
+  int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
+  int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
+  long days = (
+  /* difference in day of year */
+               a->tm_yday - b->tm_yday
+  /* + intervening leap days */
+               + ((ay >> 2) - (by >> 2))
+               - (ay / 100 - by / 100)
+               + ((ay / 100 >> 2) - (by / 100 >> 2))
+  /* + difference in years * 365 */
+               + (long) (ay - by) * 365
+  );
+  return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+               + (a->tm_min - b->tm_min))
+         + (a->tm_sec - b->tm_sec));
+}
+
+time_t
+get_date (p, now)
+     const char *p;
+     const time_t *now;
+{
+  struct tm tm, tm0, *tmp;
+  time_t Start;
+
+  yyInput = p;
+  Start = now ? *now : time ((time_t *) NULL);
+  tmp = localtime (&Start);
+  yyYear = tmp->tm_year + TM_YEAR_ORIGIN;
+  yyMonth = tmp->tm_mon + 1;
+  yyDay = tmp->tm_mday;
+  yyHour = tmp->tm_hour;
+  yyMinutes = tmp->tm_min;
+  yySeconds = tmp->tm_sec;
+  yyMeridian = MER24;
+  yyRelSeconds = 0;
+  yyRelMinutes = 0;
+  yyRelHour = 0;
+  yyRelDay = 0;
+  yyRelMonth = 0;
+  yyRelYear = 0;
+  yyHaveDate = 0;
+  yyHaveDay = 0;
+  yyHaveRel = 0;
+  yyHaveTime = 0;
+  yyHaveZone = 0;
+
+  if (yyparse ()
+      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
+    return -1;
+
+  tm.tm_year = ToYear (yyYear) - TM_YEAR_ORIGIN + yyRelYear;
+  tm.tm_mon = yyMonth - 1 + yyRelMonth;
+  tm.tm_mday = yyDay + yyRelDay;
+  if (yyHaveTime || (yyHaveRel && !yyHaveDate && !yyHaveDay))
+    {
+      tm.tm_hour = ToHour (yyHour, yyMeridian);
+      if (tm.tm_hour < 0)
+       return -1;
+      tm.tm_min = yyMinutes;
+      tm.tm_sec = yySeconds;
+    }
+  else
+    {
+      tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+    }
+  tm.tm_hour += yyRelHour;
+  tm.tm_min += yyRelMinutes;
+  tm.tm_sec += yyRelSeconds;
+  tm.tm_isdst = -1;
+  tm0 = tm;
+
+  Start = mktime (&tm);
+
+  if (Start == (time_t) -1)
+    {
+
+      /* Guard against falsely reporting errors near the time_t boundaries
+         when parsing times in other time zones.  For example, if the min
+         time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
+         of UTC, then the min localtime value is 1970-01-01 08:00:00; if
+         we apply mktime to 1970-01-01 00:00:00 we will get an error, so
+         we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
+         zone by 24 hours to compensate.  This algorithm assumes that
+         there is no DST transition within a day of the time_t boundaries.  */
+      if (yyHaveZone)
+       {
+         tm = tm0;
+         if (tm.tm_year <= EPOCH - TM_YEAR_ORIGIN)
+           {
+             tm.tm_mday++;
+             yyTimezone -= 24 * 60;
+           }
+         else
+           {
+             tm.tm_mday--;
+             yyTimezone += 24 * 60;
+           }
+         Start = mktime (&tm);
+       }
+
+      if (Start == (time_t) -1)
+       return Start;
+    }
+
+  if (yyHaveDay && !yyHaveDate)
+    {
+      tm.tm_mday += ((yyDayNumber - tm.tm_wday + 7) % 7
+                    + 7 * (yyDayOrdinal - (0 < yyDayOrdinal)));
+      Start = mktime (&tm);
+      if (Start == (time_t) -1)
+       return Start;
+    }
+
+  if (yyHaveZone)
+    {
+      long delta = yyTimezone * 60L + difftm (&tm, gmtime (&Start));
+      if ((Start + delta < Start) != (delta < 0))
+       return -1;              /* time_t overflow */
+      Start += delta;
+    }
+
+  return Start;
+}
+
+#if    defined (TEST)
+
+/* ARGSUSED */
+int
+main (ac, av)
+     int ac;
+     char *av[];
+{
+  char buff[MAX_BUFF_LEN + 1];
+  time_t d;
+
+  (void) printf ("Enter date, or blank line to exit.\n\t> ");
+  (void) fflush (stdout);
+
+  buff[MAX_BUFF_LEN] = 0;
+  while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
+    {
+      d = get_date (buff, (time_t *) NULL);
+      if (d == -1)
+       (void) printf ("Bad format - couldn't convert.\n");
+      else
+       (void) printf ("%s", ctime (&d));
+      (void) printf ("\t> ");
+      (void) fflush (stdout);
+    }
+  exit (0);
+  /* NOTREACHED */
+}
+#endif /* defined (TEST) */
diff --git a/getdate.h b/getdate.h
new file mode 100644 (file)
index 0000000..d2d8722
--- /dev/null
+++ b/getdate.h
@@ -0,0 +1,28 @@
+/*  Copyright (C) 1995 Free Software Foundation, Inc.
+
+   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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* Modified from the original to add stdlib.h and string.h */
+
+#ifndef GOT_GETDATE_H
+#define GOT_GETDATE_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+time_t get_date (const char *p, const time_t *now);
+
+#endif /* GOT_GETDATE_H */
diff --git a/keys.c b/keys.c
new file mode 100644 (file)
index 0000000..7f27fd4
--- /dev/null
+++ b/keys.c
@@ -0,0 +1,243 @@
+/*
+  $Header: /cvs/src/chrony/keys.c,v 1.11 2003/01/20 22:52:07 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Module for managing keys used for authenticating NTP packets and commands
+
+  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "keys.h"
+#include "conf.h"
+#include "memory.h"
+
+typedef struct {
+  unsigned long id;
+  char *val;
+  int len;
+} Key;
+
+#define MAX_KEYS 256
+
+static int n_keys;
+static Key keys[MAX_KEYS];
+
+static int command_key_valid;
+static int command_key_pos;
+static int cache_valid;
+static unsigned long cache_key_id;
+static int cache_key_pos;
+
+/* ================================================== */
+
+void
+KEY_Initialise(void)
+{
+  n_keys = 0;
+  command_key_valid = 0;
+  cache_valid = 0;
+  KEY_Reload();
+  return;
+}
+
+/* ================================================== */
+
+void
+KEY_Finalise(void)
+{
+  /* Nothing to do */
+  return;
+}
+
+/* ================================================== */
+
+/* Compare two keys */
+
+static int
+compare_keys_by_id(const void *a, const void *b)
+{
+  const Key *c = (const Key *) a;
+  const Key *d = (const Key *) b;
+
+  if (c->id < d->id) {
+    return -1;
+  } else if (c->id > d->id) {
+    return +1;
+  } else {
+    return 0;
+  }
+
+}
+
+/* ================================================== */
+
+
+#define KEYLEN 2047
+#define SKEYLEN "2047"
+
+void
+KEY_Reload(void)
+{
+  int i, len1;
+  char *key_file;
+  FILE *in;
+  unsigned long key_id;
+  char line[KEYLEN+1], keyval[KEYLEN+1];
+
+  for (i=0; i<n_keys; i++) {
+    Free(keys[i].val);
+  }
+
+  n_keys = 0;
+
+  key_file = CNF_GetKeysFile();
+
+  if (key_file) {
+    in = fopen(key_file, "r");
+    if (in) {
+      while (fgets(line, sizeof(line), in)) {
+        len1 = strlen(line) - 1;
+
+        /* Guard against removing last character of the line
+         * if the last line of the file is missing an end-of-line */
+        if (line[len1] == '\n') {
+          line[len1] = '\0';
+        }
+
+        if (sscanf(line, "%lu%" SKEYLEN "s", &key_id, keyval) == 2) {
+          keys[n_keys].id = key_id;
+          keys[n_keys].len = strlen(keyval);
+          keys[n_keys].val = MallocArray(char, 1 + keys[n_keys].len);
+          strcpy(keys[n_keys].val, keyval);
+          n_keys++;
+        }
+      }
+      fclose(in);
+      
+      /* Sort keys into order.  Note, if there's a duplicate, it is
+         arbitrary which one we use later - the user should have been
+         more careful! */
+      qsort((void *) keys, n_keys, sizeof(Key), compare_keys_by_id);
+
+    }
+  }
+
+  command_key_valid = 0;
+  cache_valid = 0;
+
+  return;
+}
+
+/* ================================================== */
+
+static int
+lookup_key(unsigned long id)
+{
+  Key specimen, *where;
+  int pos;
+
+  specimen.id = id;
+  where = (Key *) bsearch((void *)&specimen, (void *)keys, n_keys, sizeof(Key), compare_keys_by_id);
+  if (!where) {
+    return -1;
+  } else {
+    pos = where - keys;
+    return pos;
+  }
+}
+
+/* ================================================== */
+
+void
+KEY_CommandKey(char **key, int *len)
+{
+  unsigned long command_key_id;
+
+  if (!command_key_valid) {
+    command_key_id = CNF_GetCommandKey();
+    command_key_pos = lookup_key(command_key_id);
+    command_key_valid = 1;
+  }
+
+  if (command_key_pos >= 0) {
+    *key = keys[command_key_pos].val;
+    *len = keys[command_key_pos].len;
+  } else {
+    *key = "";
+    *len = 0;
+  }
+}
+
+/* ================================================== */
+
+int
+KEY_GetKey(unsigned long key_id, char **key, int *len)
+{
+  if (!cache_valid || key_id != cache_key_id) {
+    cache_valid = 1;
+    cache_key_pos = lookup_key(key_id);
+    cache_key_id = key_id;
+  }
+
+  if (cache_key_pos >= 0) {
+    *key = keys[cache_key_pos].val;
+    *len = keys[cache_key_pos].len;
+    return 1;
+  } else {
+    *key = "";
+    *len = 0;
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+int
+KEY_KeyKnown(unsigned long key_id)
+{
+  int position;
+
+  if (cache_valid && (key_id == cache_key_id)) {
+    return 1;
+  } else {
+
+    position = lookup_key(key_id);
+
+    if (position >= 0) {
+      /* Store key in cache, we will probably be using it in a
+         minute... */
+      cache_valid = 1;
+      cache_key_pos = position;
+      cache_key_id = key_id;
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+}
diff --git a/keys.h b/keys.h
new file mode 100644 (file)
index 0000000..9348c08
--- /dev/null
+++ b/keys.h
@@ -0,0 +1,44 @@
+/*
+  $Header: /cvs/src/chrony/keys.h,v 1.8 2002/02/28 23:27:10 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header for key management module
+  */
+
+#ifndef GOT_KEYS_H
+#define GOT_KEYS_H
+
+extern void KEY_Initialise(void);
+extern void KEY_Finalise(void);
+
+extern void KEY_Reload(void);
+
+extern void KEY_CommandKey(char **key, int *len);
+
+extern int KEY_GetKey(unsigned long key_id, char **key, int *len);
+extern int KEY_KeyKnown(unsigned long key_id);
+
+#endif /* GOT_KEYS_H */
diff --git a/local.c b/local.c
new file mode 100644 (file)
index 0000000..59c8e07
--- /dev/null
+++ b/local.c
@@ -0,0 +1,574 @@
+/*
+  $Header: /cvs/src/chrony/local.c,v 1.20 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  The routines in this file present a common local (system) clock
+  interface to the rest of the software.
+
+  They interface with the system specific driver files in sys_*.c
+  */
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "local.h"
+#include "localp.h"
+#include "memory.h"
+#include "util.h"
+#include "logging.h"
+
+/* ================================================== */
+
+/* Variable to store the current frequency, in ppm */
+static double current_freq_ppm;
+
+/* ================================================== */
+/* Store the system dependent drivers */
+
+static lcl_ReadFrequencyDriver drv_read_freq;
+static lcl_SetFrequencyDriver drv_set_freq;
+static lcl_AccrueOffsetDriver drv_accrue_offset;
+static lcl_ApplyStepOffsetDriver drv_apply_step_offset;
+static lcl_OffsetCorrectionDriver drv_offset_convert;
+static lcl_ImmediateStepDriver drv_immediate_step;
+
+/* ================================================== */
+
+/* Types and variables associated with handling the parameter change
+   list */
+
+typedef struct _ChangeListEntry {
+  struct _ChangeListEntry *next;
+  struct _ChangeListEntry *prev;
+  LCL_ParameterChangeHandler handler;
+  void *anything;
+} ChangeListEntry;
+
+static ChangeListEntry change_list;
+
+/* ================================================== */
+
+/* Types and variables associated with handling the parameter change
+   list */
+
+typedef struct _DispersionNotifyListEntry {
+  struct _DispersionNotifyListEntry *next;
+  struct _DispersionNotifyListEntry *prev;
+  LCL_DispersionNotifyHandler handler;
+  void *anything;
+} DispersionNotifyListEntry;
+
+static DispersionNotifyListEntry dispersion_notify_list;
+
+/* ================================================== */
+
+static int precision_log;
+static double precision_quantum;
+
+/* ================================================== */
+
+/* Define the number of increments of the system clock that we want
+   to see to be fairly sure that we've got something approaching
+   the minimum increment.  Even on a crummy implementation that can't
+   interpolate between 10ms ticks, we should get this done in
+   under 1s of busy waiting. */
+#define NITERS 100
+
+static void
+calculate_sys_precision(void)
+{
+  struct timeval tv, old_tv, first_tv;
+  struct timezone tz;
+  int dusec, best_dusec;
+  int iters;
+
+  gettimeofday(&old_tv, &tz);
+  first_tv = old_tv;
+  best_dusec = 1000000; /* Assume we must be better than a second */
+  iters = 0;
+  do {
+    gettimeofday(&tv, &tz);
+    dusec = 1000000*(tv.tv_sec - old_tv.tv_sec) + (tv.tv_usec - old_tv.tv_usec);
+    old_tv = tv;
+    if (dusec > 0)  {
+      if (dusec < best_dusec) {
+        best_dusec = dusec;
+      }
+      iters++;
+    }
+  } while (iters < NITERS);
+  if (!(best_dusec > 0)) {
+    CROAK("best_dusec should be positive");
+  }
+  precision_log = 0;
+  while (best_dusec < 500000) {
+    precision_log--;
+    best_dusec *= 2;
+  }
+
+  precision_quantum = 1.0 / (double)(1<<(-precision_log));
+
+  return;
+}
+
+/* ================================================== */
+
+void
+LCL_Initialise(void)
+{
+  change_list.next = change_list.prev = &change_list;
+
+  dispersion_notify_list.next = dispersion_notify_list.prev = &dispersion_notify_list;
+
+  /* Null out the system drivers, so that we die
+     if they never get defined before use */
+  
+  drv_read_freq = NULL;
+  drv_set_freq = NULL;
+  drv_accrue_offset = NULL;
+  drv_offset_convert = NULL;
+
+  /* This ought to be set from the system driver layer */
+  current_freq_ppm = 0.0;
+
+  calculate_sys_precision();
+}
+
+/* ================================================== */
+
+void
+LCL_Finalise(void)
+{
+  return;
+}
+
+/* ================================================== */
+
+/* Routine to read the system precision as a log to base 2 value. */
+int
+LCL_GetSysPrecisionAsLog(void)
+{
+  return precision_log;
+}
+
+/* ================================================== */
+/* Routine to read the system precision in terms of the actual time step */
+
+double
+LCL_GetSysPrecisionAsQuantum(void)
+{
+  return precision_quantum;
+}
+
+/* ================================================== */
+
+void
+LCL_AddParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
+{
+  ChangeListEntry *ptr, *new_entry;
+
+  /* Check that the handler is not already registered */
+  for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+    if (!(ptr->handler != handler || ptr->anything != anything)) {
+      CROAK("a handler is already registered");
+    }
+  }
+
+  new_entry = MallocNew(ChangeListEntry);
+
+  new_entry->handler = handler;
+  new_entry->anything = anything;
+
+  /* Chain it into the list */
+  new_entry->next = &change_list;
+  new_entry->prev = change_list.prev;
+  change_list.prev->next = new_entry;
+  change_list.prev = new_entry;
+
+  return;
+}
+
+/* ================================================== */
+
+/* Remove a handler */
+extern 
+void LCL_RemoveParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
+{
+
+  ChangeListEntry *ptr;
+  int ok;
+
+  ptr = NULL;
+  ok = 0;
+
+  for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+    if (ptr->handler == handler && ptr->anything == anything) {
+      ok = 1;
+      break;
+    }
+  }
+
+  if (!ok) {
+    CROAK("did not find a matching handler");
+  }
+
+  /* Unlink entry from the list */
+  ptr->next->prev = ptr->prev;
+  ptr->prev->next = ptr->next;
+
+  free(ptr);
+
+  return;
+}
+
+/* ================================================== */
+
+void
+LCL_AddDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
+{
+  DispersionNotifyListEntry *ptr, *new_entry;
+
+  /* Check that the handler is not already registered */
+  for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
+    if (!(ptr->handler != handler || ptr->anything != anything)) {
+      CROAK("a handler is already registered");
+    }
+  }
+
+  new_entry = MallocNew(DispersionNotifyListEntry);
+
+  new_entry->handler = handler;
+  new_entry->anything = anything;
+
+  /* Chain it into the list */
+  new_entry->next = &dispersion_notify_list;
+  new_entry->prev = dispersion_notify_list.prev;
+  dispersion_notify_list.prev->next = new_entry;
+  dispersion_notify_list.prev = new_entry;
+
+  return;
+}
+
+/* ================================================== */
+
+/* Remove a handler */
+extern 
+void LCL_RemoveDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
+{
+
+  DispersionNotifyListEntry *ptr;
+  int ok;
+
+  ptr = NULL;
+  ok = 0;
+
+  for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
+    if (ptr->handler == handler && ptr->anything == anything) {
+      ok = 1;
+      break;
+    }
+  }
+
+  if (!ok) {
+    CROAK("no matching handler found");
+  }
+
+  /* Unlink entry from the list */
+  ptr->next->prev = ptr->prev;
+  ptr->prev->next = ptr->next;
+
+  free(ptr);
+
+  return;
+}
+
+/* ================================================== */
+/* At the moment, this is just gettimeofday(), because
+   I can't think of a Unix system where it would not be */
+
+void
+LCL_ReadRawTime(struct timeval *result)
+{
+  struct timezone tz;
+
+  if (!(gettimeofday(result, &tz) >= 0)) {
+    CROAK("Could not get time of day");
+  }
+  return;
+
+}
+
+/* ================================================== */
+
+void
+LCL_ReadCookedTime(struct timeval *result, double *err)
+{
+  struct timeval raw;
+  double correction;
+
+  LCL_ReadRawTime(&raw);
+
+  /* For now, cheat and set the error to zero in all cases.
+   */
+
+  *err = 0.0;
+
+  /* Call system specific driver to get correction */
+  (*drv_offset_convert)(&raw, &correction);
+  UTI_AddDoubleToTimeval(&raw, correction, result);
+
+  return;
+}
+
+/* ================================================== */
+
+double
+LCL_GetOffsetCorrection(struct timeval *raw)
+{
+  double correction;
+  (*drv_offset_convert)(raw, &correction);
+  return correction;
+}
+
+/* ================================================== */
+/* This is just a simple passthrough of the system specific routine */
+
+double
+LCL_ReadAbsoluteFrequency(void)
+{
+  return (*drv_read_freq)();
+}
+
+/* ================================================== */
+/* This involves both setting the absolute frequency with the
+   system-specific driver, as well as calling all notify handlers */
+
+void
+LCL_SetAbsoluteFrequency(double afreq_ppm)
+{
+  ChangeListEntry *ptr;
+  struct timeval raw, cooked;
+  double correction;
+  double dfreq;
+  
+  /* Call the system-specific driver for setting the frequency */
+  
+  (*drv_set_freq)(afreq_ppm);
+
+  dfreq = 1.0e-6 * (afreq_ppm - current_freq_ppm) / (1.0 - 1.0e-6 * current_freq_ppm);
+
+  LCL_ReadRawTime(&raw);
+  (drv_offset_convert)(&raw, &correction);
+  UTI_AddDoubleToTimeval(&raw, correction, &cooked);
+
+  /* Dispatch to all handlers */
+  for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+    (ptr->handler)(&raw, &cooked, dfreq, afreq_ppm, 0.0, 0, ptr->anything);
+  }
+
+  current_freq_ppm = afreq_ppm;
+
+}
+
+/* ================================================== */
+
+void
+LCL_AccumulateDeltaFrequency(double dfreq)
+{
+  ChangeListEntry *ptr;
+  struct timeval raw, cooked;
+  double correction;
+
+  /* Work out new absolute frequency.  Note that absolute frequencies
+   are handled in units of ppm, whereas the 'dfreq' argument is in
+   terms of the gradient of the (offset) v (local time) function. */
+
+  current_freq_ppm = (1.0 - dfreq) * current_freq_ppm +
+    (1.0e6 * dfreq);
+
+  /* Call the system-specific driver for setting the frequency */
+  (*drv_set_freq)(current_freq_ppm);
+
+  LCL_ReadRawTime(&raw);
+  (drv_offset_convert)(&raw, &correction);
+  UTI_AddDoubleToTimeval(&raw, correction, &cooked);
+
+  /* Dispatch to all handlers */
+  for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+    (ptr->handler)(&raw, &cooked, dfreq, current_freq_ppm, 0.0, 0, ptr->anything);
+  }
+
+}
+
+/* ================================================== */
+
+void
+LCL_AccumulateOffset(double offset)
+{
+  ChangeListEntry *ptr;
+  struct timeval raw, cooked;
+  double correction;
+
+  /* In this case, the cooked time to be passed to the notify clients
+     has to be the cooked time BEFORE the change was made */
+
+  LCL_ReadRawTime(&raw);
+  (drv_offset_convert)(&raw, &correction);
+  UTI_AddDoubleToTimeval(&raw, correction, &cooked);
+
+  (*drv_accrue_offset)(offset);
+
+  /* Dispatch to all handlers */
+  for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+    (ptr->handler)(&raw, &cooked, 0.0, current_freq_ppm, offset, 0, ptr->anything);
+  }
+
+}
+
+/* ================================================== */
+
+void
+LCL_ApplyStepOffset(double offset)
+{
+  ChangeListEntry *ptr;
+  struct timeval raw, cooked;
+  double correction;
+
+  /* In this case, the cooked time to be passed to the notify clients
+     has to be the cooked time BEFORE the change was made */
+
+  LCL_ReadRawTime(&raw);
+  (drv_offset_convert)(&raw, &correction);
+  UTI_AddDoubleToTimeval(&raw, correction, &cooked);
+
+  (*drv_apply_step_offset)(offset);
+
+  /* Dispatch to all handlers */
+  for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+    (ptr->handler)(&raw, &cooked, 0.0, current_freq_ppm, offset, 1, ptr->anything);
+  }
+
+}
+
+/* ================================================== */
+
+void
+LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset)
+{
+  ChangeListEntry *ptr;
+  struct timeval raw, cooked;
+  double correction;
+  double old_freq_ppm;
+
+  LCL_ReadRawTime(&raw);
+  (drv_offset_convert)(&raw, &correction);
+  /* Due to modifying the offset, this has to be the cooked time prior
+     to the change we are about to make */
+  UTI_AddDoubleToTimeval(&raw, correction, &cooked);
+
+  old_freq_ppm = current_freq_ppm;
+
+  /* Work out new absolute frequency.  Note that absolute frequencies
+   are handled in units of ppm, whereas the 'dfreq' argument is in
+   terms of the gradient of the (offset) v (local time) function. */
+  current_freq_ppm = (1.0 - dfreq) * old_freq_ppm +
+    (1.0e6 * dfreq);
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_Local, "old_freq=%.3fppm new_freq=%.3fppm offset=%.6fsec",
+      old_freq_ppm, current_freq_ppm, doffset);
+#endif
+
+  /* Call the system-specific driver for setting the frequency */
+  (*drv_set_freq)(current_freq_ppm);
+  (*drv_accrue_offset)(doffset);
+
+  /* Dispatch to all handlers */
+  for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+    (ptr->handler)(&raw, &cooked, dfreq, current_freq_ppm, doffset, 0, ptr->anything);
+  }
+
+
+}
+
+/* ================================================== */
+
+void
+lcl_InvokeDispersionNotifyHandlers(double dispersion)
+{
+  DispersionNotifyListEntry *ptr;
+
+  for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
+    (ptr->handler)(dispersion, ptr->anything);
+  }
+
+}
+
+/* ================================================== */
+
+void
+lcl_RegisterSystemDrivers(lcl_ReadFrequencyDriver read_freq,
+                          lcl_SetFrequencyDriver set_freq,
+                          lcl_AccrueOffsetDriver accrue_offset,
+                          lcl_ApplyStepOffsetDriver apply_step_offset,
+                          lcl_OffsetCorrectionDriver offset_convert,
+                          lcl_ImmediateStepDriver immediate_step)
+{
+  drv_read_freq = read_freq;
+  drv_set_freq = set_freq;
+  drv_accrue_offset = accrue_offset;
+  drv_apply_step_offset = apply_step_offset;
+  drv_offset_convert = offset_convert;
+  drv_immediate_step = immediate_step;
+
+  current_freq_ppm = (*drv_read_freq)();
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_Local, "Local freq=%.3fppm", current_freq_ppm);
+#endif
+
+  return;
+}
+
+/* ================================================== */
+/* Look at the current difference between the system time and the NTP
+   time, and make a step to cancel it. */
+
+int
+LCL_MakeStep(void)
+{
+  if (drv_immediate_step) {
+    (drv_immediate_step)();
+#ifdef TRACEON
+    LOG(LOGS_INFO, LOGF_Local, "Made step to system time to apply remaining slew");
+#endif
+    return 1;
+  }
+
+  return 0;
+}
+
+/* ================================================== */
diff --git a/local.h b/local.h
new file mode 100644 (file)
index 0000000..857d8b7
--- /dev/null
+++ b/local.h
@@ -0,0 +1,184 @@
+/*
+  $Header: /cvs/src/chrony/local.h,v 1.16 2002/02/28 23:27:10 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This module provides an interface to the system time, and
+  insulates the rest of the program from the different way
+  that interface has to be done on various operating systems.
+  */
+
+#ifndef GOT_LOCAL_H
+#define GOT_LOCAL_H
+
+#include "sysincl.h"
+
+/* Read the system clock.  This is analogous to gettimeofday(),
+   but with the timezone information ignored */
+extern void LCL_ReadRawTime(struct timeval *);
+
+/* Read the system clock, corrected according to all accumulated
+   drifts and uncompensated offsets.
+
+   In a kernel implementation with vernier frequency control (like
+   Linux), and if we were to apply offsets by stepping the clock, this
+   would be identical to raw time.  In any other case (use of
+   adjtime()-like interface to correct offsets, and to adjust the
+   frequency), we must correct the raw time to get this value */
+
+extern void LCL_ReadCookedTime(struct timeval *t, double *err);
+
+/* Read the current offset between the system clock and true time
+   (i.e. 'cooked' - 'raw') (in seconds).  Only intended for use in
+   status reporting, really. */
+
+extern double LCL_GetOffsetCorrection(struct timeval *raw);
+
+/* Type of routines that may be invoked as callbacks when there is a
+   change to the frequency or offset.
+
+   raw : raw local clock time at which change occurred
+
+   cooked : cooked local time at which change occurred
+
+   dfreq : delta frequency relative to previous value (in terms of
+   seconds gained by system clock per unit system clock time)
+
+   afreq : absolute frequency relative to uncompensated system (in
+   terms of ppm seconds gained by system clock per unit of the
+   uncalibrated system clock)
+
+   doffset : delta offset applied (positive => make local system fast
+   by that amount, negative => make it slow by that amount)
+
+   is_step_change : true if change is being applied as a jump (using
+   settimeofday rather than adjtime)
+   
+   anything : Passthrough argument from call to registration routine */
+
+
+typedef void (*LCL_ParameterChangeHandler)
+     (struct timeval *raw, struct timeval *cooked,
+      double dfreq, double afreq_ppm,
+      double doffset, int is_step_change,
+      void *anything
+      );
+
+/* Add a handler.  Then handler MUST NOT deregister itself!!! */
+extern void LCL_AddParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything);
+
+/* Remove a handler */
+extern void LCL_RemoveParameterChangeHandler(LCL_ParameterChangeHandler, void *anything);
+
+/* Function type for handlers to be called back when an indeterminate
+   offset is introduced into the local time.  This situation occurs
+   when the frequency must be adjusted to effect a clock slew and
+   there is doubt about one of the endpoints of the interval over
+   which the frequency change was applied.It is expected that such
+   handlers will add extra dispersion to any existing samples stored
+   in their registers. 
+
+   dispersion : The bound on how much error has been introduced in the
+   local clock, in seconds.
+
+   anything : passthrough from the registration routine
+
+   */
+
+typedef void (*LCL_DispersionNotifyHandler)(double dispersion, void *anything);
+
+/* Register a handler for being notified of dispersion being added to
+   the local clock.  The handler MUST NOT unregister itself!!! */
+
+extern void LCL_AddDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything);
+
+/* Delete a handler */
+
+extern void LCL_RemoveDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything);
+
+
+/* Read the absolute system frequency, relative to the uncompensated
+   system.  Returned in units of parts per million.  Thus the result of
+   this is how many seconds fast the uncompensated system would be after
+   its own time has reached 1 million seconds from the start of the
+   measurement.  */
+extern double LCL_ReadAbsoluteFrequency(void);
+
+/* Routine to set the absolute frequency.  Only expected to be used
+   when either (i) reading the drift from a file at the start of a
+   run, or (ii) responsing to a user parameter 'poke'.  This is
+   defined in ppm, as for the absolute frequency reading routine. */
+
+extern void LCL_SetAbsoluteFrequency(double afreq);
+
+/* Routine to apply a change of frequency to the local clock.  The
+   argument is the estimated gain (positive) or loss (negative) of the
+   local clock relative to true time, per unit time of the PREVIOUS
+   frequency setting of the local clock.  This is assumed to be based
+   on a regression of y=offset v x=cooked local time. */
+
+extern void LCL_AccumulateDeltaFrequency(double dfreq);
+
+/* Routine to apply an offset (in seconds) to the local clock.  The
+   argument should be positive to move the clock backwards (i.e. the
+   local clock is currently fast of true time), or negative to move it
+   forwards (i.e. it is currently slow of true time). */
+
+extern void LCL_AccumulateOffset(double offset);
+
+/* Routine to apply an immediate offset by doing a sudden step if
+   possible. (Intended for use after an initial estimate of offset has
+   been obtained, so that we don't end up using adjtime to achieve a
+   slew of an hour or something like that). A positive argument means
+   the system clock is fast on true time, i.e. it needs to be stepped
+   backwards. (Same convention as for AccumulateOffset routine). */
+
+extern void LCL_ApplyStepOffset(double offset);
+
+/* Perform the combination of modifying the frequency and applying
+   a slew, in one easy step */
+extern void LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset);
+
+/* Routine to read the system precision as a log to base 2 value. */
+extern int LCL_GetSysPrecisionAsLog(void);
+
+/* Routine to read the system precision in terms of the actual time step */
+extern double LCL_GetSysPrecisionAsQuantum(void);
+
+/* Routine to initialise the module (to be called once at program
+   start-up) */
+
+extern void LCL_Initialise(void);
+
+/* Routine to finalise the module (to be called once at end of
+   run). */
+extern void LCL_Finalise(void);
+
+/* Routine to convert the outstanding system clock error to a step and
+   apply it, e.g. if the system clock has ended up an hour wrong due
+   to a timezone problem. */
+extern int LCL_MakeStep(void);
+
+#endif /* GOT_LOCAL_H */
diff --git a/localp.h b/localp.h
new file mode 100644 (file)
index 0000000..3e01a33
--- /dev/null
+++ b/localp.h
@@ -0,0 +1,73 @@
+/*
+  $Header: /cvs/src/chrony/localp.h,v 1.9 2002/02/28 23:27:10 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Private include file for local.c and all system dependent
+  driver modules.
+  */
+
+
+#ifndef GOT_LOCALP_H
+#define GOT_LOCALP_H
+
+/* System driver to read the current local frequency, in ppm relative
+   to nominal.  A positive value indicates that the local clock runs
+   fast when uncompensated. */
+typedef double (*lcl_ReadFrequencyDriver)(void);
+
+/* System driver to set the current local frequency, in ppm relative
+   to nominal.  A positive value indicates that the local clock runs
+   fast when uncompensated. */
+typedef void (*lcl_SetFrequencyDriver)(double freq_ppm);
+
+/* System driver to accrue an offset. A positive argument means slew
+   the clock forwards. */
+typedef void (*lcl_AccrueOffsetDriver)(double offset);
+
+/* System driver to apply a step offset. A positive argument means step
+   the clock forwards. */
+typedef void (*lcl_ApplyStepOffsetDriver)(double offset);
+
+/* System driver to convert a raw time to an adjusted (cooked) time.
+   The number of seconds returned in 'corr' have to be added to the
+   raw time to get the corrected time */
+typedef void (*lcl_OffsetCorrectionDriver)(struct timeval *raw, double *corr);
+
+/* System driver to stop slewing the current offset and to apply is
+   as an immediate step instead */
+typedef void (*lcl_ImmediateStepDriver)(void);
+
+extern void lcl_InvokeDispersionNotifyHandlers(double dispersion);
+
+extern void
+lcl_RegisterSystemDrivers(lcl_ReadFrequencyDriver read_freq,
+                          lcl_SetFrequencyDriver set_freq,
+                          lcl_AccrueOffsetDriver accrue_offset,
+                          lcl_ApplyStepOffsetDriver apply_step_offset,
+                          lcl_OffsetCorrectionDriver offset_convert,
+                          lcl_ImmediateStepDriver immediate_step_driver);
+
+#endif /* GOT_LOCALP_H */
diff --git a/logging.c b/logging.c
new file mode 100644 (file)
index 0000000..974c7df
--- /dev/null
+++ b/logging.c
@@ -0,0 +1,216 @@
+/*
+  $Header: /cvs/src/chrony/logging.c,v 1.13 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Module to handle logging of diagnostic information
+  */
+
+#include "sysincl.h"
+
+#include "main.h"
+#include "logging.h"
+#include "version.h"
+
+/* ================================================== */
+/* Flag indicating we have initialised */
+static int initialised = 0;
+
+static int is_detached = 0;
+
+#ifdef WINNT
+static FILE *logfile;
+#endif
+
+/* ================================================== */
+/* Init function */
+
+void
+LOG_Initialise(void)
+{
+  initialised = 1;
+
+#ifdef WINNT
+  logfile = fopen("./chronyd.err", "a");
+#endif
+
+  return;
+}
+
+/* ================================================== */
+/* Fini function */
+
+void
+LOG_Finalise(void)
+{
+#ifdef WINNT
+  if (logfile) {
+    fclose(logfile);
+  }
+#else
+  if (is_detached) {
+    closelog();
+  }
+#endif
+
+  initialised = 0;
+  return;
+}
+
+/* ================================================== */
+
+void
+LOG_Line_Function(LOG_Severity severity, LOG_Facility facility, const char *format, ...)
+{
+  char buf[2048];
+  va_list other_args;
+  va_start(other_args, format);
+  vsprintf(buf, format, other_args);
+  va_end(other_args);
+#ifdef WINNT
+  if (logfile) {
+    fprintf(logfile, "%s\n", buf);
+  }
+#else
+  if (is_detached) {
+    switch (severity) {
+      case LOGS_INFO:
+        syslog(LOG_INFO, "%s", buf);
+        break;
+      case LOGS_WARN:
+        syslog(LOG_WARNING, "%s", buf);
+        break;
+      case LOGS_ERR:
+      default:
+        syslog(LOG_ERR, "%s", buf);
+        break;
+    }
+  } else {
+    fprintf(stderr, "%s\n", buf);
+  }
+#endif
+  return;
+}
+
+/* ================================================== */
+
+volatile void
+LOG_Fatal_Function(LOG_Facility facility, const char *format, ...)
+{
+  char buf[2048];
+  va_list other_args;
+  va_start(other_args, format);
+  vsprintf(buf, format, other_args);
+  va_end(other_args);
+
+#ifdef WINNT
+  if (logfile) {
+    fprintf(logfile, "Fatal error : %s\n", buf);
+  }
+#else
+  if (is_detached) {
+    syslog(LOG_CRIT, "Fatal error : %s", buf);
+  } else {
+    fprintf(stderr, "Fatal error : %s\n", buf);
+  }
+#endif
+
+  MAI_CleanupAndExit();
+
+  return;
+}
+
+/* ================================================== */
+
+void
+LOG_Position(const char *filename, int line_number, const char *function_name)
+{
+#ifdef WINNT
+#else
+  time_t t;
+  struct tm stm;
+  char buf[64];
+  if (!is_detached) {
+    /* Don't clutter up syslog with internal debugging info */
+    time(&t);
+    stm = *gmtime(&t);
+    strftime(buf, sizeof(buf), "%d-%H:%M:%S", &stm);
+    fprintf(stderr, "%s:%d:(%s)[%s] ", filename, line_number, function_name, buf);
+  }
+#endif
+  return;
+}
+
+/* ================================================== */
+
+void
+LOG_GoDaemon(void)
+{
+#ifdef WINNT
+
+
+#else
+
+  int pid, fd;
+
+  /* Does this preserve existing signal handlers? */
+  pid = fork();
+
+  if (pid < 0) {
+    LOG(LOGS_ERR, LOGF_Logging, "Could not detach, fork failed : %s", strerror(errno));
+  } else if (pid > 0) {
+    exit(0); /* In the 'grandparent' */
+  } else {
+
+    setsid();
+
+    /* Do 2nd fork, as-per recommended practice for launching daemons. */
+    pid = fork();
+
+    if (pid < 0) {
+      LOG(LOGS_ERR, LOGF_Logging, "Could not detach, fork failed : %s", strerror(errno));
+    } else if (pid > 0) {
+      exit(0); /* In the 'parent' */
+    } else {
+      /* In the child we want to leave running as the daemon */
+
+      /* Don't keep stdin/out/err from before. */
+      for (fd=0; fd<1024; fd++) {
+        close(fd);
+      }
+
+      is_detached = 1;
+
+      openlog("chronyd", LOG_PID, LOG_DAEMON);
+
+      LOG(LOGS_INFO, LOGF_Logging, "chronyd version %s starting", PROGRAM_VERSION_STRING);
+
+    }
+  }
+
+#endif
+}
+
+/* ================================================== */
diff --git a/logging.h b/logging.h
new file mode 100644 (file)
index 0000000..33a792d
--- /dev/null
+++ b/logging.h
@@ -0,0 +1,97 @@
+/*
+  $Header: /cvs/src/chrony/logging.h,v 1.15 2002/02/28 23:27:10 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for diagnostic logging module
+
+  */
+
+#ifndef GOT_LOGGING_H
+#define GOT_LOGGING_H
+
+/* Definition of severity */
+typedef enum {
+  LOGS_INFO,
+  LOGS_WARN,
+  LOGS_ERR
+} LOG_Severity;
+
+/* Definition of facility.  Each message is tagged with who generated
+   it, so that the user can customise what level of reporting he gets
+   for each area of the software */
+typedef enum {
+  LOGF_Reference,
+  LOGF_NtpIO,
+  LOGF_NtpCore,
+  LOGF_NtpSources,
+  LOGF_Scheduler,
+  LOGF_SourceStats,
+  LOGF_Sources,
+  LOGF_Local,
+  LOGF_Util,
+  LOGF_Main,
+  LOGF_Configure,
+  LOGF_CmdMon,
+  LOGF_Acquire,
+  LOGF_Manual,
+  LOGF_Logging,
+  LOGF_Rtc,
+  LOGF_Regress,
+  LOGF_SysLinux,
+  LOGF_SysSolaris,
+  LOGF_SysSunOS,
+  LOGF_SysWinnt,
+  LOGF_RtcLinux
+} LOG_Facility;
+
+/* Init function */
+extern void LOG_Initialise(void);
+
+/* Fini function */
+extern void LOG_Finalise(void);
+
+/* Line logging function */
+extern void LOG_Line_Function(LOG_Severity severity, LOG_Facility facility, const char *format, ...);
+
+/* Logging function for fatal errors */
+extern volatile void LOG_Fatal_Function(LOG_Facility facility, const char *format, ...);
+
+/* Position in code reporting function */
+extern void LOG_Position(const char *filename, int line_number, const char *function_name);
+
+extern void LOG_GoDaemon(void);
+
+/* Line logging macro.  If the compiler is GNU C, we take advantage of
+   being able to get the function name also. */
+#if defined(__GNUC__)
+#define LOG LOG_Position(__FILE__, __LINE__, __FUNCTION__); LOG_Line_Function
+#define LOG_FATAL LOG_Position(__FILE__, __LINE__, __FUNCTION__); LOG_Fatal_Function
+#else
+#define LOG LOG_Position(__FILE__, __LINE__, ""); LOG_Line_Function
+#define LOG_FATAL LOG_Position(__FILE__, __LINE__, ""); LOG_Fatal_Function
+#endif /* defined (__GNUC__) */
+
+#endif /* GOT_LOGGING_H */
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..5424d1d
--- /dev/null
+++ b/main.c
@@ -0,0 +1,311 @@
+/*
+  $Header: /cvs/src/chrony/main.c,v 1.30 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  The main program
+  */
+
+#include "sysincl.h"
+
+#include "main.h"
+#include "sched.h"
+#include "local.h"
+#include "sys.h"
+#include "ntp_io.h"
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "sources.h"
+#include "sourcestats.h"
+#include "reference.h"
+#include "logging.h"
+#include "conf.h"
+#include "cmdmon.h"
+#include "keys.h"
+#include "acquire.h"
+#include "manual.h"
+#include "version.h"
+#include "rtc.h"
+#include "clientlog.h"
+#include "broadcast.h"
+
+/* ================================================== */
+
+/* Set when the initialisation chain has been completed.  Prevents finalisation
+ * chain being run if a fatal error happened early. */
+
+static int initialised = 0;
+
+/* ================================================== */
+
+static int reload = 0;
+
+/* ================================================== */
+
+static void
+delete_pidfile(void)
+{
+  const char *pidfile = CNF_GetPidFile();
+  /* Don't care if this fails, there's not a lot we can do */
+  unlink(pidfile);
+}
+
+/* ================================================== */
+
+volatile void
+MAI_CleanupAndExit(void)
+{
+  if (!initialised) exit(0);
+  
+  if (CNF_GetDumpOnExit()) {
+    SRC_DumpSources();
+  }
+
+  RTC_Finalise();
+  MNL_Finalise();
+  ACQ_Finalise();
+  CAM_Finalise();
+  KEY_Finalise();
+  CLG_Finalise();
+  NIO_Finalise();
+  NSR_Finalise();
+  NCR_Finalise();
+  BRD_Finalise();
+  SRC_Finalise();
+  SST_Finalise();
+  REF_Finalise();
+  SYS_Finalise();
+  SCH_Finalise();
+  LCL_Finalise();
+
+  delete_pidfile();
+  
+  LOG_Finalise();
+
+  exit(0);
+}
+
+/* ================================================== */
+
+static void
+signal_cleanup(int x)
+{
+  LOG(LOGS_WARN, LOGF_Main, "chronyd exiting on signal");
+  MAI_CleanupAndExit();
+}
+
+/* ================================================== */
+
+static void
+post_acquire_hook(void *anything)
+{
+
+  CNF_AddSources();
+  CNF_AddBroadcasts();
+  if (reload) {
+    /* Note, we want reload to come well after the initialisation from
+       the real time clock - this gives us a fighting chance that the
+       system-clock scale for the reloaded samples still has a
+       semblence of validity about it. */
+    SRC_ReloadSources();
+  }
+  CNF_SetupAccessRestrictions();
+
+  RTC_StartMeasurements();
+}
+
+/* ================================================== */
+
+static void
+post_init_rtc_hook(void *anything)
+{
+  CNF_ProcessInitStepSlew(post_acquire_hook, NULL);
+}
+
+/* ================================================== */
+/* Return 1 if the process exists on the system. */
+
+static int
+does_process_exist(int pid)
+{
+  int status;
+  status = getsid(pid);
+  if (status >= 0) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static int
+maybe_another_chronyd_running(int *other_pid)
+{
+  const char *pidfile = CNF_GetPidFile();
+  FILE *in;
+  int pid, count;
+  
+  *other_pid = 0;
+
+  in = fopen(pidfile, "r");
+  if (!in) return 0;
+
+  count = fscanf(in, "%d", &pid);
+  fclose(in);
+  
+  if (count != 1) return 0;
+
+  *other_pid = pid;
+  return does_process_exist(pid);
+  
+}
+
+/* ================================================== */
+
+static void
+write_lockfile(void)
+{
+  const char *pidfile = CNF_GetPidFile();
+  FILE *out;
+
+  out = fopen(pidfile, "w");
+  if (!out) {
+    LOG(LOGS_ERR, LOGF_Main, "could not open lockfile %s for writing", pidfile);
+  } else {
+    fprintf(out, "%d\n", getpid());
+    fclose(out);
+  }
+}
+
+/* ================================================== */
+
+int main
+(int argc, char **argv)
+{
+  char *conf_file = NULL;
+  int debug = 0;
+  int do_init_rtc = 0;
+  int other_pid;
+
+  LOG_Initialise();
+
+  /* Parse command line options */
+  while (++argv, (--argc)>0) {
+
+    if (!strcmp("-f", *argv)) {
+      ++argv, --argc;
+      conf_file = *argv;
+    } else if (!strcmp("-r", *argv)) {
+      reload = 1;
+    } else if (!strcmp("-s", *argv)) {
+      do_init_rtc = 1;
+    } else if (!strcmp("-v", *argv) || !strcmp("--version",*argv)) {
+      /* This write to the terminal is OK, it comes before we turn into a daemon */
+      printf("chronyd (chrony) version %s\n", PROGRAM_VERSION_STRING);
+      exit(0);
+    } else if (!strcmp("-d", *argv)) {
+      debug = 1;
+    } else {
+      LOG(LOGS_WARN, LOGF_Main, "Unrecognized command line option [%s]", *argv);
+    }
+  }
+
+#ifndef SYS_WINNT
+  if (getuid() != 0) {
+    /* This write to the terminal is OK, it comes before we turn into a daemon */
+    fprintf(stderr,"Not superuser\n");
+    exit(1);
+  }
+
+
+  /* Turn into a daemon */
+  if (!debug) {
+    LOG_GoDaemon();
+  }
+  
+  /* Check whether another chronyd may already be running.  Do this after
+   * forking, so that message logging goes to the right place (i.e. syslog), in
+   * case this chronyd is being run from a boot script. */
+  if (maybe_another_chronyd_running(&other_pid)) {
+    LOG_FATAL(LOGF_Main, "Another chronyd may already be running (pid=%d), check lockfile (%s)",
+              other_pid, CNF_GetPidFile());
+    exit(1);
+  }
+
+  /* Write our lockfile to prevent other chronyds running.  This has *GOT* to
+   * be done *AFTER* the daemon-creation fork() */
+  write_lockfile();
+#endif
+
+  CNF_ReadFile(conf_file);
+
+  if (do_init_rtc) {
+    RTC_TimePreInit();
+  }
+
+  LCL_Initialise();
+  SCH_Initialise();
+  SYS_Initialise();
+  REF_Initialise();
+  SST_Initialise();
+  SRC_Initialise();
+  BRD_Initialise();
+  NCR_Initialise();
+  NSR_Initialise();
+  NIO_Initialise();
+  CLG_Initialise();
+  KEY_Initialise();
+  CAM_Initialise();
+  ACQ_Initialise();
+  MNL_Initialise();
+  RTC_Initialise();
+
+  /* From now on, it is safe to do finalisation on exit */
+  initialised = 1;
+
+  if (do_init_rtc) {
+    RTC_TimeInit(post_init_rtc_hook, NULL);
+  } else {
+    post_init_rtc_hook(NULL);
+  }
+
+  signal(SIGINT, signal_cleanup);
+  signal(SIGTERM, signal_cleanup);
+#if !defined(WINNT)
+  signal(SIGQUIT, signal_cleanup);
+  signal(SIGHUP, signal_cleanup);
+#endif /* WINNT */
+
+  /* The program normally runs under control of the main loop in
+     the scheduler. */
+  SCH_MainLoop();
+
+  MAI_CleanupAndExit();
+
+  return 0;
+}
+
+/* ================================================== */
diff --git a/main.h b/main.h
new file mode 100644 (file)
index 0000000..aaf071a
--- /dev/null
+++ b/main.h
@@ -0,0 +1,39 @@
+/*
+  $Header: /cvs/src/chrony/main.h,v 1.8 2002/02/28 23:27:10 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for main routine
+  */
+
+#ifndef GOT_MAIN_H
+#define GOT_MAIN_H
+
+/* Function to clean up at end of run */
+extern volatile void MAI_CleanupAndExit(void);
+
+#endif /* GOT_MAIN_H */
+
+
diff --git a/manual.c b/manual.c
new file mode 100644 (file)
index 0000000..83f7ceb
--- /dev/null
+++ b/manual.c
@@ -0,0 +1,331 @@
+/*
+  $Header: /cvs/src/chrony/manual.c,v 1.20 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Routines for implementing manual input of real time.
+
+  The daemon accepts manual time input over the control connection,
+  and adjusts the system time to match.  Besides this, though, it can
+  determine the average rate of time loss or gain of the local system
+  and adjust the frequency accordingly.
+
+  */
+
+#include <stddef.h>
+
+#include "manual.h"
+#include "logging.h"
+#include "local.h"
+#include "conf.h"
+#include "util.h"
+#include "ntp.h"
+#include "reference.h"
+#include "regress.h"
+
+static int enabled = 0;
+
+/* More recent samples at highest indices */
+typedef struct {
+  struct timeval when; /* This is our 'cooked' time */
+  double orig_offset; /*+ Not modified by slew samples */
+  double offset; /*+ if we are fast of the supplied reference */
+  double residual; /*+ regression residual (sign convention given by
+                     (measured-predicted)) */
+} Sample;
+
+#define MAX_SAMPLES 16
+
+static Sample samples[16];
+static int n_samples;
+
+static int replace_margin;
+static int error;
+
+/* Eventually these constants need to be user-defined in conf file */
+#define REPLACE_MARGIN 300
+#define ERROR_MARGIN 0.2
+
+/* ================================================== */
+
+static void
+slew_samples(struct timeval *raw,
+             struct timeval *cooked, 
+             double dfreq,
+             double afreq,
+             double doffset,
+             int is_step_change,
+             void *not_used);
+
+/* ================================================== */
+
+void
+MNL_Initialise(void)
+{
+  if (CNF_GetManualEnabled()) {
+    enabled = 1;
+  } else {
+    enabled = 0;
+  }
+
+  n_samples = 0;
+
+  replace_margin = REPLACE_MARGIN;
+  error = ERROR_MARGIN;
+
+  LCL_AddParameterChangeHandler(slew_samples, NULL);
+
+  return;
+}
+
+/* ================================================== */
+
+void
+MNL_Finalise(void)
+{
+  return;
+}
+
+/* ================================================== */
+
+static void
+estimate_and_set_system(struct timeval *now, int offset_provided, double offset, long *offset_cs, double *dfreq_ppm, double *new_afreq_ppm)
+{
+  double agos[MAX_SAMPLES], offsets[MAX_SAMPLES];
+  double b0, b1;
+  int n_runs, best_start; /* Unused results from regression analyser */
+  int i;
+  double freq = 0.0;
+  double skew = 0.099999999; /* All 9's when printed to log file */
+  int found_freq;
+  double slew_by;
+
+  if (n_samples > 1) {
+    for (i=0; i<n_samples; i++) {
+      UTI_DiffTimevalsToDouble(&agos[i], &samples[n_samples-1].when, &samples[i].when);
+      offsets[i] = samples[i].offset;
+    }
+    
+    RGR_FindBestRobustRegression(agos, offsets, n_samples,
+                                 1.0e-8, /* 0.01ppm easily good enough for this! */
+                                 &b0, &b1, &n_runs, &best_start);
+    
+    
+    /* Ignore b0 from regression; treat offset as being the most
+       recently entered value.  (If the administrator knows he's put
+       an outlier in, he will rerun the settime operation.)   However,
+       the frequency estimate comes from the regression. */
+    
+    freq = -b1;
+    found_freq = 1;
+  } else {
+    if (offset_provided) {
+      b0 = offset;
+    } else {
+      b0 = 0.0;
+    }
+    b1 = freq = 0.0;
+    found_freq = 0;
+  }
+
+  if (offset_provided) {
+    slew_by = offset;
+  } else {
+    slew_by = b0;
+  }
+  
+  if (found_freq) {
+    LOG(LOGS_INFO, LOGF_Manual,
+        "Making a frequency change of %.3fppm and a slew of %.6f\n",
+        1.0e6 * freq, slew_by);
+    
+    REF_SetManualReference(now,
+                           slew_by,
+                           freq, skew);
+  } else {
+    LOG(LOGS_INFO, LOGF_Manual, "Making a slew of %.6f", slew_by);
+    REF_SetManualReference(now,
+                           slew_by,
+                           0.0, skew);
+  }
+  
+  if (offset_cs) *offset_cs = (long)(0.5 + 100.0 * b0);
+  if (dfreq_ppm) *dfreq_ppm = 1.0e6 * freq;
+  if (new_afreq_ppm) *new_afreq_ppm = LCL_ReadAbsoluteFrequency();
+  
+  /* Calculate residuals to store them */
+  for (i=0; i<n_samples; i++) {
+    samples[i].residual = offsets[i] - (b0 + agos[i] * b1);
+  }
+  
+}
+
+/* ================================================== */
+
+int
+MNL_AcceptTimestamp(struct timeval *ts, long *offset_cs, double *dfreq_ppm, double *new_afreq_ppm)
+{
+  struct timeval now;
+  double local_clock_err;
+  double offset;
+  int i;
+
+  if (enabled) {
+
+    /* Check whether timestamp is within margin of old one */
+    LCL_ReadCookedTime(&now, &local_clock_err);
+
+    UTI_DiffTimevalsToDouble(&offset, &now, ts);
+
+    /* Check if buffer full up */
+    if (n_samples == MAX_SAMPLES) {
+      /* Shift samples down */
+      for (i=1; i<n_samples; i++) {
+        samples[i-1] = samples[i];
+      }
+      --n_samples;
+    }
+    
+    samples[n_samples].when = now;
+    samples[n_samples].offset = offset;
+    samples[n_samples].orig_offset = offset;
+    ++n_samples;
+
+    estimate_and_set_system(&now, 1, offset, offset_cs, dfreq_ppm, new_afreq_ppm);
+
+    return 1;
+
+  } else {
+  
+    return 0;
+
+  }
+}
+
+/* ================================================== */
+
+static void
+slew_samples(struct timeval *raw,
+             struct timeval *cooked, 
+             double dfreq,
+             double afreq,
+             double doffset,
+             int is_step_change,
+             void *not_used)
+{
+  double elapsed, delta_time;
+  int i;
+  for (i=0; i<n_samples; i++) {
+    UTI_DiffTimevalsToDouble(&elapsed, cooked, &samples[i].when);
+    delta_time = elapsed * dfreq - doffset;
+    UTI_AddDoubleToTimeval(&samples[i].when, delta_time, &samples[i].when);
+    samples[i].offset += delta_time;
+  }
+  return;
+}
+
+/* ================================================== */
+
+void
+MNL_Enable(void)
+{
+  enabled = 1;
+}
+
+
+/* ================================================== */
+
+void
+MNL_Disable(void)
+{
+  enabled = 0;
+}
+
+/* ================================================== */
+
+void
+MNL_Reset(void)
+{
+  n_samples = 0;
+}
+
+/* ================================================== */
+/* Generate report data for the REQ_MANUAL_LIST command/monitoring
+   protocol */
+
+void
+MNL_ReportSamples(RPT_ManualSamplesReport *report, int max, int *n)
+{
+  int i;
+
+  if (n_samples > max) {
+    *n = max;
+  } else {
+    *n = n_samples;
+  }
+
+  for (i=0; i<n_samples && i<max; i++) {
+    report[i].when = samples[i].when.tv_sec;
+    report[i].slewed_offset = samples[i].offset;
+    report[i].orig_offset = samples[i].orig_offset;
+    report[i].residual = samples[i].residual;
+  }
+}
+
+/* ================================================== */
+/* Delete a sample if it's within range, re-estimate the error and
+   drift and apply it to the system clock. */
+
+int
+MNL_DeleteSample(int index)
+{
+  int i;
+  struct timeval now;
+  double local_clock_err;
+
+  if ((index < 0) || (index >= n_samples)) {
+    return 0;
+  }
+
+  /* Crunch the samples down onto the one being deleted */
+
+  for (i=index; i<(n_samples-1); i++) {
+    samples[i] = samples[i+1];
+  }
+  
+  n_samples -= 1;
+
+  /* Now re-estimate.  NULLs because we don't want the parameters back
+     in this case. */
+  LCL_ReadCookedTime(&now, &local_clock_err);
+  estimate_and_set_system(&now, 0, 0.0, NULL, NULL, NULL);
+
+  return 1;
+
+}
+
+/* ================================================== */
+
+
diff --git a/manual.h b/manual.h
new file mode 100644 (file)
index 0000000..86045ee
--- /dev/null
+++ b/manual.h
@@ -0,0 +1,49 @@
+/*
+  $Header: /cvs/src/chrony/manual.h,v 1.12 2002/02/28 23:27:11 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for manual time input module.
+
+  */
+
+#ifndef GOT_MANUAL_H
+#define GOT_MANUAL_H
+
+#include "sysincl.h"
+#include "reports.h"
+
+extern void MNL_Initialise(void);
+extern void MNL_Finalise(void);
+extern int MNL_AcceptTimestamp(struct timeval *ts, long *offset_cs, double *dfreq_ppm, double *new_afreq_ppm);
+
+extern void MNL_Enable(void);
+extern void MNL_Disable(void);
+extern void MNL_Reset(void);
+
+extern void MNL_ReportSamples(RPT_ManualSamplesReport *report, int max, int *n);
+extern int MNL_DeleteSample(int index);
+
+#endif /* GOT_MANUAL_H */
diff --git a/md5.c b/md5.c
new file mode 100644 (file)
index 0000000..f997a6e
--- /dev/null
+++ b/md5.c
@@ -0,0 +1,322 @@
+/*
+ ***********************************************************************
+ ** md5.c -- the source code for MD5 routines                         **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm              **
+ ** Created: 2/17/90 RLR                                              **
+ ** Revised: 1/91 SRD,AJ,BSK,JT Reference C Version                   **
+ ** Revised (for MD5): RLR 4/27/91                                    **
+ **   -- G modified to have y&~z instead of y&z                       **
+ **   -- FF, GG, HH modified to add in last register done             **
+ **   -- Access pattern: round 2 works mod 5, round 3 works mod 3     **
+ **   -- distinct additive constant for each step                     **
+ **   -- round 4 added, working mod 7                                 **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.  **
+ **                                                                   **
+ ** License to copy and use this software is granted provided that    **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message-     **
+ ** Digest Algorithm" in all material mentioning or referencing this  **
+ ** software or this function.                                        **
+ **                                                                   **
+ ** License is also granted to make and use derivative works          **
+ ** provided that such works are identified as "derived from the RSA  **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all          **
+ ** material mentioning or referencing the derived work.              **
+ **                                                                   **
+ ** RSA Data Security, Inc. makes no representations concerning       **
+ ** either the merchantability of this software or the suitability    **
+ ** of this software for any particular purpose.  It is provided "as  **
+ ** is" without express or implied warranty of any kind.              **
+ **                                                                   **
+ ** These notices must be retained in any copies of any part of this  **
+ ** documentation and/or software.                                    **
+ ***********************************************************************
+ */
+
+#include "md5.h"
+
+/*
+ ***********************************************************************
+ **  Message-digest routines:                                         **
+ **  To form the message digest for a message M                       **
+ **    (1) Initialize a context buffer mdContext using MD5Init        **
+ **    (2) Call MD5Update on mdContext and M                          **
+ **    (3) Call MD5Final on mdContext                                 **
+ **  The message digest is now in mdContext->digest[0...15]           **
+ ***********************************************************************
+ */
+
+/* forward declaration */
+static void Transform (UINT4 *, UINT4 *);
+
+#ifdef __STDC__
+static const
+#else
+static
+#endif
+unsigned char PADDING[64] = {
+  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* F, G, H and I are basic MD5 functions */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits */
+#if    defined(FAST_MD5) && defined(__GNUC__) && defined(mc68000)
+/*
+ * If we're on a 68000 based CPU and using a GNU C compiler with
+ * inline assembly code, we can speed this up a bit.
+ */
+inline UINT4 ROTATE_LEFT(UINT4 x, int n)
+{   
+    asm("roll %2,%0" : "=d" (x) : "0" (x), "Ir" (n));
+    return x;
+}
+#else
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+#endif
+
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */
+/* Rotation is separate from addition to prevent recomputation */
+#define FF(a, b, c, d, x, s, ac) \
+  {(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define GG(a, b, c, d, x, s, ac) \
+  {(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define HH(a, b, c, d, x, s, ac) \
+  {(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define II(a, b, c, d, x, s, ac) \
+  {(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+
+/* The routine MD5Init initializes the message-digest context
+   mdContext. All fields are set to zero.
+ */
+void MD5Init (mdContext)
+MD5_CTX *mdContext;
+{
+  mdContext->i[0] = mdContext->i[1] = (UINT4)0;
+
+  /* Load magic initialization constants.
+   */
+  mdContext->buf[0] = (UINT4)0x67452301;
+  mdContext->buf[1] = (UINT4)0xefcdab89;
+  mdContext->buf[2] = (UINT4)0x98badcfe;
+  mdContext->buf[3] = (UINT4)0x10325476;
+}
+
+/* The routine MD5Update updates the message-digest context to
+   account for the presence of each of the characters inBuf[0..inLen-1]
+   in the message whose digest is being computed.
+ */
+void MD5Update (mdContext, inBuf, inLen)
+MD5_CTX *mdContext;
+unsigned const char *inBuf;
+unsigned int inLen;
+{
+  UINT4 in[16];
+  int mdi;
+  unsigned int i, ii;
+
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* update number of bits */
+  if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0])
+    mdContext->i[1]++;
+  mdContext->i[0] += ((UINT4)inLen << 3);
+  mdContext->i[1] += ((UINT4)inLen >> 29);
+
+  while (inLen--) {
+    /* add new character to buffer, increment mdi */
+    mdContext->in[mdi++] = *inBuf++;
+
+    /* transform if necessary */
+    if (mdi == 0x40) {
+      for (i = 0, ii = 0; i < 16; i++, ii += 4)
+        in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+                (((UINT4)mdContext->in[ii+2]) << 16) |
+                (((UINT4)mdContext->in[ii+1]) << 8) |
+                ((UINT4)mdContext->in[ii]);
+      Transform (mdContext->buf, in);
+      mdi = 0;
+    }
+  }
+}
+
+/* The routine MD5Final terminates the message-digest computation and
+   ends with the desired message digest in mdContext->digest[0...15].
+ */
+
+void MD5Final (mdContext)
+MD5_CTX *mdContext;
+{
+  UINT4 in[16];
+  int mdi;
+  unsigned int i, ii;
+  unsigned int padLen;
+
+  /* save number of bits */
+  in[14] = mdContext->i[0];
+  in[15] = mdContext->i[1];
+
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* pad out to 56 mod 64 */
+  padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi);
+  MD5Update (mdContext, PADDING, padLen);
+
+  /* append length in bits and transform */
+  for (i = 0, ii = 0; i < 14; i++, ii += 4)
+    in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+            (((UINT4)mdContext->in[ii+2]) << 16) |
+            (((UINT4)mdContext->in[ii+1]) << 8) |
+            ((UINT4)mdContext->in[ii]);
+  Transform (mdContext->buf, in);
+
+  /* store buffer in digest */
+  for (i = 0, ii = 0; i < 4; i++, ii += 4) {
+    mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF);
+    mdContext->digest[ii+1] =
+      (unsigned char)((mdContext->buf[i] >> 8) & 0xFF);
+    mdContext->digest[ii+2] =
+      (unsigned char)((mdContext->buf[i] >> 16) & 0xFF);
+    mdContext->digest[ii+3] =
+      (unsigned char)((mdContext->buf[i] >> 24) & 0xFF);
+  }
+}
+
+/* Basic MD5 step. Transforms buf based on in.
+ */
+static void Transform (buf, in)
+UINT4 *buf;
+UINT4 *in;
+{
+  UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+  /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+
+  FF ( a, b, c, d, in[ 0], S11, 0xd76aa478); /* 1 */
+  FF ( d, a, b, c, in[ 1], S12, 0xe8c7b756); /* 2 */
+  FF ( c, d, a, b, in[ 2], S13, 0x242070db); /* 3 */
+  FF ( b, c, d, a, in[ 3], S14, 0xc1bdceee); /* 4 */
+  FF ( a, b, c, d, in[ 4], S11, 0xf57c0faf); /* 5 */
+  FF ( d, a, b, c, in[ 5], S12, 0x4787c62a); /* 6 */
+  FF ( c, d, a, b, in[ 6], S13, 0xa8304613); /* 7 */
+  FF ( b, c, d, a, in[ 7], S14, 0xfd469501); /* 8 */
+  FF ( a, b, c, d, in[ 8], S11, 0x698098d8); /* 9 */
+  FF ( d, a, b, c, in[ 9], S12, 0x8b44f7af); /* 10 */
+  FF ( c, d, a, b, in[10], S13, 0xffff5bb1); /* 11 */
+  FF ( b, c, d, a, in[11], S14, 0x895cd7be); /* 12 */
+  FF ( a, b, c, d, in[12], S11, 0x6b901122); /* 13 */
+  FF ( d, a, b, c, in[13], S12, 0xfd987193); /* 14 */
+  FF ( c, d, a, b, in[14], S13, 0xa679438e); /* 15 */
+  FF ( b, c, d, a, in[15], S14, 0x49b40821); /* 16 */
+
+  /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+  GG ( a, b, c, d, in[ 1], S21, 0xf61e2562); /* 17 */
+  GG ( d, a, b, c, in[ 6], S22, 0xc040b340); /* 18 */
+  GG ( c, d, a, b, in[11], S23, 0x265e5a51); /* 19 */
+  GG ( b, c, d, a, in[ 0], S24, 0xe9b6c7aa); /* 20 */
+  GG ( a, b, c, d, in[ 5], S21, 0xd62f105d); /* 21 */
+  GG ( d, a, b, c, in[10], S22,  0x2441453); /* 22 */
+  GG ( c, d, a, b, in[15], S23, 0xd8a1e681); /* 23 */
+  GG ( b, c, d, a, in[ 4], S24, 0xe7d3fbc8); /* 24 */
+  GG ( a, b, c, d, in[ 9], S21, 0x21e1cde6); /* 25 */
+  GG ( d, a, b, c, in[14], S22, 0xc33707d6); /* 26 */
+  GG ( c, d, a, b, in[ 3], S23, 0xf4d50d87); /* 27 */
+  GG ( b, c, d, a, in[ 8], S24, 0x455a14ed); /* 28 */
+  GG ( a, b, c, d, in[13], S21, 0xa9e3e905); /* 29 */
+  GG ( d, a, b, c, in[ 2], S22, 0xfcefa3f8); /* 30 */
+  GG ( c, d, a, b, in[ 7], S23, 0x676f02d9); /* 31 */
+  GG ( b, c, d, a, in[12], S24, 0x8d2a4c8a); /* 32 */
+
+  /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+  HH ( a, b, c, d, in[ 5], S31, 0xfffa3942); /* 33 */
+  HH ( d, a, b, c, in[ 8], S32, 0x8771f681); /* 34 */
+  HH ( c, d, a, b, in[11], S33, 0x6d9d6122); /* 35 */
+  HH ( b, c, d, a, in[14], S34, 0xfde5380c); /* 36 */
+  HH ( a, b, c, d, in[ 1], S31, 0xa4beea44); /* 37 */
+  HH ( d, a, b, c, in[ 4], S32, 0x4bdecfa9); /* 38 */
+  HH ( c, d, a, b, in[ 7], S33, 0xf6bb4b60); /* 39 */
+  HH ( b, c, d, a, in[10], S34, 0xbebfbc70); /* 40 */
+  HH ( a, b, c, d, in[13], S31, 0x289b7ec6); /* 41 */
+  HH ( d, a, b, c, in[ 0], S32, 0xeaa127fa); /* 42 */
+  HH ( c, d, a, b, in[ 3], S33, 0xd4ef3085); /* 43 */
+  HH ( b, c, d, a, in[ 6], S34,  0x4881d05); /* 44 */
+  HH ( a, b, c, d, in[ 9], S31, 0xd9d4d039); /* 45 */
+  HH ( d, a, b, c, in[12], S32, 0xe6db99e5); /* 46 */
+  HH ( c, d, a, b, in[15], S33, 0x1fa27cf8); /* 47 */
+  HH ( b, c, d, a, in[ 2], S34, 0xc4ac5665); /* 48 */
+
+  /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+  II ( a, b, c, d, in[ 0], S41, 0xf4292244); /* 49 */
+  II ( d, a, b, c, in[ 7], S42, 0x432aff97); /* 50 */
+  II ( c, d, a, b, in[14], S43, 0xab9423a7); /* 51 */
+  II ( b, c, d, a, in[ 5], S44, 0xfc93a039); /* 52 */
+  II ( a, b, c, d, in[12], S41, 0x655b59c3); /* 53 */
+  II ( d, a, b, c, in[ 3], S42, 0x8f0ccc92); /* 54 */
+  II ( c, d, a, b, in[10], S43, 0xffeff47d); /* 55 */
+  II ( b, c, d, a, in[ 1], S44, 0x85845dd1); /* 56 */
+  II ( a, b, c, d, in[ 8], S41, 0x6fa87e4f); /* 57 */
+  II ( d, a, b, c, in[15], S42, 0xfe2ce6e0); /* 58 */
+  II ( c, d, a, b, in[ 6], S43, 0xa3014314); /* 59 */
+  II ( b, c, d, a, in[13], S44, 0x4e0811a1); /* 60 */
+  II ( a, b, c, d, in[ 4], S41, 0xf7537e82); /* 61 */
+  II ( d, a, b, c, in[11], S42, 0xbd3af235); /* 62 */
+  II ( c, d, a, b, in[ 2], S43, 0x2ad7d2bb); /* 63 */
+  II ( b, c, d, a, in[ 9], S44, 0xeb86d391); /* 64 */
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+/*
+ ***********************************************************************
+ ** End of md5.c                                                      **
+ ******************************** (cut) ********************************
+ */
diff --git a/md5.h b/md5.h
new file mode 100644 (file)
index 0000000..aaf9548
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,60 @@
+/*
+ ***********************************************************************
+ ** md5.h -- header file for implementation of MD5                    **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm              **
+ ** Created: 2/17/90 RLR                                              **
+ ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version               **
+ ** Revised (for MD5): RLR 4/27/91                                    **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.  **
+ **                                                                   **
+ ** License to copy and use this software is granted provided that    **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message-     **
+ ** Digest Algorithm" in all material mentioning or referencing this  **
+ ** software or this function.                                        **
+ **                                                                   **
+ ** License is also granted to make and use derivative works          **
+ ** provided that such works are identified as "derived from the RSA  **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all          **
+ ** material mentioning or referencing the derived work.              **
+ **                                                                   **
+ ** RSA Data Security, Inc. makes no representations concerning       **
+ ** either the merchantability of this software or the suitability    **
+ ** of this software for any particular purpose.  It is provided "as  **
+ ** is" without express or implied warranty of any kind.              **
+ **                                                                   **
+ ** These notices must be retained in any copies of any part of this  **
+ ** documentation and/or software.                                    **
+ ***********************************************************************
+ */
+
+#ifdef HAS_STDINT_H
+#include <stdint.h>
+#elif defined(HAS_INTTYPES_H)
+#include <inttypes.h>
+#endif
+
+/* typedef a 32-bit type */
+typedef uint32_t UINT4;
+
+/* Data structure for MD5 (Message-Digest) computation */
+typedef struct {
+  UINT4 i[2];                   /* number of _bits_ handled mod 2^64 */
+  UINT4 buf[4];                                    /* scratch buffer */
+  unsigned char in[64];                              /* input buffer */
+  unsigned char digest[16];     /* actual digest after MD5Final call */
+} MD5_CTX;
+
+void MD5Init (MD5_CTX *mdContext);
+void MD5Update (MD5_CTX *, unsigned const char *, unsigned int);
+void MD5Final (MD5_CTX *);
+
+/*
+ ***********************************************************************
+ ** End of md5.h                                                      **
+ ******************************** (cut) ********************************
+ */
diff --git a/memory.h b/memory.h
new file mode 100644 (file)
index 0000000..bf4d48d
--- /dev/null
+++ b/memory.h
@@ -0,0 +1,43 @@
+/*
+  $Header: /cvs/src/chrony/memory.h,v 1.7 2002/02/28 23:27:11 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for memory functions
+  */
+
+#ifndef GOT_MEMORY_H
+#define GOT_MEMORY_H
+
+#include <stdlib.h>
+
+#define Malloc(x) malloc(x)
+#define MallocNew(T) ((T *) malloc(sizeof(T)))
+#define MallocArray(T, n) ((T *) malloc((n) * sizeof(T)))
+#define Realloc(x,y) realloc(x,y)
+#define ReallocArray(T,n,x) ((T *) realloc((void *)(x), (n)*sizeof(T)))
+#define Free(x) free(x)
+
+#endif /* GOT_MEMORY_H */
diff --git a/mkdirpp.c b/mkdirpp.c
new file mode 100644 (file)
index 0000000..5e6af7d
--- /dev/null
+++ b/mkdirpp.c
@@ -0,0 +1,135 @@
+/*
+  $Header: /cvs/src/chrony/mkdirpp.c,v 1.10 2002/11/03 22:49:17 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  A function for creating a directory and any parent directories that
+  don't exist.
+
+  */
+
+#include "sysincl.h"
+
+#include "mkdirpp.h"
+
+static int
+do_dir(char *p)
+{
+  int status;
+  struct stat buf;
+
+#if defined(TEST)
+  fprintf(stderr, "do_dir(%s)\n", p);
+#endif
+
+  /* See if directory exists */
+  status = stat(p, &buf);
+
+  if (status < 0) {
+    if (errno == ENOENT) {
+      /* Try to create directory */
+      status = mkdir(p, 0755);
+      return status;
+    } else {
+      return status;
+    }
+  }
+
+  if (!S_ISDIR(buf.st_mode)) {
+    return -1;
+  }
+
+  return 0;
+}
+
+/* ================================================== */
+/* Return 0 if the directory couldn't be created, 1 if it could (or
+   already existed) */
+
+int
+mkdir_and_parents(const char *path)
+{
+  char *p;
+  int len;
+  int i, j, k, last;
+  len = strlen(path);
+
+  p = (char *) malloc(1 + len);
+
+  i = k = 0;
+  while (1) {
+    p[i++] = path[k++];
+    
+    if (path[k] == '/' || !path[k]) {
+      p[i] = 0;
+
+      if (do_dir(p) < 0) {
+        return 0;
+      }
+
+      if (!path[k]) {
+        /* End of the string */
+        break;
+      }
+
+      /* check whether its a trailing / or group of / */
+      last = 1;
+      j = k+1;
+      while (path[j]) {
+        if (path[j] != '/') {
+          k = j - 1; /* Pick up a / into p[] thru the assignment at the top of the loop */
+          last = 0;
+          break;
+        }
+        j++;
+      }
+
+      if (last) {
+        break;
+      }
+    }
+
+    if (!path[k]) break;
+
+  }  
+
+  free(p);
+  return 1;
+
+}
+
+/* ================================================== */
+
+#if defined(TEST)
+int main(int argc, char **argv) {
+  if (argc > 1) {
+    /* Invert sense of result */
+    return mkdir_and_parents(argv[1]) ? 0 : 1;
+  } else {
+    return 1;
+  }
+}
+#endif
+                                  
diff --git a/mkdirpp.h b/mkdirpp.h
new file mode 100644 (file)
index 0000000..632a7da
--- /dev/null
+++ b/mkdirpp.h
@@ -0,0 +1,35 @@
+/* 
+   $Header: /cvs/src/chrony/mkdirpp.h,v 1.6 2002/02/28 23:27:11 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+   */
+
+#ifndef GOT_MKDIRPP_H
+#define GOT_MKDIRPP_H
+
+extern int mkdir_and_parents(const char *path);
+
+#endif
diff --git a/nameserv.c b/nameserv.c
new file mode 100644 (file)
index 0000000..6a4d6e9
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+  $Header: /cvs/src/chrony/nameserv.c,v 1.13 2002/02/28 23:27:11 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Functions to do name to IP address conversion
+
+  */
+
+#include "sysincl.h"
+
+#include "nameserv.h"
+
+/* ================================================== */
+
+unsigned long
+DNS_Name2IPAddress(const char *name)
+{
+  struct hostent *host;
+  unsigned char *address0;
+  unsigned long result;
+
+  host = gethostbyname(name);
+  if (host == NULL) {
+    result = DNS_Failed_Address;
+  } else {
+    address0 = host->h_addr_list[0];
+    result = ((((unsigned long)address0[0])<<24) |
+             (((unsigned long)address0[1])<<16) |
+             (((unsigned long)address0[2])<<8) |
+             (((unsigned long)address0[3])));
+  }
+
+  return result;
+
+}
+
+/* ================================================== */
+
+const char *
+DNS_IPAddress2Name(unsigned long ip_addr)
+{
+  struct hostent *host;
+  static char buffer[16];
+  unsigned int a, b, c, d;
+  unsigned long addr;
+
+  addr = htonl(ip_addr);
+  if (addr == 0UL) {
+    /* Catch this as a special case that will never resolve to
+       anything */
+    strcpy(buffer, "0.0.0.0");
+    return buffer;
+  } else {
+    host = gethostbyaddr((const char *) &addr, sizeof(ip_addr), AF_INET);
+    if (!host) {
+      a = (ip_addr >> 24) & 0xff;
+      b = (ip_addr >> 16) & 0xff;
+      c = (ip_addr >>  8) & 0xff;
+      d = (ip_addr)       & 0xff;
+      sprintf(buffer, "%u.%u.%u.%u", a, b, c, d);
+      return buffer;
+    } else {
+      return host->h_name;
+    }
+  }
+}
+
+/* ================================================== */
+
diff --git a/nameserv.h b/nameserv.h
new file mode 100644 (file)
index 0000000..e62f334
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+  $Header: /cvs/src/chrony/nameserv.h,v 1.8 2002/02/28 23:27:11 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Module header for nameserver functions
+  */
+
+
+#ifndef GOT_NAMESERV_H
+#define GOT_NAMESERV_H
+
+static const unsigned long DNS_Failed_Address = 0x0UL;
+
+extern unsigned long DNS_Name2IPAddress(const char *name);
+
+const char *DNS_IPAddress2Name(unsigned long ip_addr);
+
+#endif /* GOT_NAMESERV_H */
+
diff --git a/ntp.h b/ntp.h
new file mode 100644 (file)
index 0000000..b0e0bb9
--- /dev/null
+++ b/ntp.h
@@ -0,0 +1,116 @@
+/*
+  $Header: /cvs/src/chrony/ntp.h,v 1.11 2003/04/10 21:28:11 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file containing common NTP bits and pieces
+  */
+
+#ifndef GOT_NTP_H
+#define GOT_NTP_H
+
+#ifdef HAS_STDINT_H
+#include <stdint.h>
+#elif defined(HAS_INTTYPES_H)
+#include <inttypes.h>
+#endif
+
+typedef struct {
+  uint32_t hi;
+  uint32_t lo;
+} NTP_int64;
+
+typedef uint32_t NTP_int32;
+
+#define AUTH_DATA_LEN 16 
+
+/* Type definition for leap bits */
+typedef enum {
+  LEAP_Normal = 0,
+  LEAP_InsertSecond = 1,
+  LEAP_DeleteSecond = 2,
+  LEAP_Unsynchronised = 3
+} NTP_Leap;
+
+typedef enum {
+  MODE_UNDEFINED = 0,
+  MODE_ACTIVE = 1,
+  MODE_PASSIVE = 2,
+  MODE_CLIENT = 3,
+  MODE_SERVER = 4,
+  MODE_BROADCAST = 5
+} NTP_Mode;
+
+typedef struct {
+  uint8_t lvm;
+  uint8_t stratum;
+  int8_t poll;
+  int8_t precision;
+  NTP_int32 root_delay;
+  NTP_int32 root_dispersion;
+  NTP_int32 reference_id;
+  NTP_int64 reference_ts;
+  NTP_int64 originate_ts;
+  NTP_int64 receive_ts;
+  NTP_int64 transmit_ts;
+  NTP_int32 auth_keyid;
+  uint8_t auth_data[AUTH_DATA_LEN];
+} NTP_Packet;
+
+/* We have to declare a buffer type to hold a datagram read from the
+   network.  Even though we won't be using them (yet?!), this must be
+   large enough to hold NTP control messages. */
+
+/* Define the maximum number of bytes that can be read in a single
+   message.  (This is cribbed from ntp.h in the xntpd source code). */
+
+#define MAX_NTP_MESSAGE_SIZE (468+12+16+4)
+
+typedef union {
+  NTP_Packet ntp_pkt;
+  uint8_t arbitrary[MAX_NTP_MESSAGE_SIZE];
+} ReceiveBuffer;
+
+#define NTP_NORMAL_PACKET_SIZE (sizeof(NTP_Packet) - (sizeof(NTP_int32) + AUTH_DATA_LEN))
+
+/* ================================================== */
+
+inline static double
+int32_to_double(NTP_int32 x)
+{
+  return (double) ntohl(x) / 65536.0;
+}
+
+/* ================================================== */
+
+inline static NTP_int32
+double_to_int32(double x)
+{
+  return htonl((NTP_int32)(0.5 + 65536.0 * x));
+}
+
+/* ================================================== */
+
+#endif /* GOT_NTP_H */
diff --git a/ntp_core.c b/ntp_core.c
new file mode 100644 (file)
index 0000000..6d5852a
--- /dev/null
@@ -0,0 +1,1867 @@
+/*
+  $Header: /cvs/src/chrony/ntp_core.c,v 1.49 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Core NTP protocol engine
+  */
+
+#include "sysincl.h"
+
+#include "ntp_core.h"
+#include "ntp_io.h"
+#include "memory.h"
+#include "sched.h"
+#include "reference.h"
+#include "local.h"
+#include "sources.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "keys.h"
+#include "md5.h"
+#include "addrfilt.h"
+#include "mkdirpp.h"
+#include "clientlog.h"
+
+/* ================================================== */
+/* File handle for file to which we write the measurement log */
+static FILE *logfile = NULL;
+
+static char *logfilename = NULL;
+static unsigned long logwrites=0;
+
+#define MEASUREMENTS_LOG "measurements.log" 
+
+/* ================================================== */
+
+/* Day number of 1 Jan 1970 */
+#define MJD_1970 40587
+
+/* ================================================== */
+
+#define ZONE_WIDTH 4
+
+/* ================================================== */
+/* Enumeration used for remembering the operating mode of one of the
+   sources */
+
+typedef enum {
+  MD_OFFLINE,                   /* No sampling at all */
+  MD_ONLINE,                    /* Normal sampling based on sampling interval */
+  MD_BURST_WAS_OFFLINE,         /* Burst sampling, return to offline afterwards */
+  MD_BURST_WAS_ONLINE,          /* Burst sampling, return to online afterwards */
+} OperatingMode;
+
+/* ================================================== */
+/* Structure used for holding a single peer/server's
+   protocol machine */
+
+struct NCR_Instance_Record {
+  NTP_Remote_Address remote_addr; /* Needed for routing transmit packets */
+  NTP_Mode mode;                /* The source's NTP mode
+                                   (client/server or symmetric active peer) */
+  OperatingMode opmode;         /* Whether we are sampling this source
+                                   or not and in what way */
+  int timer_running;            /* Boolean indicating whether we have a timeout
+                                   pending to transmit to the source */
+  SCH_TimeoutID timeout_id;     /* Scheduler's timeout ID, if we are
+                                   running on a timer. */
+
+  int auto_offline;             /* If 1, automatically go offline if server/peer
+                                   isn't responding */
+
+  int local_poll;               /* Log2 of polling interval at our end */
+  int remote_poll;              /* Log2 of server/peer's polling interval (recovered
+                                   from received packets) */
+
+  int presend_minpoll;           /* If the current polling interval is
+                                    at least this, an echo datagram
+                                    will be send some time before every
+                                    transmit.  This ensures that both
+                                    us and the server/peer have an ARP
+                                    entry for each other ready, which
+                                    means our measurement is not
+                                    botched by an ARP round-trip on one
+                                    side or the other. */
+
+  int presend_done;             /* The presend packet has been sent */
+
+  int minpoll;                  /* Log2 of minimum defined polling interval */
+  int maxpoll;                  /* Log2 of maximum defined polling interval */
+
+  double max_delay;             /* Maximum round-trip delay to the
+                                   peer that we can tolerate and still
+                                   use the sample for generating
+                                   statistics from */
+
+  double max_delay_ratio;       /* Largest ratio of delta /
+                                   min_delay_in_register that we can
+                                   tolerate.  */
+
+  int do_auth;                  /* Flag indicating whether we
+                                   authenticate packets we send to
+                                   this machine (if it's serving us or
+                                   the association is symmetric). Note
+                                   : we don't authenticate if we can't
+                                   find the key in our database. */
+  unsigned long auth_key_id;    /* The ID of the authentication key to
+                                   use. */
+
+  /* Count of how many packets we have transmitted since last successful
+     receive from this peer */
+  int tx_count;
+
+  /* Timestamp in tx field of last received packet.  We have to
+     reproduce this exactly as the orig field or our outgoing
+     packet. */
+  NTP_int64 remote_orig;     
+
+  /* Local timestamp when the last packet was received from the
+     source.  We have to be prepared to tinker with this if the local
+     clock has its frequency adjusted before we repond.  The value we
+     store here is what our own local time was when the same arrived.
+     Before replying, we have to correct this to fit with the
+     parameters for the current reference.  (It must be stored
+     relative to local time to permit frequency and offset adjustments
+     to be made when we trim the local clock). */
+  struct timeval local_rx;
+
+  /* Local timestamp when we last transmitted a packet to the source.
+     We store two versions.  The first is in NTP format, and is used
+     to validate the next received packet from the source.
+     Additionally, this is corrected to bring it into line with the
+     current reference.  The second is in timeval format, and is kept
+     relative to the local clock.  We modify this in accordance with
+     local clock frequency/offset changes, and use this for computing
+     statistics about the source when a return packet arrives. */
+  NTP_int64 local_ntp_tx;
+  struct timeval local_tx;
+
+  /* The instance record in the main source management module.  This
+     performs the statistical analysis on the samples we generate */
+
+  SRC_Instance source;
+
+  int score;
+
+  int burst_good_samples_to_go;
+  int burst_total_samples_to_go;
+
+};
+
+/* ================================================== */
+/* Initial delay period before first packet is transmitted (in seconds) */
+#define INITIAL_DELAY 2.0
+
+/* Spacing required between samples for any two servers/peers (to
+   minimise risk of network collisions) (in seconds) */
+#define SAMPLING_SEPARATION 2.0
+
+/* Time to wait before retransmitting in burst mode, if we did not get
+   a reply to the previous probe */
+#define BURST_TIMEOUT 8.0
+
+/* The NTP protocol version that we support */
+#define NTP_VERSION 3
+
+/* Maximum allowed dispersion - as defined in RFC1305 (16 seconds) */
+#define NTP_MAX_DISPERSION 16.0
+
+/* Maximum allowed age of a reference to be supplied to a client (1 day) */
+#define NTP_MAXAGE 86400
+
+/* Maximum allowed stratum */
+#define NTP_MAX_STRATUM 15
+
+/* ================================================== */
+
+static ADF_AuthTable access_auth_table;
+
+static int md5_offset_usecs;
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void transmit_timeout(void *arg);
+static void determine_md5_delay(void);
+
+/* ================================================== */
+
+void
+NCR_Initialise(void)
+{
+  char *direc;
+
+  if (CNF_GetLogMeasurements()) {
+    direc = CNF_GetLogDir();
+    if (!mkdir_and_parents(direc)) {
+      LOG(LOGS_ERR, LOGF_NtpCore, "Could not create directory %s", direc);
+      logfile = NULL;
+    } else {
+      logfilename = MallocArray(char, 2 + strlen(direc) + strlen(MEASUREMENTS_LOG));
+      strcpy(logfilename, direc);
+      strcat(logfilename, "/");
+      strcat(logfilename, MEASUREMENTS_LOG);
+      logfile = fopen(logfilename, "a");
+      if (!logfile) {
+        LOG(LOGS_WARN, LOGF_NtpCore, "Couldn't open logfile %s for update", logfilename);
+      }
+    }
+  }
+
+  access_auth_table = ADF_CreateTable();
+
+  determine_md5_delay();
+
+}
+
+/* ================================================== */
+
+void
+NCR_Finalise(void)
+{
+  if (logfile) {
+    fclose(logfile);
+  }
+
+  ADF_DestroyTable(access_auth_table);
+
+}
+
+/* ================================================== */
+
+static void
+start_initial_timeout(NCR_Instance inst)
+{
+
+  /* Start timer for first transmission */
+  inst->timeout_id = SCH_AddTimeoutInClass(INITIAL_DELAY, SAMPLING_SEPARATION,
+                                           SCH_NtpSamplingClass,
+                                           transmit_timeout, (void *)inst);
+  inst->timer_running = 1;
+
+  return;
+}
+
+/* ================================================== */
+
+static NCR_Instance
+create_instance(NTP_Remote_Address *remote_addr, NTP_Mode mode, SourceParameters *params)
+{
+  NCR_Instance result;
+
+  result = MallocNew(struct NCR_Instance_Record);
+
+  result->remote_addr = *remote_addr;
+  result->mode = mode;
+
+  result->minpoll = params->minpoll;
+  result->maxpoll = params->maxpoll;
+
+  result->presend_minpoll = params->presend_minpoll;
+  result->presend_done = 0;
+
+  if (params->authkey == INACTIVE_AUTHKEY) {
+    result->do_auth = 0;
+    result->auth_key_id = 0UL;
+  } else {
+    result->do_auth = 1;
+    result->auth_key_id = params->authkey;
+  }
+
+  result->max_delay = params->max_delay;
+  result->max_delay_ratio = params->max_delay_ratio;
+
+  result->tx_count = 0;
+
+  result->score = 0;
+
+  if (params->online) {
+    start_initial_timeout(result);
+    result->opmode = MD_ONLINE;
+  } else {
+    result->timer_running = 0;
+    result->timeout_id = 0;
+    result->opmode = MD_OFFLINE;
+  }
+  
+  result->auto_offline = params->auto_offline;
+  
+  result->local_poll = params->minpoll;
+
+  /* Create a source instance for this NTP source */
+  result->source = SRC_CreateNewInstance(remote_addr->ip_addr); /* Will need extra params eventually */
+
+  result->local_rx.tv_sec = 0;
+  result->local_rx.tv_usec = 0;
+
+  return result;
+
+}
+
+/* ================================================== */
+
+/* Get a new instance for a server */
+NCR_Instance
+NCR_GetServerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params)
+{
+  return create_instance(remote_addr, MODE_CLIENT, params);
+}
+
+/* ================================================== */
+
+/* Get a new instance for a peer */
+NCR_Instance
+NCR_GetPeerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params)
+{
+  return create_instance(remote_addr, MODE_ACTIVE, params);
+}
+
+/* ================================================== */
+
+/* Destroy an instance */
+void
+NCR_DestroyInstance(NCR_Instance instance)
+{
+  /* This will destroy the source instance inside the
+     structure, which will cause reselection if this was the
+     synchronising source etc. */
+  SRC_DestroyInstance(instance->source);
+
+  /* Cancel any pending timeouts */
+  if (instance->timer_running) {
+    SCH_RemoveTimeout(instance->timeout_id);
+    instance->timer_running = 0;
+  }
+
+  /* Free the data structure */
+  Free(instance);
+  return;
+}
+
+/* ================================================== */
+
+/* ================================================== */
+
+static int
+generate_packet_auth(NTP_Packet *pkt, unsigned long keyid)
+{
+  int keylen;
+  char *keytext;
+  int keyok;
+  MD5_CTX ctx;
+
+  keyok = KEY_GetKey(keyid, &keytext, &keylen);
+  if (keyok) {
+    pkt->auth_keyid = htonl(keyid);
+    MD5Init(&ctx);
+    MD5Update(&ctx, keytext, keylen);
+    MD5Update(&ctx, (unsigned char *) pkt, offsetof(NTP_Packet, auth_keyid));
+    MD5Final(&ctx);
+    memcpy(&(pkt->auth_data), &ctx.digest, 16);
+    return 1;
+  } else {
+    pkt->auth_keyid = htonl(0);
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static void
+determine_md5_delay(void)
+{
+  NTP_Packet pkt;
+  struct timeval before, after;
+  unsigned long usecs, min_usecs=0;
+  MD5_CTX ctx;
+  static const char *example_key = "#a0,243asd=-b ds";
+  int slen;
+  int i;
+
+  slen = strlen(example_key);
+
+  for (i=0; i<10; i++) {
+    LCL_ReadRawTime(&before);
+    MD5Init(&ctx);
+    MD5Update(&ctx, (unsigned const char *) example_key, slen);
+    MD5Update(&ctx, (unsigned const char *) &pkt, offsetof(NTP_Packet, auth_keyid));
+    MD5Final(&ctx);
+    LCL_ReadRawTime(&after);
+    
+    usecs = (after.tv_sec - before.tv_sec) * 1000000 + (after.tv_usec - before.tv_usec);
+
+    if (i == 0) {
+      min_usecs = usecs;
+    } else {
+      if (usecs < min_usecs) {
+        min_usecs = usecs;
+      }
+    }          
+
+  }
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_NtpCore, "MD5 took %d useconds", min_usecs);
+#endif
+
+  /* Add on a bit extra to allow for copying, conversions etc */
+  md5_offset_usecs = min_usecs + (min_usecs >> 4);
+
+}
+
+/* ================================================== */
+
+static int
+check_packet_auth(NTP_Packet *pkt, unsigned long keyid)
+{
+  int keylen;
+  char *keytext;
+  int keyok;
+  MD5_CTX ctx;
+
+  keyok = KEY_GetKey(keyid, &keytext, &keylen);
+  if (keyok) {
+    pkt->auth_keyid = htonl(keyid);
+    MD5Init(&ctx);
+    MD5Update(&ctx, keytext, keylen);
+    MD5Update(&ctx, (unsigned char *) pkt, offsetof(NTP_Packet, auth_keyid));
+    MD5Final(&ctx);
+    if (!memcmp((void *) &ctx.digest, (void *) &(pkt->auth_data), 16)) {
+      return 1;
+    } else {
+      return 0;
+    }
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+static void
+normalise_score(NCR_Instance inst)
+{
+
+  while (inst->score >= ZONE_WIDTH) {
+    ++inst->local_poll;
+    inst->score -= ZONE_WIDTH;
+  }
+  while (inst->score < 0) {
+    if (inst->local_poll > 0) {
+      --inst->local_poll;
+    }
+    inst->score += ZONE_WIDTH;
+  }
+  
+  /* Clamp polling interval to defined range */
+  if (inst->local_poll < inst->minpoll) {
+    inst->local_poll = inst->minpoll;
+    inst->score = 0;
+  } else if (inst->local_poll > inst->maxpoll) {
+    inst->local_poll = inst->maxpoll;
+    inst->score = ZONE_WIDTH - 1;
+  }
+
+}
+
+/* ================================================== */
+
+static void
+transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */
+                int my_poll, /* The log2 of the local poll interval */
+                int do_auth, /* Boolean indicating whether to authenticate the packet or not */
+                unsigned long key_id, /* The authentication key ID */
+                NTP_int64 *orig_ts, /* Originate timestamp (from received packet) */
+                struct timeval *local_rx, /* Local time request packet was received */
+                struct timeval *local_tx, /* RESULT : Time this reply
+                                             is sent as local time, or
+                                             NULL if don't want to
+                                             know */
+                NTP_int64 *local_ntp_tx, /* RESULT : Time reply sent
+                                            as NTP timestamp
+                                            (including adjustment to
+                                            reference), ignored if
+                                            NULL */
+                NTP_Remote_Address *where_to /* Where to address the reponse to */
+                )
+{
+  NTP_Packet message;
+  int version;
+  int leap;
+  double local_time_err;
+  struct timeval local_transmit;
+
+  /* Parameters read from reference module */
+  int are_we_synchronised, our_stratum;
+  NTP_Leap leap_status;
+  unsigned long our_ref_id;
+  struct timeval our_ref_time;
+  double our_root_delay, our_root_dispersion;
+
+  version = 3;
+
+  LCL_ReadCookedTime(&local_transmit, &local_time_err);
+  REF_GetReferenceParams(&local_transmit,
+                         &are_we_synchronised, &leap_status,
+                         &our_stratum,
+                         &our_ref_id, &our_ref_time,
+                         &our_root_delay, &our_root_dispersion);
+
+  if (are_we_synchronised) {
+    leap = (int) leap_status;
+  } else {
+    leap = 3;
+  }
+
+  /* Generate transmit packet */
+  message.lvm = ((leap << 6) &0xc0) | ((version << 3) & 0x38) | (my_mode & 0x07); 
+  message.stratum = our_stratum;
+  message.poll = my_poll;
+  message.precision = LCL_GetSysPrecisionAsLog();
+
+  /* If we're sending a client mode packet and we aren't synchronized yet, 
+     we might have to set up artificial values for some of these parameters */
+  message.root_delay = double_to_int32(our_root_delay);
+  message.root_dispersion = double_to_int32(our_root_dispersion);
+
+  message.reference_id = htonl((NTP_int32) our_ref_id);
+
+  /* Now fill in timestamps */
+  UTI_TimevalToInt64(&our_ref_time, &message.reference_ts);
+
+  /* Originate - this comes from the last packet the source sent us */
+  message.originate_ts = *orig_ts;
+
+  /* Receive - this is when we received the last packet from the source.
+     This timestamp will have been adjusted so that it will now look to
+     the source like we have been running on our latest estimate of
+     frequency all along */
+  UTI_TimevalToInt64(local_rx, &message.receive_ts);
+
+  /* Transmit - this our local time right now!  Also, we might need to
+     store this for our own use later, next time we receive a message
+     from the source we're sending to now. */
+  LCL_ReadCookedTime(&local_transmit, &local_time_err);
+
+  /* Authenticate */
+  if (do_auth) {
+    /* Pre-compensate the transmit time by approx. how long it will
+       take to generate the MD5 authentication bytes. */
+    local_transmit.tv_usec += md5_offset_usecs;
+    UTI_NormaliseTimeval(&local_transmit);
+    UTI_TimevalToInt64(&local_transmit, &message.transmit_ts);
+    generate_packet_auth(&message, key_id);
+    NIO_SendAuthenticatedPacket(&message, where_to);
+  } else {
+    UTI_TimevalToInt64(&local_transmit, &message.transmit_ts);
+    NIO_SendNormalPacket(&message, where_to);
+  }
+
+  if (local_tx) {
+    *local_tx = local_transmit;
+  }
+
+  if (local_ntp_tx) {
+    *local_ntp_tx = message.transmit_ts;
+  }
+
+}
+
+
+/* ================================================== */
+
+/* ================================================== */
+
+#define WARM_UP_DELAY 4.0
+
+/* ================================================== */
+/* Timeout handler for transmitting to a source. */
+
+static void
+transmit_timeout(void *arg)
+{
+  NCR_Instance inst = (NCR_Instance) arg;
+  NTP_Mode my_mode;
+  double timeout_delay=0.0;
+  int do_timer = 0;
+  int do_auth;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_NtpCore, "Transmit timeout for [%s:%d]",
+      UTI_IPToDottedQuad(inst->remote_addr.ip_addr), inst->remote_addr.port);
+#endif
+
+  /* Check whether we need to 'warm up' the link to the other end by
+     sending an echo exchange to ensure both ends' ARP caches are
+     primed.  On loaded systems this might also help ensure that bits
+     of the program are paged in properly before we start. */
+
+  if ((inst->presend_minpoll > 0) &&
+      (inst->presend_minpoll <= inst->local_poll) &&
+      !inst->presend_done) {
+    
+    /* Send */
+    NIO_SendEcho(&inst->remote_addr);
+
+    inst->presend_done = 1;
+
+    /* Requeue timeout */
+    inst->timer_running = 1;
+    inst->timeout_id = SCH_AddTimeoutInClass(WARM_UP_DELAY, SAMPLING_SEPARATION,
+                                             SCH_NtpSamplingClass,
+                                             transmit_timeout, (void *)inst);
+
+    return;
+  }
+
+  inst->presend_done = 0; /* Reset for next time */
+
+  ++inst->tx_count;
+  if (inst->tx_count >= 9) {
+    /* Mark source unreachable */
+    SRC_UnsetReachable(inst->source);
+  } else if (inst->tx_count >= 3) {
+    if (inst->auto_offline) {
+      NCR_TakeSourceOffline(inst);
+    }
+    /* Do reselection */
+    SRC_SelectSource(0);
+  } else {
+    /* Nothing */
+  }
+
+  /* If the source to which we are currently locked starts to lose
+     connectivity, increase the sampling rate to try and bring it
+     back.  If any other source loses connectivity, back off the
+     sampling rate to reduce wasted sampling. */
+
+  if (SRC_IsSyncPeer(inst->source)) {
+    if (inst->tx_count >= 2) {
+      /* Implies we have missed at least one transmission */
+      inst->score -= 3;
+      normalise_score(inst);
+    }
+  } else {
+    if (inst->tx_count >= 2) {
+      inst->score += 1;
+      normalise_score(inst);
+    }
+  }
+
+  my_mode = inst->mode;
+
+  if (inst->do_auth && KEY_KeyKnown(inst->auth_key_id)) {
+    do_auth = 1;
+  } else {
+    do_auth = 0;
+  }
+
+  transmit_packet(my_mode, inst->local_poll,
+                  do_auth, inst->auth_key_id,
+                  &inst->remote_orig,
+                  &inst->local_rx, &inst->local_tx, &inst->local_ntp_tx,
+                  &inst->remote_addr);
+
+
+  switch (inst->opmode) {
+    case MD_BURST_WAS_OFFLINE:
+      --inst->burst_total_samples_to_go;
+      if (inst->burst_total_samples_to_go <= 0) {
+        inst->opmode = MD_OFFLINE;
+      }
+      break;
+    case MD_BURST_WAS_ONLINE:
+      --inst->burst_total_samples_to_go;
+      if (inst->burst_total_samples_to_go <= 0) {
+        inst->opmode = MD_ONLINE;
+      }
+      break;
+    default:
+      break;
+  }
+
+
+  /* Restart timer for this message */
+  switch (inst->opmode) {
+    case MD_ONLINE:
+      timeout_delay = (double)(1 << inst->local_poll);
+      do_timer = 1;
+      break;
+    case MD_OFFLINE:
+      do_timer = 0;
+      break;
+    case MD_BURST_WAS_ONLINE:
+    case MD_BURST_WAS_OFFLINE:
+      timeout_delay = BURST_TIMEOUT;
+      do_timer = 1;
+      break;
+  }
+
+  if (do_timer) {
+    inst->timer_running = 1;
+    inst->timeout_id = SCH_AddTimeoutInClass(timeout_delay, SAMPLING_SEPARATION,
+                                             SCH_NtpSamplingClass,
+                                             transmit_timeout, (void *)inst);
+  } else {
+    inst->timer_running = 0;
+  }
+
+  /* And we're done */
+  return;
+}
+
+
+/* ================================================== */
+
+static void
+receive_packet(NTP_Packet *message, struct timeval *now, NCR_Instance inst, int do_auth)
+{
+  int pkt_leap;
+  int source_is_synchronized;
+  double pkt_root_delay;
+  double pkt_root_dispersion;
+
+  unsigned long auth_key_id;
+
+  /* The local time to which the (offset, delay, dispersion) triple will
+     be taken to relate.  For client/server operation this is practically
+     the same as either the transmit or receive time.  The difference comes
+     in symmetric active mode, when the receive may come minutes after the transmit, and this time
+     will be midway between the two */
+  struct timeval sample_time;
+
+  /* The estimated offset (nomenclature from RFC1305 section 3.4.4).
+     In seconds, a positive value indicates that the local clock is
+     SLOW of the remote source and a negative value indicates that the local
+     clock is FAST of the remote source. */
+  double theta;
+
+  /* The estimated round delay in seconds */
+  double delta;
+
+  /* The estimated peer dispersion in seconds */
+  double epsilon;
+
+  /* The estimated peer distance in seconds */
+  double peer_distance;
+
+  /* The total root delay */
+  double root_delay;
+
+  /* The total root dispersion */
+  double root_dispersion;
+
+  /* The skew relative to the remote source */
+  double skew;
+
+  /* The estimated skew relative to the remote source. */
+  double source_freq_lo, source_freq_hi;
+
+  /* These are the timeval equivalents of the remote epochs */  
+  struct timeval remote_receive_tv, remote_transmit_tv;
+  struct timeval remote_reference_tv;
+  struct timeval local_average, remote_average;
+  double local_interval, remote_interval;
+
+  int test1, test2, test3, test4, test5, test6, test7, test8;
+
+  int test4a, test4b;
+
+  /* In the words of section 3.4.4 of RFC1305, valid_data means
+     that the NTP protocol association with the peer/server is
+     properly synchronised.  valid_header means that the measurement
+     obtained from the packet is suitable for use in synchronising
+     our local clock.  Wierd choice of terminology. */
+
+  int valid_data;
+  int valid_header;
+
+  /* Variables used for doing logging */
+  static char sync_stats[4] = {'N', '-', '+', '?'};
+
+  /* The estimated offset predicted from previous samples.  The
+     convention here is that positive means local clock FAST of
+     reference, i.e. backwards to the way that 'theta' is defined. */
+  double estimated_offset;
+
+  /* The absolute difference between the offset estimate and
+     measurement in seconds */
+  double error_in_estimate;
+  int poll_to_use;
+  double delay_time = 0;
+  int requeue_transmit = 0;
+  int delta_score;
+
+  /* ==================== */
+
+  /* Save local receive timestamp */
+  inst->local_rx = *now;
+
+  pkt_leap = (message->lvm >> 6) & 0x3;
+  if (pkt_leap == 0x3) {
+    source_is_synchronized = 0;
+  } else {
+    source_is_synchronized = 1;
+  }
+
+  pkt_root_delay = int32_to_double(message->root_delay);
+  pkt_root_dispersion = int32_to_double(message->root_dispersion);
+
+  /* Perform packet validity tests */
+  
+  /* Test 1 requires that pkt.xmt != peer.org.  This protects
+     against receiving a duplicate packet */
+
+  if ((message->transmit_ts.hi == inst->remote_orig.hi) &&
+      (message->transmit_ts.lo == inst->remote_orig.lo)) {
+    test1 = 0; /* Failed */
+  } else {
+    test1 = 1; /* Success */
+  }
+
+  /* Test 2 requires pkt.org == peer.xmt.  This ensures the source
+     is responding to the latest packet we sent to it. */
+  if ((message->originate_ts.hi != inst->local_ntp_tx.hi) || 
+      (message->originate_ts.lo != inst->local_ntp_tx.lo)) {
+    test2 = 0; /* Failed */
+  } else {
+    test2 = 1; /* Success */
+  }
+
+  /* Regardless of any validity checks we apply, we are required to
+     save these two fields from the packet into the ntp source
+     instance record.  See RFC1305 section 3.4.4, peer.org <- pkt.xmt
+     & peer.peerpoll <- pkt.poll.  Note we can't do this assignment
+     before test1 has been carried out!! */
+
+  inst->remote_orig = message->transmit_ts;
+  inst->remote_poll = message->poll;
+
+  /* Test 3 requires that pkt.org != 0 and pkt.rec != 0.  If
+     either of these are true it means the association is not properly
+     'up'. */
+  
+  if (((message->originate_ts.hi == 0) && (message->originate_ts.lo == 0)) ||
+      ((message->receive_ts.hi == 0) && (message->receive_ts.lo == 0))) {
+    test3 = 0; /* Failed */
+  } else {
+    test3 = 1; /* Success */
+  }
+
+  SRC_GetFrequencyRange(inst->source, &source_freq_lo, &source_freq_hi);
+
+  UTI_Int64ToTimeval(&message->receive_ts, &remote_receive_tv);
+  UTI_Int64ToTimeval(&message->transmit_ts, &remote_transmit_tv);
+
+  if (test3) {
+    
+    UTI_AverageDiffTimevals(&remote_receive_tv, &remote_transmit_tv,
+                            &remote_average, &remote_interval);
+
+    UTI_AverageDiffTimevals(&inst->local_tx, now,
+                            &local_average, &local_interval);
+
+    /* In our case, we work out 'delta' as the worst case delay,
+       assuming worst case frequency error between us and the other
+       source */
+    
+    delta = local_interval - remote_interval / (1.0 - source_freq_lo);
+    
+    /* Calculate theta.  Following the NTP definition, this is negative
+       if we are fast of the remote source. */
+    
+    theta = (double) (remote_average.tv_sec - local_average.tv_sec) +
+      (double) (remote_average.tv_usec - local_average.tv_usec) * 1.0e-6;
+    
+    /* We treat the time of the sample as being midway through the local
+       measurement period.  An analysis assuming constant relative
+       frequency and zero network delay shows this is the only possible
+       choice to estimate the frequency difference correctly for every
+       sample pair. */
+    sample_time = local_average;
+    
+    /* Calculate skew */
+    skew = source_freq_hi - source_freq_lo;
+    
+    /* and then calculate peer dispersion */
+    epsilon = LCL_GetSysPrecisionAsQuantum() + skew * local_interval;
+    
+  } else {
+    /* If test3 failed, we probably can't calculate these quantities
+       properly (e.g. for the first sample received in a peering
+       connection). */
+    theta = delta = epsilon = 0.0;
+
+  }
+  
+  peer_distance = epsilon + 0.5 * fabs(delta);
+  
+  /* Test 4 requires that the round trip delay to the source and the
+     source (RFC1305 'peer') dispersion are less than a cutoff value */
+
+  if ((fabs(delta) >= NTP_MAX_DISPERSION) ||
+      (epsilon >= NTP_MAX_DISPERSION)) {
+    test4 = 0; /* Failed */
+  } else {
+    test4 = 1; /* Success */
+  }
+
+  /* Test 4a (additional to RFC1305) requires that the round trip
+     delay is less than an administrator-defined value */
+
+  if (fabs(delta) > inst->max_delay) {
+    test4a = 0; /* Failed */
+  } else {
+    test4a = 1; /* Success */
+  }
+
+  /* Test 4b (additional to RFC1305) requires that the ratio of the
+     round trip delay to the minimum one currently in the stats data
+     register is less than an administrator-defined value */
+
+  if (fabs(delta/SRC_MinRoundTripDelay(inst->source)) > inst->max_delay_ratio) {
+    test4b = 0; /* Failed */
+  } else {
+    test4b = 1; /* Success */
+  }
+
+  /* Test 5 relates to authentication. */
+  if (inst->do_auth) {
+    if (do_auth) {
+      auth_key_id = ntohl(message->auth_keyid);
+      if (!KEY_KeyKnown(auth_key_id)) {
+        test5 = 0;
+      } else {
+        test5 = check_packet_auth(message, auth_key_id);
+      }
+    } else {
+      /* If we expect authenticated info from this peer/server and the packet
+         doesn't have it, it's got to fail */
+      test5 = 0;
+    }
+  } else {
+    /* If the peer or server sends us an authenticated frame, but
+       we're not bothered about whether he authenticates or not, just
+       ignore the test. */
+    test5 = 1;
+  }
+
+  /* Test 6 checks that (i) the remote clock is synchronised (ii) the
+     transmit timestamp is not before the time it was synchronized (clearly
+     bogus if it is), and (iii) that it was not synchronised too long ago
+     */
+  UTI_Int64ToTimeval(&message->reference_ts, &remote_reference_tv);
+  if ((!source_is_synchronized) ||
+      (UTI_CompareTimevals(&remote_reference_tv, &remote_transmit_tv) == 1) ||
+      ((remote_reference_tv.tv_sec + NTP_MAXAGE - remote_transmit_tv.tv_sec) < 0)) {
+    test6 = 0; /* Failed */
+  } else {
+    test6 = 1; /* Succeeded */
+  }
+
+  /* Test 7 checks that the stratum in the packet is appropriate */
+  if ((message->stratum > REF_GetOurStratum()) ||
+      (message->stratum > NTP_MAX_STRATUM)) {
+    test7 = 0; /* Failed */
+  } else {
+    test7 = 1;
+  }
+
+  /* Test 8 checks that the root delay and dispersion quoted in 
+     the packet are appropriate */
+  if ((fabs(pkt_root_delay) >= NTP_MAX_DISPERSION) ||
+      (pkt_root_dispersion >= NTP_MAX_DISPERSION)) {
+    test8 = 0; /* Failed */
+  } else {
+    test8 = 1;
+  }
+
+  valid_data = test1 && test2 && test3 && test4 && test4a && test4b;
+  valid_header = test5 && test6 && test7 && test8;
+
+  root_delay = pkt_root_delay + delta;
+  root_dispersion = pkt_root_dispersion + epsilon;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_NtpCore, "lvm=%o stratum=%d poll=%d prec=%d",
+      message->lvm, message->stratum, message->poll, message->precision);
+  LOG(LOGS_INFO, LOGF_NtpCore, "Root delay=%08lx (%f), dispersion=%08lx (%f)",
+      message->root_delay, pkt_root_delay, message->root_dispersion, pkt_root_dispersion);
+  LOG(LOGS_INFO, LOGF_NtpCore, "Ref id=[%s], ref_time=%08lx.%08lx [%s]",
+      UTI_IPToDottedQuad(ntohl(message->reference_id)),
+      message->reference_ts.hi, message->reference_ts.lo,
+      UTI_TimestampToString(&message->reference_ts));
+  LOG(LOGS_INFO, LOGF_NtpCore, "Originate=%08lx.%08lx [%s]",
+      message->originate_ts.hi, message->originate_ts.lo,
+      UTI_TimestampToString(&message->originate_ts));
+  LOG(LOGS_INFO, LOGF_NtpCore, "Message receive=%08lx.%08lx [%s]",
+      message->receive_ts.hi, message->receive_ts.lo,
+      UTI_TimestampToString(&message->receive_ts));
+
+  LOG(LOGS_INFO, LOGF_NtpCore, "Transmit=%08lx.%08lx [%s]",
+      message->transmit_ts.hi, message->transmit_ts.lo,
+      UTI_TimestampToString(&message->transmit_ts));
+
+  LOG(LOGS_INFO, LOGF_NtpCore, "theta=%f delta=%f epsilon=%f root_delay=%f root_dispersion=%f",
+      theta, delta, epsilon, root_delay, root_dispersion);
+
+  LOG(LOGS_INFO, LOGF_NtpCore, "test1=%d test2=%d test3=%d test4=%d valid_data=%d",
+      test1, test2, test3, test4, valid_data);
+
+  LOG(LOGS_INFO, LOGF_NtpCore, "test5=%d test6=%d test7=%d test8=%d valid_header=%d",
+      test5, test6, test7, test8, valid_header);
+#endif
+
+  if (valid_header) {
+    inst->tx_count = 0;
+    SRC_SetReachable(inst->source);
+  }
+
+  /* Do this before we accumulate a new sample into the stats registers, obviously */
+  estimated_offset = SRC_PredictOffset(inst->source, &sample_time);
+
+  if (valid_data) {
+    SRC_AccumulateSample(inst->source,
+                         &sample_time,
+                         theta, delta, epsilon,
+                         root_delay, root_dispersion,
+                         message->stratum, (NTP_Leap) pkt_leap);
+  }
+
+
+  /* Only do performance monitoring if we got valid data! */
+
+  if (valid_data) {
+    
+    /* Now examine the registers.  First though, if the prediction is
+       not even within +/- the peer distance of the peer, we are clearly
+       not tracking the peer at all well, so we back off the sampling
+       rate depending on just how bad the situation is. */
+    error_in_estimate = fabs(-theta - estimated_offset);
+    /* Now update the polling interval */
+
+    if (error_in_estimate > peer_distance) {
+      int shift = 0;
+      unsigned long temp = (int)(error_in_estimate / peer_distance);
+      do {
+        shift++;
+        temp>>=1;
+      } while (temp);
+      
+      inst->local_poll -= shift;
+      inst->score = 0;
+      
+    } else {
+
+      switch (SRC_LastSkewChange(inst->source)) {
+        case SRC_Skew_Decrease:
+          delta_score = 1;
+          break;
+        case SRC_Skew_Nochange:
+          delta_score = 0;
+          break;
+        case SRC_Skew_Increase:
+          delta_score = -2;
+          break;
+        default: /* Should not happen! */
+          delta_score = 0;
+          break;
+      }      
+
+      inst->score += delta_score;
+    }
+
+    normalise_score(inst);
+
+  }
+
+  /* If we're in burst mode, check whether the burst is completed and
+     revert to the previous mode */
+
+  switch (inst->opmode) {
+    case MD_BURST_WAS_ONLINE:
+      if (valid_data) {
+        --inst->burst_good_samples_to_go;
+      }
+
+      if (inst->burst_good_samples_to_go <= 0) {
+        inst->opmode = MD_ONLINE;
+      }
+      break;
+
+    case MD_BURST_WAS_OFFLINE:
+      if (valid_data) {
+        --inst->burst_good_samples_to_go;
+      }
+
+      if (inst->burst_good_samples_to_go <= 0) {
+        inst->opmode = MD_OFFLINE;
+        if (inst->timer_running) {
+          SCH_RemoveTimeout(inst->timeout_id);
+        }
+        inst->timer_running = 0;
+      }
+      break;
+                     
+    default:
+      break;
+  }
+
+  /* And now, requeue the timer.
+
+     If we're in burst mode, queue for immediate dispatch.
+
+     If we're operating in client/server mode, queue the timeout for
+     the poll interval hence.  The fact that a timeout has been queued
+     in the transmit handler is immaterial - that is only done so that
+     we at least send something, if no reply is heard.
+
+     If we're in symmetric mode, we have to take account of the peer's
+     wishes, otherwise his sampling regime will fall to pieces.  If
+     we're in client/server mode, we don't care what poll interval the
+     server responded with last time. */
+
+  switch (inst->opmode) {
+    case MD_OFFLINE:
+      requeue_transmit = 0;
+      break; /* Even if we've received something, we don't want to
+                transmit back.  This might be a symmetric active peer
+                that is trying to talk to us. */
+
+    case MD_ONLINE:
+      /* Normal processing, depending on whether we're in
+         client/server or symmetric mode */
+
+      requeue_transmit = 1;
+
+      switch(inst->mode) {
+        case MODE_CLIENT:
+          /* Client/server association - aim at some randomised time
+             approx the poll interval away */
+          poll_to_use = inst->local_poll;
+          
+          delay_time = (double) (1UL<<poll_to_use);
+          
+          break;
+          
+        case MODE_ACTIVE:
+          /* Symmetric active association - aim at some randomised time approx
+             half the poll interval away, to interleave 50-50 with the peer */
+          
+          poll_to_use = (inst->local_poll < inst->remote_poll) ?  inst->local_poll : inst->remote_poll;
+          
+          /* Limit by min and max poll */
+          if (poll_to_use < inst->minpoll) poll_to_use = inst->minpoll;
+          if (poll_to_use > inst->maxpoll) poll_to_use = inst->maxpoll;
+          
+          delay_time = (double) (1UL<<(poll_to_use - 1));
+          
+          break;
+        default:
+          CROAK("Impossible");
+          break;
+      }
+      
+      break;
+
+    case MD_BURST_WAS_ONLINE:
+    case MD_BURST_WAS_OFFLINE:
+
+      requeue_transmit = 1;
+      delay_time = 0.0;
+      break;
+
+    default:
+      CROAK("Impossible");
+      break;
+
+  }
+
+
+  if (requeue_transmit) {
+    /* Get rid of old timeout and start a new one */
+    SCH_RemoveTimeout(inst->timeout_id);
+    inst->timeout_id = SCH_AddTimeoutInClass(delay_time, SAMPLING_SEPARATION,
+                                             SCH_NtpSamplingClass,
+                                             transmit_timeout, (void *)inst);
+  }
+
+  /* Do measurement logging */
+  if (logfile) {
+    if (((logwrites++) % 32) == 0) {
+      fprintf(logfile,
+              "=====================================================================================================================\n"
+              "   Date (UTC) Time     IP Address   L St 1234 ab 5678 LP RP SC  Offset     Peer del. Peer disp. Root del.  Root disp.\n"
+              "=====================================================================================================================\n");
+    }
+
+    fprintf(logfile, "%s %-15s %1c %2d %1d%1d%1d%1d %1d%1d %1d%1d%1d%1d %2d %2d %2d %10.3e %10.3e %10.3e %10.3e %10.3e\n",
+            UTI_TimeToLogForm(sample_time.tv_sec),
+            UTI_IPToDottedQuad(inst->remote_addr.ip_addr),
+            sync_stats[pkt_leap],
+            message->stratum,
+            test1, test2, test3, test4,
+            test4a, test4b,
+            test5, test6, test7, test8,
+            inst->local_poll, inst->remote_poll,
+            (inst->score),
+            theta, delta, epsilon,
+            pkt_root_delay, pkt_root_dispersion);
+    fflush(logfile);
+  }            
+
+
+  /* At this point we will have to do something about trimming the
+     poll interval for the source and requeueing the polling timeout.
+
+     Left until the source statistics management has been written */
+
+  return;
+}
+
+/* ================================================== */
+/* From RFC1305, the standard handling of receive packets, depending
+   on the mode of the packet and of the source, is :
+
+          Source mode>>>
+   Packet
+   mode       active  passive  client   server  bcast
+   vvv
+
+   active     recv    pkt      recv     xmit    xmit
+   passive    recv    error    recv     error   error
+   client     xmit    xmit     error    xmit    xmit
+   server     recv    error    recv     error   error
+   bcast      recv    error    recv     error   error
+
+   We ignore broadcasts in this implementation - they create too many
+   problems.
+
+ */
+
+/* ================================================== */
+
+static void
+process_known
+(NTP_Packet *message,           /* the received message */
+ struct timeval *now,           /* timestamp at time of receipt */
+ NCR_Instance inst,             /* the instance record for this peer/server */
+ int do_auth                   /* whether the received packet allegedly contains
+                                   authentication info*/
+ )
+{
+  int pkt_mode;
+  int version;
+  int valid_auth, valid_key;
+  int authenticate_reply;
+  unsigned long auth_key_id;
+  unsigned long reply_auth_key_id;
+
+  /* Check version */
+  version = (message->lvm >> 3) & 0x7;
+  if (version != NTP_VERSION) {
+    /* Ignore packet, but might want to log it */
+    return;
+  } 
+
+  /* Perform tests mentioned in RFC1305 to validate packet contents */
+  pkt_mode = (message->lvm >> 0) & 0x7;
+
+  /* Now, depending on the mode we decide what to do */
+  switch (pkt_mode) {
+    case MODE_CLIENT:
+      /* If message is client mode, we just respond with a server mode
+         packet, regardless of what we think the remote machine is
+         supposed to be.  However, even though this is a configured
+         peer or server, we still implement access restrictions on
+         client mode operation.
+
+         This is an extension to RFC1305, as we don't bother to check
+         whether we are a client of the remote machine.
+
+         This copes with the case for an isolated network where one
+         machine is set by eye and is used as the master, with the
+         other machines pointed at it.  If the master goes down, we
+         want to be able to reset its time at startup by relying on
+         one of the secondaries to flywheel it. The behaviour coded here
+         is required in the secondaries to make this possible. */
+
+      if (ADF_IsAllowed(access_auth_table, inst->remote_addr.ip_addr)) {
+
+        CLG_LogNTPClientAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec);
+
+        if (do_auth) {
+          auth_key_id = ntohl(message->auth_keyid);
+          valid_key = KEY_KeyKnown(auth_key_id);
+          if (valid_key) {
+            valid_auth = check_packet_auth(message, auth_key_id);
+          } else {
+            valid_auth = 0;
+          }
+          
+          if (valid_key && valid_auth) {
+            authenticate_reply = 1;
+            reply_auth_key_id = auth_key_id;
+          } else {
+            authenticate_reply = 0;
+            reply_auth_key_id = 0UL;
+          }
+        } else {
+          authenticate_reply = 0;
+          reply_auth_key_id = 0UL;
+        }
+        
+        transmit_packet(MODE_SERVER, inst->local_poll,
+                        authenticate_reply, reply_auth_key_id,
+                        &message->transmit_ts,
+                        now,
+                        &inst->local_tx,
+                        &inst->local_ntp_tx,
+                        &inst->remote_addr);
+
+      } else {
+        LOG(LOGS_WARN, LOGF_NtpCore, "NTP packet received from unauthorised host %s port %d",
+            UTI_IPToDottedQuad(inst->remote_addr.ip_addr),
+            inst->remote_addr.port);
+      }
+
+      break;
+
+    case MODE_ACTIVE:
+
+      switch(inst->mode) {
+        case MODE_ACTIVE:
+          /* Ordinary symmetric peering */
+          CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec);
+          receive_packet(message, now, inst, do_auth);
+          break;
+        case MODE_PASSIVE:
+          /* In this software this case should not arise, we don't
+             support unconfigured peers */
+          break;
+        case MODE_CLIENT:
+          /* This is where we have the remote configured as a server and he has
+             us configured as a peer - fair enough. */
+          CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec);
+          receive_packet(message, now, inst, do_auth);
+          break;
+        case MODE_SERVER:
+          /* Nonsense - we can't have a preconfigured server */
+          break;
+        case MODE_BROADCAST:
+          /* We don't handle broadcasts */
+          break;
+        default:
+          /* Obviously ignore */
+          break;
+      }
+
+      break;
+
+    case MODE_SERVER:
+
+      switch(inst->mode) {
+        case MODE_ACTIVE:
+          /* Slightly bizarre combination, but we can still process it */
+          CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec);
+          receive_packet(message, now, inst, do_auth);
+          break;
+        case MODE_PASSIVE:
+          /* We have no passive peers in this software */
+          break;
+        case MODE_CLIENT:
+          /* Standard case where he's a server and we're the client */
+          receive_packet(message, now, inst, do_auth);
+          break;
+        case MODE_SERVER:
+          /* RFC1305 error condition. */
+          break;
+        case MODE_BROADCAST:
+          /* RFC1305 error condition */
+          break;
+        default:
+          /* Obviously ignore */
+          break;
+      }
+      break;
+
+    case MODE_PASSIVE:
+
+      switch(inst->mode) {
+        case MODE_ACTIVE:
+          /* This would arise if we have the remote configured as a peer and
+             he does not have us configured */
+          CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec);
+          receive_packet(message, now, inst, do_auth);
+          break;
+        case MODE_PASSIVE:
+          /* Error condition in RFC1305.  Also, we can't have any
+             non-transient PASSIVE sources in this version, we only
+             allow configured peers! */
+          break;
+        case MODE_CLIENT:
+          /* This is a wierd combination - how could it arise? */
+          receive_packet(message, now, inst, do_auth);
+          break;
+        case MODE_SERVER:
+          /* Error condition in RFC1305 */
+          break;
+        case MODE_BROADCAST:
+          /* Error condition in RFC1305 */
+          break;
+        default:
+          /* Obviously ignore */
+          break;
+      }
+      break;
+
+    case MODE_BROADCAST:
+      /* Just ignore these, but might want to log them */
+      break;
+
+    default:
+      /* Obviously ignore */
+      break;
+      
+  }
+
+
+
+}
+
+/* ================================================== */
+/* This routine is called when a new packet arrives off the network,
+   and it relates to a source we have an ongoing protocol exchange with */
+
+void
+NCR_ProcessNoauthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance inst)
+{
+
+  process_known(message, now, inst, 0);
+
+}
+
+/* ================================================== */
+/* This routine is called when a new packet arrives off the network,
+   and we do not recognize its source */
+
+void
+NCR_ProcessNoauthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr)
+{
+
+  NTP_Mode his_mode;
+  NTP_Mode my_mode;
+  int my_poll;
+
+  if (ADF_IsAllowed(access_auth_table, remote_addr->ip_addr)) {
+
+    his_mode = message->lvm & 0x07;
+
+    if (his_mode == MODE_CLIENT) {
+      /* We are server */
+      my_mode = MODE_SERVER;
+      CLG_LogNTPClientAccess(remote_addr->ip_addr, (time_t) now->tv_sec);
+
+    } else if (his_mode == MODE_ACTIVE) {
+      /* We are symmetric passive, even though we don't ever lock to him */
+      my_mode = MODE_PASSIVE;
+      CLG_LogNTPPeerAccess(remote_addr->ip_addr, (time_t) now->tv_sec);
+
+    } else {
+      my_mode = MODE_UNDEFINED;
+    }
+    
+    /* If we can't determine a sensible mode to reply with, it means
+       he has supplied a wierd mode in his request, so ignore it. */
+    
+    if (my_mode != MODE_UNDEFINED) {
+      
+      my_poll = message->poll; /* What should this be set to?  Does the client actually care? */
+      
+      transmit_packet(my_mode, my_poll,
+                      0, 0UL,
+                      &message->transmit_ts, /* Originate (for us) is the transmit time for the client */
+                      now, /* Time we received the packet */
+                      NULL, /* Don't care when we send reply, we aren't maintaining state about this client */
+                      NULL, /* Ditto */
+                      remote_addr);
+      
+    }
+  } else {
+    LOG(LOGS_WARN, LOGF_NtpCore, "NTP packet received from unauthorised host %s port %d",
+        UTI_IPToDottedQuad(remote_addr->ip_addr),
+        remote_addr->port);
+  }
+
+  return;
+
+}
+
+/* ================================================== */
+/* This routine is called when a new authenticated packet arrives off
+   the network, and it relates to a source we have an ongoing protocol
+   exchange with */
+
+void
+NCR_ProcessAuthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance data)
+{
+  process_known(message, now, data, 1);
+
+}
+
+/* ================================================== */
+/* This routine is called when a new authenticated packet arrives off
+   the network, and we do not recognize its source */
+
+void
+NCR_ProcessAuthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr)
+{
+
+  NTP_Mode his_mode;
+  NTP_Mode my_mode;
+  int my_poll;
+  int valid_key, valid_auth;
+  unsigned long key_id;
+
+  if (ADF_IsAllowed(access_auth_table, remote_addr->ip_addr)) {
+
+    his_mode = message->lvm & 0x07;
+    
+    if (his_mode == MODE_CLIENT) {
+      /* We are server */
+      my_mode = MODE_SERVER;
+      CLG_LogNTPClientAccess(remote_addr->ip_addr, (time_t) now->tv_sec);
+
+    } else if (his_mode == MODE_ACTIVE) {
+      /* We are symmetric passive, even though we don't ever lock to him */
+      my_mode = MODE_PASSIVE;
+      CLG_LogNTPPeerAccess(remote_addr->ip_addr, (time_t) now->tv_sec);
+
+    } else {
+      my_mode = MODE_UNDEFINED;
+    }
+    
+    /* If we can't determine a sensible mode to reply with, it means
+       he has supplied a wierd mode in his request, so ignore it. */
+
+    if (my_mode != MODE_UNDEFINED) {
+
+      /* Only reply if we know the key and the packet authenticates
+         properly. */
+      key_id = ntohl(message->auth_keyid);
+      valid_key = KEY_KeyKnown(key_id);
+
+      if (valid_key) {
+        valid_auth = check_packet_auth(message, key_id);
+      } else {
+        valid_auth = 0;
+      }
+
+      if (valid_key && valid_auth) {
+        my_poll = message->poll; /* What should this be set to?  Does the client actually care? */
+
+        transmit_packet(my_mode, my_poll,
+                        1, key_id,
+                        &message->transmit_ts, /* Originate (for us) is the transmit time for the client */
+                        now, /* Time we received the packet */
+                        NULL, /* Don't care when we send reply, we aren't maintaining state about this client */
+                        NULL, /* Ditto */
+                        remote_addr);
+      }
+    }
+  }
+  return;
+
+
+}
+
+/* ================================================== */
+
+void
+NCR_SlewTimes(NCR_Instance inst, struct timeval *when, double dfreq, double doffset)
+{
+  struct timeval prev;
+  prev = inst->local_rx;
+  UTI_AdjustTimeval(&inst->local_rx, when, &inst->local_rx, dfreq, doffset);
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_NtpCore, "rx prev=[%s] new=[%s]",
+      UTI_TimevalToString(&prev), UTI_TimevalToString(&inst->local_rx));
+#endif
+  prev = inst->local_tx;
+  UTI_AdjustTimeval(&inst->local_tx, when, &inst->local_tx, dfreq, doffset);
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_NtpCore, "tx prev=[%s] new=[%s]",
+      UTI_TimevalToString(&prev), UTI_TimevalToString(&inst->local_tx));
+#endif
+}
+
+/* ================================================== */
+
+void
+NCR_TakeSourceOnline(NCR_Instance inst)
+{
+  switch (inst->opmode) {
+    case MD_ONLINE:
+      /* Nothing to do */
+      break;
+    case MD_OFFLINE:
+      if (!inst->timer_running) {
+        /* We are not already actively polling it */
+        LOG(LOGS_INFO, LOGF_NtpCore, "Source %s online", UTI_IPToDottedQuad(inst->remote_addr.ip_addr));
+        inst->local_poll = inst->minpoll;
+        inst->score = (ZONE_WIDTH >> 1);
+        inst->opmode = MD_ONLINE;
+        start_initial_timeout(inst);
+      }
+      break;
+    case MD_BURST_WAS_ONLINE:
+      /* Will revert */
+      break;
+    case MD_BURST_WAS_OFFLINE:
+      inst->opmode = MD_BURST_WAS_ONLINE;
+      break;
+  }
+}
+
+/* ================================================== */
+
+void
+NCR_TakeSourceOffline(NCR_Instance inst)
+{
+  switch (inst->opmode) {
+    case MD_ONLINE:
+      if (inst->timer_running) {
+        LOG(LOGS_INFO, LOGF_NtpCore, "Source %s offline", UTI_IPToDottedQuad(inst->remote_addr.ip_addr));
+        SCH_RemoveTimeout(inst->timeout_id);
+        inst->timer_running = 0;
+        inst->opmode = MD_OFFLINE;
+      }
+      break;
+    case MD_OFFLINE:
+      break;
+    case MD_BURST_WAS_ONLINE:
+      inst->opmode = MD_BURST_WAS_OFFLINE;
+      break;
+    case MD_BURST_WAS_OFFLINE:
+      break;
+  }
+
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMinpoll(NCR_Instance inst, int new_minpoll)
+{
+  inst->minpoll = new_minpoll;
+  LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new minpoll %d", UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_minpoll);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMaxpoll(NCR_Instance inst, int new_maxpoll)
+{
+  inst->maxpoll = new_maxpoll;
+  LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new maxpoll %d", UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_maxpoll);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMaxdelay(NCR_Instance inst, double new_max_delay)
+{
+  inst->max_delay = new_max_delay;
+  LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new max delay %f",
+      UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_max_delay);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMaxdelayratio(NCR_Instance inst, double new_max_delay_ratio)
+{
+  inst->max_delay_ratio = new_max_delay_ratio;
+  LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new max delay ratio %f",
+      UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_max_delay_ratio);
+}
+
+/* ================================================== */
+
+void
+NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples)
+{
+
+  if (inst->mode == MODE_CLIENT) {
+
+    /* We want to prevent burst mode being used on symmetric active
+       associations - it will play havoc with the peer's sampling
+       strategy. (This obviously relies on us having the peer
+       configured that way if he has us configured symmetric active -
+       but there's not much else we can do.) */
+
+    switch (inst->opmode) {
+      case MD_BURST_WAS_OFFLINE:
+      case MD_BURST_WAS_ONLINE:
+        /* If already burst sampling, don't start again */
+        break;
+
+      case MD_ONLINE:
+        inst->opmode = MD_BURST_WAS_ONLINE;
+        inst->burst_good_samples_to_go = n_good_samples;
+        inst->burst_total_samples_to_go = n_total_samples;
+        if (inst->timer_running) {
+          SCH_RemoveTimeout(inst->timeout_id);
+        }
+        inst->timer_running = 1;
+        inst->timeout_id = SCH_AddTimeoutInClass(0.0, SAMPLING_SEPARATION,
+                                                 SCH_NtpSamplingClass,
+                                                 transmit_timeout, (void *) inst);
+        break;
+
+      case MD_OFFLINE:
+        inst->opmode = MD_BURST_WAS_OFFLINE;
+        inst->burst_good_samples_to_go = n_good_samples;
+        inst->burst_total_samples_to_go = n_total_samples;
+        if (inst->timer_running) {
+          SCH_RemoveTimeout(inst->timeout_id);
+        }
+        inst->timer_running = 1;
+        inst->timeout_id = SCH_AddTimeoutInClass(0.0, SAMPLING_SEPARATION,
+                                                 SCH_NtpSamplingClass,
+                                                 transmit_timeout, (void *) inst);
+        break;
+
+
+      default:
+        CROAK("Impossible");
+        break;
+    }
+  }
+
+}
+
+/* ================================================== */
+
+void
+NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timeval *now)
+{
+  report->poll = inst->local_poll;
+
+  switch (inst->mode) {
+    case MODE_CLIENT:
+      report->mode = RPT_NTP_CLIENT;
+      break;
+    case MODE_ACTIVE:
+      report->mode = RPT_NTP_PEER;
+      break;
+    default:
+      CROAK("Impossible");
+  }
+  
+  return;
+}
+
+/* ================================================== */
+
+int
+NCR_AddAccessRestriction(unsigned long ip_addr, int subnet_bits, int allow, int all)
+ {
+  ADF_Status status;
+
+  if (allow) {
+    if (all) {
+      status = ADF_AllowAll(access_auth_table, ip_addr, subnet_bits);
+    } else {
+      status = ADF_Allow(access_auth_table, ip_addr, subnet_bits);
+    }
+  } else {
+    if (all) {
+      status = ADF_DenyAll(access_auth_table, ip_addr, subnet_bits);
+    } else {
+      status = ADF_Deny(access_auth_table, ip_addr, subnet_bits);
+    }
+  }
+
+  if (status == ADF_BADSUBNET) {
+    return 0;
+  } else if (status == ADF_SUCCESS) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+int
+NCR_CheckAccessRestriction(unsigned long ip_addr)
+{
+  return ADF_IsAllowed(access_auth_table, ip_addr);
+}
+
+/* ================================================== */
+
+void
+NCR_CycleLogFile(void)
+{
+  if (logfile && logfilename) {
+    fclose(logfile);
+    logfile = fopen(logfilename, "a");
+    if (!logfile) {
+      LOG(LOGS_WARN, LOGF_NtpCore, "Could not reopen logfile %s", logfilename);
+    }
+    logwrites = 0;
+  }
+}
+
+/* ================================================== */
+
+void
+NCR_IncrementActivityCounters(NCR_Instance inst, int *online, int *offline,
+                              int *burst_online, int *burst_offline)
+{
+  switch (inst->opmode) {
+    case MD_BURST_WAS_OFFLINE:
+      ++*burst_offline;
+      break;
+    case MD_BURST_WAS_ONLINE:
+      ++*burst_online;
+      break;
+    case MD_ONLINE:
+      ++*online;
+      break;
+    case MD_OFFLINE:
+      ++*offline;
+      break;
+    default:
+      CROAK("Impossible");
+      break;
+  }
+}
+
+/* ================================================== */
diff --git a/ntp_core.h b/ntp_core.h
new file mode 100644 (file)
index 0000000..84b21d9
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+  $Header: /cvs/src/chrony/ntp_core.h,v 1.16 2002/02/28 23:27:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for the main NTP protocol engine
+  */
+
+#ifndef GOT_NTP_CORE_H
+#define GOT_NTP_CORE_H
+
+#include "sysincl.h"
+
+#include "addressing.h"
+#include "srcparams.h"
+#include "ntp.h"
+#include "reports.h"
+
+/* This is a private data type used for storing the instance record for
+   each source that we are chiming with */
+typedef struct NCR_Instance_Record *NCR_Instance;
+
+/* Init and fini functions */
+extern void NCR_Initialise(void);
+extern void NCR_Finalise(void);
+
+/* Get a new instance for a server */
+extern NCR_Instance NCR_GetServerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params);
+
+/* Get a new instance for a peer */
+extern NCR_Instance NCR_GetPeerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params);
+
+/* Destroy an instance */
+extern void NCR_DestroyInstance(NCR_Instance instance);
+
+/* This routine is called when a new packet arrives off the network,
+   and it relates to a source we have an ongoing protocol exchange with */
+extern void NCR_ProcessNoauthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance data);
+
+/* This routine is called when a new packet arrives off the network,
+   and we do not recognize its source */
+extern void NCR_ProcessNoauthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr);
+
+/* This routine is called when a new authenticated packet arrives off
+   the network, and it relates to a source we have an ongoing protocol
+   exchange with */
+extern void NCR_ProcessAuthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance data);
+
+/* This routine is called when a new authenticated packet arrives off
+   the network, and we do not recognize its source */
+extern void NCR_ProcessAuthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr);
+
+/* Slew receive and transmit times in instance records */
+extern void NCR_SlewTimes(NCR_Instance inst, struct timeval *when, double dfreq, double doffset);
+
+/* Take a particular source online (i.e. start sampling it) */
+extern void NCR_TakeSourceOnline(NCR_Instance inst);
+
+/* Take a particular source offline (i.e. stop sampling it, without
+   marking it unreachable in the source selection stuff) */
+extern void NCR_TakeSourceOffline(NCR_Instance inst);
+
+extern void NCR_ModifyMinpoll(NCR_Instance inst, int new_minpoll);
+
+extern void NCR_ModifyMaxpoll(NCR_Instance inst, int new_maxpoll);
+
+extern void NCR_ModifyMaxdelay(NCR_Instance inst, double new_max_delay);
+
+extern void NCR_ModifyMaxdelayratio(NCR_Instance inst, double new_max_delay_ratio);
+
+extern void NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples);
+
+extern void NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timeval *now);
+
+extern int NCR_AddAccessRestriction(unsigned long ip_addr, int subnet_bits, int allow, int all);
+extern int NCR_CheckAccessRestriction(unsigned long ip_addr);
+
+extern void NCR_CycleLogFile(void);
+
+extern void NCR_IncrementActivityCounters(NCR_Instance inst, int *online, int *offline, 
+                                          int *burst_online, int *burst_offline);
+
+#endif /* GOT_NTP_CORE_H */
diff --git a/ntp_io.c b/ntp_io.c
new file mode 100644 (file)
index 0000000..91bee1d
--- /dev/null
+++ b/ntp_io.c
@@ -0,0 +1,297 @@
+/*
+  $Header: /cvs/src/chrony/ntp_io.c,v 1.23 2003/04/01 20:54:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This file deals with the IO aspects of reading and writing NTP packets
+  */
+
+#include "sysincl.h"
+
+#include "ntp_io.h"
+#include "ntp_core.h"
+#include "ntp_sources.h"
+#include "sched.h"
+#include "local.h"
+#include "logging.h"
+#include "conf.h"
+#include "util.h"
+
+#include <fcntl.h>
+
+/* The file descriptor for the socket */
+static int sock_fd;
+
+/* Flag indicating that we have been initialised */
+static int initialised=0;
+
+/* ================================================== */
+
+/* Forward prototypes */
+static void read_from_socket(void *anything);
+
+/* ================================================== */
+
+static void
+do_size_checks(void)
+{
+  /* Assertions to check the sizes of certain data types
+     and the positions of certain record fields */
+
+  /* Check that certain invariants are true */
+  assert(sizeof(NTP_int32) == 4);
+  assert(sizeof(NTP_int64) == 8);
+
+  /* Check offsets of all fields in the NTP packet format */
+  assert(offsetof(NTP_Packet, lvm)             ==  0);
+  assert(offsetof(NTP_Packet, stratum)         ==  1);
+  assert(offsetof(NTP_Packet, poll)            ==  2);
+  assert(offsetof(NTP_Packet, precision)       ==  3);
+  assert(offsetof(NTP_Packet, root_delay)      ==  4);
+  assert(offsetof(NTP_Packet, root_dispersion) ==  8);
+  assert(offsetof(NTP_Packet, reference_id)    == 12);
+  assert(offsetof(NTP_Packet, reference_ts)    == 16);
+  assert(offsetof(NTP_Packet, originate_ts)    == 24);
+  assert(offsetof(NTP_Packet, receive_ts)      == 32);
+  assert(offsetof(NTP_Packet, transmit_ts)     == 40);
+
+}
+
+/* ================================================== */
+
+void
+NIO_Initialise(void)
+{
+  struct sockaddr_in my_addr;
+  unsigned short port_number;
+  unsigned long bind_address;
+  int on_off = 1;
+  
+  assert(!initialised);
+  initialised = 1;
+
+  do_size_checks();
+
+  port_number = CNF_GetNTPPort();
+
+  /* Open Internet domain UDP socket for NTP message transmissions */
+
+#if 0
+  sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+#else
+  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+#endif 
+  if (sock_fd < 0) {
+    LOG_FATAL(LOGF_NtpIO, "Could not open socket : %s", strerror(errno));
+  }
+
+  /* Make the socket capable of re-using an old address */
+  if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on_off, sizeof(on_off)) < 0) {
+    LOG(LOGS_ERR, LOGF_NtpIO, "Could not set reuseaddr socket options");
+    /* Don't quit - we might survive anyway */
+  }
+  
+  /* Make the socket capable of sending broadcast pkts - needed for NTP broadcast mode */
+  if (setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (char *)&on_off, sizeof(on_off)) < 0) {
+    LOG(LOGS_ERR, LOGF_NtpIO, "Could not set broadcast socket options");
+    /* Don't quit - we might survive anyway */
+  }
+
+  /* Bind the port */
+  my_addr.sin_family = AF_INET;
+  my_addr.sin_port = htons(port_number);
+
+  CNF_GetBindAddress(&bind_address);
+
+  if (bind_address != 0UL) {
+    my_addr.sin_addr.s_addr = htonl(bind_address);
+  } else {
+    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+  }
+
+#if 0
+  LOG(LOGS_INFO, LOGF_NtpIO, "Initialising, socket fd=%d", sock_fd);
+#endif
+
+  if (bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
+    LOG_FATAL(LOGF_NtpIO, "Could not bind socket : %s", strerror(errno));
+  }
+
+  /* Register handler for read events on the socket */
+  SCH_AddInputFileHandler(sock_fd, read_from_socket, NULL);
+
+#if 0
+  if (fcntl(sock_fd, F_SETFL, O_NONBLOCK | O_NDELAY) < 0) {
+    LOG(LOGS_ERR, LOGF_NtpIO, "Could not make socket non-blocking");
+  }
+
+  if (ioctl(sock_fd, I_SETSIG, S_INPUT) < 0) {
+    LOG(LOGS_ERR, LOGF_NtpIO, "Could not enable signal");
+  }
+#endif
+
+  return;
+}
+
+/* ================================================== */
+
+void
+NIO_Finalise(void)
+{
+  if (sock_fd >= 0) {
+    SCH_RemoveInputFileHandler(sock_fd);
+    close(sock_fd);
+  }
+  sock_fd = -1;
+  initialised = 0;
+  return;
+}
+
+/* ================================================== */
+
+
+/* ================================================== */
+
+static void
+read_from_socket(void *anything)
+{
+  /* This should only be called when there is something
+     to read, otherwise it will block. */
+
+  int status;
+  ReceiveBuffer message;
+  int message_length;
+  struct sockaddr_in where_from;
+  int from_length;
+  unsigned int flags = 0;
+  struct timeval now;
+  NTP_Remote_Address remote_addr;
+  double local_clock_err;
+
+  assert(initialised);
+
+  from_length = sizeof(where_from);
+  message_length = sizeof(message);
+
+  LCL_ReadCookedTime(&now, &local_clock_err);
+  status = recvfrom(sock_fd, (char *)&message, message_length, flags,
+                    (struct sockaddr *)&where_from, &from_length);
+
+  /* Don't bother checking if read failed or why if it did.  More
+     likely than not, it will be connection refused, resulting from a
+     previous sendto() directing a datagram at a port that is not
+     listening (which appears to generate an ICMP response, and on
+     some architectures e.g. Linux this is translated into an error
+     reponse on a subsequent recvfrom). */
+
+  if (status > 0) {
+    remote_addr.ip_addr = ntohl(where_from.sin_addr.s_addr);
+    remote_addr.port = ntohs(where_from.sin_port);
+
+    if (status == NTP_NORMAL_PACKET_SIZE) {
+
+      NSR_ProcessReceive((NTP_Packet *) &message.ntp_pkt, &now, &remote_addr);
+
+    } else if (status == sizeof(NTP_Packet)) {
+
+      NSR_ProcessAuthenticatedReceive((NTP_Packet *) &message.ntp_pkt, &now, &remote_addr);
+
+    } else {
+
+      /* Just ignore the packet if it's not of a recognized length */
+
+    }
+  }
+  
+  return;
+}
+
+/* ================================================== */
+/* Send an unauthenticated packet to a given address */
+
+void
+NIO_SendNormalPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr)
+{
+  struct sockaddr_in remote;
+
+  assert(initialised);
+
+  remote.sin_family = AF_INET;
+  remote.sin_port = htons(remote_addr->port);
+  remote.sin_addr.s_addr = htonl(remote_addr->ip_addr);
+
+  if (sendto(sock_fd, (void *) packet, NTP_NORMAL_PACKET_SIZE, 0,
+             (struct sockaddr *) &remote, sizeof(remote)) < 0) {
+    LOG(LOGS_WARN, LOGF_NtpIO, "Could not send to :%s%d : %s",
+        UTI_IPToDottedQuad(remote_addr->ip_addr), remote_addr->port, strerror(errno));
+  }
+
+  return;
+}
+
+/* ================================================== */
+/* Send an authenticated packet to a given address */
+
+void
+NIO_SendAuthenticatedPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr)
+{
+  struct sockaddr_in remote;
+
+  assert(initialised);
+
+  remote.sin_family = AF_INET;
+  remote.sin_port = htons(remote_addr->port);
+  remote.sin_addr.s_addr = htonl(remote_addr->ip_addr);
+
+  if (sendto(sock_fd, (void *) packet, sizeof(NTP_Packet), 0,
+             (struct sockaddr *) &remote, sizeof(remote)) < 0) {
+    LOG(LOGS_WARN, LOGF_NtpIO, "Could not send to :%s%d : %s",
+        UTI_IPToDottedQuad(remote_addr->ip_addr), remote_addr->port, strerror(errno));
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+/* We ought to use getservbyname, but I can't really see this changing */
+#define ECHO_PORT 7
+
+void
+NIO_SendEcho(NTP_Remote_Address *remote_addr)
+{
+  unsigned long magic_message = 0xbe7ab1e7UL;
+  struct sockaddr_in addr;
+
+  addr.sin_family = AF_INET;
+  addr.sin_port = htons(ECHO_PORT);
+  addr.sin_addr.s_addr = htonl(remote_addr->ip_addr);
+
+  /* Just ignore error status on send - this is not a big deal anyway */
+  sendto(sock_fd, (void *) &magic_message, sizeof(unsigned long), 0,
+         (struct sockaddr *) &addr, sizeof(addr));
+
+
+}
diff --git a/ntp_io.h b/ntp_io.h
new file mode 100644 (file)
index 0000000..b7a763f
--- /dev/null
+++ b/ntp_io.h
@@ -0,0 +1,53 @@
+/*
+  $Header: /cvs/src/chrony/ntp_io.h,v 1.9 2002/02/28 23:27:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This is the header file for the NTP socket I/O bits.
+
+  */
+
+#ifndef GOT_NTP_IO_H
+#define GOT_NTP_IO_H
+
+#include "ntp.h"
+#include "addressing.h"
+
+/* Function to initialise the module. */
+extern void NIO_Initialise(void);
+
+/* Function to finalise the module */
+extern void NIO_Finalise(void);
+
+/* Function to transmit a packet */
+extern void NIO_SendNormalPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr);
+
+/* Function to transmit an authenticated packet */
+extern void NIO_SendAuthenticatedPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr);
+
+/* Function to send a datagram to a remote machine's UDP echo port. */
+extern void NIO_SendEcho(NTP_Remote_Address *remote_addr);
+
+#endif /* GOT_NTP_IO_H */
diff --git a/ntp_sources.c b/ntp_sources.c
new file mode 100644 (file)
index 0000000..ab3afd1
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+  $Header: /cvs/src/chrony/ntp_sources.c,v 1.17 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Functions which manage the pool of NTP sources that we are currently
+  a client of or peering with.
+
+  */
+
+#include "sysincl.h"
+
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "util.h"
+#include "logging.h"
+#include "local.h"
+
+/* ================================================== */
+
+/* Record type private to this file, used to store information about
+   particular sources */
+typedef struct {
+  NTP_Remote_Address remote_addr;       /* The address of this source */
+  int in_use;                   /* Whether this slot in the table is in use */
+  NCR_Instance data;            /* Data for the protocol engine for this source */
+} SourceRecord;
+
+#define N_RECORDS 256
+
+/* Fixed size table, because we use a hard coded hash algorithm.  It
+   is rather unlikely we would have anything approaching this number
+   of sources. */
+static SourceRecord records[N_RECORDS];
+
+static int n_sources;
+
+/* The largest number of sources we want to have stored in the hash table */
+#define MAX_SOURCES 64
+
+/* ================================================== */
+/* Forward prototypes */
+static void
+slew_sources(struct timeval *raw,
+             struct timeval *cooked,
+             double dfreq,
+             double afreq,
+             double doffset,
+             int is_step_change,
+             void *anything);
+
+/* ================================================== */
+
+/* Flag indicating whether module is initialised */
+static int initialised = 0;
+
+/* ================================================== */
+
+void
+NSR_Initialise(void)
+{
+  int i;
+  for (i=0; i<N_RECORDS; i++) {
+    records[i].in_use = 0;
+  }
+  n_sources = 0;
+  initialised = 1;
+
+  LCL_AddParameterChangeHandler(slew_sources, NULL);
+
+  return;
+}
+
+/* ================================================== */
+
+void
+NSR_Finalise(void)
+{
+  initialised = 0;
+  return; /* Nothing to do yet */
+}
+
+/* ================================================== */
+/* Return slot number and whether the IP address was matched or not.
+   found = 0 => Neither IP nor port matched, empty slot returned
+   found = 1 => Only IP matched, port doesn't match
+   found = 2 => Both IP and port matched.
+
+   It is assumed that there can only ever be one record for a
+   particular IP address.  (If a different port comes up, it probably
+   means someone is running ntpdate -d or something).  Thus, if we
+   match the IP address we stop the search regardless of whether the
+   port number matches.
+
+  */
+
+static void
+find_slot(NTP_Remote_Address *remote_addr, int *slot, int *found)
+{
+  unsigned long hash;
+  unsigned long ip = remote_addr->ip_addr;
+  unsigned short port = remote_addr->port;
+
+  assert(N_RECORDS == 256);
+  
+  /* Compute hash value just by xor'ing the 4 bytes of the address together */
+  hash = ip ^ (ip >> 16);
+  hash = (hash ^ (hash >> 8)) & 0xff;
+
+  while ((records[hash].in_use) &&
+         (records[hash].remote_addr.ip_addr != ip)) {
+    hash++;
+    if (hash == 256) hash = 0;
+  }
+
+  if (records[hash].in_use) {
+    if (records[hash].remote_addr.port == port) {
+      *found = 2;
+    } else {
+      *found = 1;
+    }
+    *slot = hash;
+  } else {
+    *found = 0;
+    *slot = hash;
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+/* Procedure to add a new server source (to which this machine will be
+   a client) */
+NSR_Status
+NSR_AddServer(NTP_Remote_Address *remote_addr, SourceParameters *params)
+{
+  int slot, found;
+
+  assert(initialised);
+
+#if 0
+  LOG(LOGS_INFO, LOGF_NtpSources, "IP=%08lx port=%d", (unsigned long)remote_addr->ip_addr, remote_addr->port);
+#endif
+
+  /* Find empty bin & check that we don't have the address already */
+  find_slot(remote_addr, &slot, &found);
+  if (found) {
+    return NSR_AlreadyInUse;
+  } else {
+    if (n_sources == MAX_SOURCES) {
+      return NSR_TooManySources;
+    } else {
+      n_sources++;
+      records[slot].remote_addr = *remote_addr;
+      records[slot].in_use = 1;
+      records[slot].data = NCR_GetServerInstance(remote_addr, params); /* Will need params passing through */
+      return NSR_Success;
+    }
+  }
+}
+
+/* ================================================== */
+
+/* Procedure to add a new peer. */
+NSR_Status
+NSR_AddPeer(NTP_Remote_Address *remote_addr, SourceParameters *params)
+{
+  int slot, found;
+
+  assert(initialised);
+
+#if 0
+  LOG(LOGS_INFO, LOGF_NtpSources, "IP=%08lx port=%d", (unsigned long) remote_addr->ip_addr, remote_addr->port);
+#endif
+
+  /* Find empty bin & check that we don't have the address already */
+  find_slot(remote_addr, &slot, &found);
+  if (found) {
+    return NSR_AlreadyInUse;
+  } else {
+    if (n_sources == MAX_SOURCES) {
+      return NSR_TooManySources;
+    } else {
+      n_sources++;
+      records[slot].remote_addr = *remote_addr;
+      records[slot].in_use = 1;
+      records[slot].data = NCR_GetPeerInstance(remote_addr, params); /* Will need params passing through */
+      return NSR_Success;
+    }
+  }
+}
+
+/* ================================================== */
+
+/* Procedure to remove a source.  We don't bother whether the port
+   address is matched - we're only interested in removing a record for
+   the right IP address.  Thus the caller can specify the port number
+   as zero if it wishes. */
+NSR_Status
+NSR_RemoveSource(NTP_Remote_Address *remote_addr)
+{
+  int slot, found;
+
+  assert(initialised);
+
+  find_slot(remote_addr, &slot, &found);
+  if (!found) {
+    return NSR_NoSuchSource;
+  } else {
+    n_sources--;
+    records[slot].in_use = 0;
+    NCR_DestroyInstance(records[slot].data);
+    return NSR_Success;
+  }
+}
+
+/* ================================================== */
+
+/* This routine is called by ntp_io when a new packet arrives off the network.*/
+void
+NSR_ProcessReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr)
+{
+  int slot, found;
+
+  assert(initialised);
+
+#if 0
+  LOG(LOGS_INFO, LOGF_NtpSources, "from (%s,%d) at %s",
+      UTI_IPToDottedQuad(remote_addr->ip_addr),
+      remote_addr->port, UTI_TimevalToString(now));
+#endif
+  
+  find_slot(remote_addr, &slot, &found);
+  if (found == 2) { /* Must match IP address AND port number */
+    NCR_ProcessNoauthKnown(message, now, records[slot].data);
+  } else {
+    NCR_ProcessNoauthUnknown(message, now, remote_addr);
+  }
+}
+
+/* ================================================== */
+
+/* This routine is called by ntp_io when a new packet with an authentication tail arrives off the network */
+void
+NSR_ProcessAuthenticatedReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr)
+{
+  int slot, found;
+
+  assert(initialised);
+
+  find_slot(remote_addr, &slot, &found);
+  if (found == 2) {
+    NCR_ProcessAuthKnown(message, now, records[slot].data);
+  } else {
+    NCR_ProcessAuthUnknown(message, now, remote_addr);
+  }
+}
+
+/* ================================================== */
+
+static void
+slew_sources(struct timeval *raw,
+             struct timeval *cooked,
+             double dfreq,
+             double afreq,
+             double doffset,
+             int is_step_change,
+             void *anything)
+{
+  int i;
+
+  for (i=0; i<N_RECORDS; i++) {
+    if (records[i].in_use) {
+#if 0
+      LOG(LOGS_INFO, LOGF_Sources, "IP=%s dfreq=%f doff=%f",
+          UTI_IPToDottedQuad(records[i].remote_addr.ip_addr), dfreq, doffset);
+#endif
+
+      NCR_SlewTimes(records[i].data, cooked, dfreq, doffset);
+    }
+  }
+
+}
+
+/* ================================================== */
+
+int
+NSR_TakeSourcesOnline(unsigned long mask, unsigned long address)
+{
+  int i;
+  int any;
+  unsigned long ip;
+
+  any = 0;
+  for (i=0; i<N_RECORDS; i++) {
+    if (records[i].in_use) {
+      ip = records[i].remote_addr.ip_addr;
+      if ((ip & mask) == address) {
+        any = 1;
+        NCR_TakeSourceOnline(records[i].data);
+      }
+    }
+  }
+
+  return any;
+}
+
+/* ================================================== */
+
+int
+NSR_TakeSourcesOffline(unsigned long mask, unsigned long address)
+{
+  int i;
+  int any;
+  unsigned long ip;
+
+  any = 0;
+  for (i=0; i<N_RECORDS; i++) {
+    if (records[i].in_use) {
+      ip = records[i].remote_addr.ip_addr;
+      if ((ip & mask) == address) {
+        any = 1;
+        NCR_TakeSourceOffline(records[i].data);
+      }
+    }
+  }
+
+  return any;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMinpoll(unsigned long address, int new_minpoll)
+{
+  int slot, found;
+  NTP_Remote_Address addr;
+  addr.ip_addr = address;
+  addr.port = 0;
+
+  find_slot(&addr, &slot, &found);
+  if (found == 0) {
+    return 0;
+  } else {
+    NCR_ModifyMinpoll(records[slot].data, new_minpoll);
+    return 1;
+  }
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMaxpoll(unsigned long address, int new_maxpoll)
+{
+  int slot, found;
+  NTP_Remote_Address addr;
+  addr.ip_addr = address;
+  addr.port = 0;
+
+  find_slot(&addr, &slot, &found);
+  if (found == 0) {
+    return 0;
+  } else {
+    NCR_ModifyMaxpoll(records[slot].data, new_maxpoll);
+    return 1;
+  }
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMaxdelay(unsigned long address, double new_max_delay)
+{
+  int slot, found;
+  NTP_Remote_Address addr;
+  addr.ip_addr = address;
+  addr.port = 0;
+
+  find_slot(&addr, &slot, &found);
+  if (found == 0) {
+    return 0;
+  } else {
+    NCR_ModifyMaxdelay(records[slot].data, new_max_delay);
+    return 1;
+  }
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMaxdelayratio(unsigned long address, double new_max_delay_ratio)
+{
+  int slot, found;
+  NTP_Remote_Address addr;
+  addr.ip_addr = address;
+  addr.port = 0;
+
+  find_slot(&addr, &slot, &found);
+  if (found == 0) {
+    return 0;
+  } else {
+    NCR_ModifyMaxdelayratio(records[slot].data, new_max_delay_ratio);
+    return 1;
+  }
+}
+
+/* ================================================== */
+
+int
+NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples,
+                        unsigned long mask, unsigned long address)
+{
+  int i;
+  int any;
+  unsigned long ip;
+
+  any = 0;
+  for (i=0; i<N_RECORDS; i++) {
+    if (records[i].in_use) {
+      ip = records[i].remote_addr.ip_addr;
+      if ((ip & mask) == address) {
+        any = 1;
+        NCR_InitiateSampleBurst(records[i].data, n_good_samples, n_total_samples);
+      }
+    }
+  }
+
+  return any;
+
+}
+
+/* ================================================== */
+/* The ip address is assumed to be completed on input, that is how we
+   identify the source record. */
+
+void
+NSR_ReportSource(RPT_SourceReport *report, struct timeval *now)
+{
+  NTP_Remote_Address rem_addr;
+  int slot, found;
+
+  rem_addr.ip_addr = report->ip_addr;
+  rem_addr.port = 0;
+  find_slot(&rem_addr, &slot, &found);
+  if (found) {
+    NCR_ReportSource(records[slot].data, report, now);
+  } else {
+    report->poll = 0;
+    report->latest_meas_ago = 0;
+  }
+}
+
+/* ================================================== */
+
+void
+NSR_GetActivityReport(RPT_ActivityReport *report)
+{
+  int i;
+
+  report->online = 0;
+  report->offline = 0;
+  report->burst_online = 0;
+  report->burst_offline = 0;
+
+  for (i=0; i<N_RECORDS; i++) {
+    if (records[i].in_use) {
+      NCR_IncrementActivityCounters(records[i].data, &report->online, &report->offline,
+                                    &report->burst_online, &report->burst_offline);
+    }
+  }
+  return;
+}
+
+
+/* ================================================== */
+
diff --git a/ntp_sources.h b/ntp_sources.h
new file mode 100644 (file)
index 0000000..2e8499c
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+  $Header: /cvs/src/chrony/ntp_sources.h,v 1.12 2002/02/28 23:27:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header for the part of the software that deals with the set of
+  current NTP servers and peers, which can resolve an IP address into
+  a source record for further processing.
+
+  */
+
+#ifndef GOT_NTP_SOURCES_H
+#define GOT_NTP_SOURCES_H
+
+#include "ntp.h"
+#include "addressing.h"
+#include "srcparams.h"
+#include "ntp_core.h"
+#include "reports.h"
+
+/* Status values returned by operations that indirectly result from user
+   input. */
+typedef enum {
+  NSR_Success, /* Operation successful */
+  NSR_NoSuchSource, /* Remove - attempt to remove a source that is not known */
+  NSR_AlreadyInUse, /* AddServer, AddPeer - attempt to add a source that is already known */ 
+  NSR_TooManySources /* AddServer, AddPeer - too many sources already present */
+} NSR_Status;
+
+/* Procedure to add a new server source (to which this machine will be
+   a client) */
+extern NSR_Status NSR_AddServer(NTP_Remote_Address *remote_addr, SourceParameters *params);
+
+/* Procedure to add a new peer source.  We will use symmetric active
+   mode packets when communicating with this source */
+extern NSR_Status NSR_AddPeer(NTP_Remote_Address *remote_addr, SourceParameters *params);
+
+/* Procedure to remove a source */
+extern NSR_Status NSR_RemoveSource(NTP_Remote_Address *remote_addr);
+
+/* This routine is called by ntp_io when a new packet arrives off the network */
+extern void NSR_ProcessReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr);
+
+/* This routine is called by ntp_io when a new packet with an authentication tail arrives off the network */
+extern void NSR_ProcessAuthenticatedReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr);
+
+/* Initialisation function */
+extern void NSR_Initialise(void);
+
+/* Finalisation function */
+extern void NSR_Finalise(void);
+
+/* This routine is used to indicate that sources whose IP addresses
+   match a particular subnet should be set online again.  Returns a
+   flag indicating whether any hosts matched the address */
+extern int NSR_TakeSourcesOnline(unsigned long mask, unsigned long address);
+
+/* This routine is used to indicate that sources whose IP addresses
+   match a particular subnet should be set offline.  Returns a flag
+   indicating whether any hosts matched the address */
+extern int NSR_TakeSourcesOffline(unsigned long mask, unsigned long address);
+
+extern int NSR_ModifyMinpoll(unsigned long address, int new_minpoll);
+
+extern int NSR_ModifyMaxpoll(unsigned long address, int new_maxpoll);
+
+extern int NSR_ModifyMaxdelay(unsigned long address, double new_max_delay);
+
+extern int NSR_ModifyMaxdelayratio(unsigned long address, double new_max_delay_ratio);
+
+extern int NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples, unsigned long mask, unsigned long address);
+
+extern void NSR_ReportSource(RPT_SourceReport *report, struct timeval *now);
+
+extern void NSR_GetActivityReport(RPT_ActivityReport *report);
+
+#endif /* GOT_NTP_SOURCES_H */
diff --git a/pktlength.c b/pktlength.c
new file mode 100644 (file)
index 0000000..3f67347
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+  $Header: /cvs/src/chrony/pktlength.c,v 1.12 2002/02/28 23:27:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Routines to compute the expected length of a command or reply packet.
+  These operate on the RAW NETWORK packets, from the point of view of
+  integer endianness within the structures.
+
+  */
+#include "sysincl.h"
+
+#include "util.h"
+#include "pktlength.h"
+
+/* ================================================== */
+
+int
+PKL_CommandLength(CMD_Request *r)
+{
+  int type;
+  type = ntohs(r->command);
+  if (type < 0 || type >= N_REQUEST_TYPES) {
+    return 0;
+  } else {
+    switch (type) {
+      
+      case REQ_NULL:
+        return offsetof(CMD_Request, data);
+      case REQ_ONLINE:
+        return offsetof(CMD_Request, data.online.EOR);
+      case REQ_OFFLINE:
+        return offsetof(CMD_Request, data.offline.EOR);
+      case REQ_BURST:
+        return offsetof(CMD_Request, data.burst.EOR);
+      case REQ_MODIFY_MINPOLL:
+        return offsetof(CMD_Request, data.modify_minpoll.EOR);
+      case REQ_MODIFY_MAXPOLL:
+        return offsetof(CMD_Request, data.modify_maxpoll.EOR);
+      case REQ_DUMP:
+        return offsetof(CMD_Request, data.dump.EOR);
+      case REQ_MODIFY_MAXDELAY:
+        return offsetof(CMD_Request, data.modify_maxdelay.EOR);
+      case REQ_MODIFY_MAXDELAYRATIO:
+        return offsetof(CMD_Request, data.modify_maxdelayratio.EOR);
+      case REQ_MODIFY_MAXUPDATESKEW:
+        return offsetof(CMD_Request, data.modify_maxupdateskew.EOR);
+      case REQ_LOGON :
+        return offsetof(CMD_Request, data.logon.EOR);
+      case REQ_SETTIME :
+        return offsetof(CMD_Request, data.settime.EOR);
+      case REQ_LOCAL :
+        return offsetof(CMD_Request, data.local.EOR);
+      case REQ_MANUAL :
+        return offsetof(CMD_Request, data.manual.EOR);
+      case REQ_N_SOURCES :
+        return offsetof(CMD_Request, data.n_sources.EOR);
+      case REQ_SOURCE_DATA :
+        return offsetof(CMD_Request, data.source_data.EOR);
+      case REQ_REKEY :
+        return offsetof(CMD_Request, data.rekey.EOR);
+      case REQ_ALLOW :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_ALLOWALL :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_DENY :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_DENYALL :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_CMDALLOW :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_CMDALLOWALL :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_CMDDENY :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_CMDDENYALL :
+        return offsetof(CMD_Request, data.allow_deny.EOR);
+      case REQ_ACCHECK :
+        return offsetof(CMD_Request, data.ac_check.EOR);
+      case REQ_CMDACCHECK :
+        return offsetof(CMD_Request, data.ac_check.EOR);
+      case REQ_ADD_SERVER :
+        return offsetof(CMD_Request, data.ntp_source.EOR);
+      case REQ_ADD_PEER :
+        return offsetof(CMD_Request, data.ntp_source.EOR);
+      case REQ_DEL_SOURCE :
+        return offsetof(CMD_Request, data.del_source.EOR);
+      case REQ_WRITERTC :
+        return offsetof(CMD_Request, data.writertc.EOR);
+      case REQ_DFREQ :
+        return offsetof(CMD_Request, data.dfreq.EOR);
+      case REQ_DOFFSET :
+        return offsetof(CMD_Request, data.doffset.EOR);
+      case REQ_TRACKING :
+        return offsetof(CMD_Request, data.tracking.EOR);
+      case REQ_SOURCESTATS :
+        return offsetof(CMD_Request, data.sourcestats.EOR);
+      case REQ_RTCREPORT :
+        return offsetof(CMD_Request, data.rtcreport.EOR);
+      case REQ_TRIMRTC :
+        return offsetof(CMD_Request, data.trimrtc.EOR);
+      case REQ_CYCLELOGS :
+        return offsetof(CMD_Request, data.cyclelogs.EOR);
+      case REQ_SUBNETS_ACCESSED :
+        {
+          unsigned long ns;
+          ns = ntohl(r->data.subnets_accessed.n_subnets);
+          return (offsetof(CMD_Request, data.subnets_accessed.subnets) +
+                  ns * sizeof(REQ_SubnetsAccessed_Subnet));
+        }
+      case REQ_CLIENT_ACCESSES:
+        {
+          unsigned long nc;
+          nc = ntohl(r->data.client_accesses.n_clients);
+          return (offsetof(CMD_Request, data.client_accesses.client_ips) +
+                  nc * sizeof(unsigned long));
+        }
+      case REQ_CLIENT_ACCESSES_BY_INDEX:
+        return offsetof(CMD_Request, data.client_accesses_by_index.EOR);
+      case REQ_MANUAL_LIST:
+        return offsetof(CMD_Request, data.manual_list.EOR);
+      case REQ_MANUAL_DELETE:
+        return offsetof(CMD_Request, data.manual_delete.EOR);
+      case REQ_MAKESTEP:
+        return offsetof(CMD_Request, data.make_step.EOR);
+      case REQ_ACTIVITY:
+        return offsetof(CMD_Request, data.activity.EOR);
+      default:
+        /* If we fall through the switch, it most likely means we've forgotten to implement a new case */
+        assert(0);
+    }
+  }
+
+  /* Catch-all case */
+  return 0;
+
+}
+
+
+/* ================================================== */
+
+int
+PKL_ReplyLength(CMD_Reply *r)
+{
+  int type;
+  type = ntohs(r->reply);
+  /* Note that reply type codes start from 1, not 0 */
+  if (type < 1 || type >= N_REPLY_TYPES) {
+    return 0;
+  } else {
+    switch (type) {
+      case RPY_NULL:
+        return offsetof(CMD_Reply, data.null.EOR);
+      case RPY_N_SOURCES:
+        return offsetof(CMD_Reply, data.n_sources.EOR);
+      case RPY_SOURCE_DATA:
+        return offsetof(CMD_Reply, data.source_data.EOR);
+      case RPY_MANUAL_TIMESTAMP:
+        return offsetof(CMD_Reply, data.manual_timestamp.EOR);
+      case RPY_TRACKING:
+        return offsetof(CMD_Reply, data.tracking.EOR);
+      case RPY_SOURCESTATS:
+        return offsetof(CMD_Reply, data.sourcestats.EOR);
+      case RPY_RTC:
+        return offsetof(CMD_Reply, data.rtc.EOR);
+      case RPY_SUBNETS_ACCESSED :
+        {
+          unsigned long ns = ntohl(r->data.subnets_accessed.n_subnets);
+          if (r->status == htons(STT_SUCCESS)) {
+            return (offsetof(CMD_Reply, data.subnets_accessed.subnets) +
+                    ns * sizeof(RPY_SubnetsAccessed_Subnet));
+          } else {
+            return offsetof(CMD_Reply, data);
+          }
+        }
+      case RPY_CLIENT_ACCESSES:
+        {
+          unsigned long nc = ntohl(r->data.client_accesses.n_clients);
+          if (r->status == htons(STT_SUCCESS)) {
+            return (offsetof(CMD_Reply, data.client_accesses.clients) +
+                    nc * sizeof(RPY_ClientAccesses_Client));
+          } else {
+            return offsetof(CMD_Reply, data);
+          }
+        }
+      case RPY_CLIENT_ACCESSES_BY_INDEX:
+        {
+          unsigned long nc = ntohl(r->data.client_accesses_by_index.n_clients);
+          if (r->status == htons(STT_SUCCESS)) {
+            return (offsetof(CMD_Reply, data.client_accesses_by_index.clients) +
+                    nc * sizeof(RPY_ClientAccesses_Client));
+          } else {
+            return offsetof(CMD_Reply, data);
+          }
+        }
+      case RPY_MANUAL_LIST:
+        {
+          unsigned long ns = ntohl(r->data.manual_list.n_samples);
+          if (r->status == htons(STT_SUCCESS)) {
+            return (offsetof(CMD_Reply, data.manual_list.samples) +
+                    ns * sizeof(RPY_ManualListSample));
+          } else {
+            return offsetof(CMD_Reply, data);
+          }
+        }
+      case RPY_ACTIVITY:
+        return offsetof(CMD_Reply, data.activity.EOR);
+        
+      default:
+        assert(0);
+    }
+  }
+
+  return 0;
+}
+
+/* ================================================== */
+
diff --git a/pktlength.h b/pktlength.h
new file mode 100644 (file)
index 0000000..f8b53f6
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+  $Header: /cvs/src/chrony/pktlength.h,v 1.4 2002/02/28 23:27:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header for pktlength.c, routines for working out the expected length
+  of a network command/reply packet.
+
+  */
+
+#ifndef GOT_PKTLENGTH_H
+#define GOT_PKTLENGTH_H
+
+#include "candm.h"
+
+extern int PKL_CommandLength(CMD_Request *r);
+
+extern int PKL_ReplyLength(CMD_Reply *r);
+
+#endif /* GOT_PKTLENGTH_H */
diff --git a/reference.c b/reference.c
new file mode 100644 (file)
index 0000000..92f0550
--- /dev/null
@@ -0,0 +1,709 @@
+/*
+  $Header: /cvs/src/chrony/reference.c,v 1.40 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This module keeps track of the source which we are claiming to be
+  our reference, for the purposes of generating outgoing NTP packets */
+
+#include "sysincl.h"
+
+#include "memory.h"
+#include "reference.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "local.h"
+#include "mkdirpp.h"
+
+/* ================================================== */
+
+static int are_we_synchronised;
+static int enable_local_stratum;
+static int local_stratum;
+static NTP_Leap our_leap_status;
+static int our_stratum;
+static unsigned long our_ref_id;
+struct timeval our_ref_time; /* Stored relative to reference, NOT local time */
+static double our_offset;
+static double our_skew;
+static double our_residual_freq;
+static double our_root_delay;
+static double our_root_dispersion;
+
+static double max_update_skew;
+
+/* Flag indicating that we are initialised */
+static int initialised = 0;
+
+/* Flag and threshold for logging clock changes to syslog */
+static int do_log_change;
+static double log_change_threshold;
+
+/* Flag, threshold and user for sending mail notification on large clock changes */
+static int do_mail_change;
+static double mail_change_threshold;
+static char *mail_change_user;
+
+/* Filename of the drift file. */
+static char *drift_file=NULL;
+
+static void update_drift_file(double, double);
+
+#define MAIL_PROGRAM "/usr/lib/sendmail"
+
+/* ================================================== */
+/* File to which statistics are logged, NULL if none */
+static FILE *logfile = NULL;
+static char *logfilename = NULL;
+static unsigned long logwrites = 0;
+
+#define TRACKING_LOG "tracking.log"
+
+/* ================================================== */
+
+/* Day number of 1 Jan 1970 */
+#define MJD_1970 40587
+
+/* Reference ID supplied when we are locally referenced */
+#define LOCAL_REFERENCE_ID 0x7f7f0101UL
+
+/* ================================================== */
+
+void
+REF_Initialise(void)
+{
+  char *direc;
+  FILE *in;
+  char line[1024];
+  double file_freq_ppm, file_skew_ppm;
+  double our_frequency_ppm;
+
+  are_we_synchronised = 0;
+  our_leap_status = LEAP_Normal;
+  initialised = 1;
+  our_root_dispersion = 1.0;
+  our_root_delay = 1.0;
+  our_frequency_ppm = 0.0;
+  our_skew = 1.0; /* i.e. rather bad */
+  our_residual_freq = 0.0;
+
+  /* Now see if we can get the drift file opened */
+  drift_file = CNF_GetDriftFile();
+  if (drift_file) {
+    in = fopen(drift_file, "r");
+    if (in) {
+      if (fgets(line, sizeof(line), in)) {
+        if (sscanf(line, "%lf%lf", &file_freq_ppm, &file_skew_ppm) == 2) {
+          /* We have read valid data */
+          our_frequency_ppm = file_freq_ppm;
+          our_skew = 1.0e-6 * file_skew_ppm;
+        } else {
+          LOG(LOGS_WARN, LOGF_Reference, "Could not parse valid frequency and skew from driftfile %s",
+              drift_file);
+        }
+      } else {
+        LOG(LOGS_WARN, LOGF_Reference, "Could not read valid frequency and skew from driftfile %s",
+            drift_file);
+      }
+      fclose(in);
+    } else {
+      LOG(LOGS_WARN, LOGF_Reference, "Could not open driftfile %s for reading",
+          drift_file);
+    }
+
+    update_drift_file(our_frequency_ppm,our_skew);
+  }
+    
+  LCL_SetAbsoluteFrequency(our_frequency_ppm);
+
+  if (CNF_GetLogTracking()) {
+    direc = CNF_GetLogDir();
+    if (!mkdir_and_parents(direc)) {
+      LOG(LOGS_ERR, LOGF_Reference, "Could not create directory %s", direc);
+      logfile = NULL;
+    } else {
+      logfilename = MallocArray(char, 2 + strlen(direc) + strlen(TRACKING_LOG));
+      strcpy(logfilename, direc);
+      strcat(logfilename, "/");
+      strcat(logfilename, TRACKING_LOG);
+      logfile = fopen(logfilename, "a");
+      if (!logfile) {
+        LOG(LOGS_WARN, LOGF_Reference, "Couldn't open logfile %s for update", logfilename);
+      }
+    }
+  }
+
+  max_update_skew = fabs(CNF_GetMaxUpdateSkew()) * 1.0e-6;
+
+  enable_local_stratum = CNF_AllowLocalReference(&local_stratum);
+
+  CNF_GetLogChange(&do_log_change, &log_change_threshold);
+  CNF_GetMailOnChange(&do_mail_change, &mail_change_threshold, &mail_change_user);
+
+  /* And just to prevent anything wierd ... */
+  if (do_log_change) {
+    log_change_threshold = fabs(log_change_threshold);
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+void
+REF_Finalise(void)
+{
+  if (logfile) {
+    fclose(logfile);
+  }
+
+  initialised = 0;
+  return;
+}
+
+/* ================================================== */
+
+static double
+Sqr(double x)
+{
+  return x*x;
+}
+
+/* ================================================== */
+#if 0
+static double
+Cube(double x)
+{
+  return x*x*x;
+}
+#endif
+
+/* ================================================== */
+/* Update the drift coefficients to the file. */
+
+static void
+update_drift_file(double freq_ppm, double skew)
+{
+  struct stat buf;
+  char *temp_drift_file;
+  FILE *out;
+
+  /* Create a temporary file with a '.tmp' extension. */
+
+  temp_drift_file = (char*) Malloc(strlen(drift_file)+8);
+
+  if(!temp_drift_file) {
+    return;
+  }
+
+  strcpy(temp_drift_file,drift_file);
+  strcat(temp_drift_file,".tmp");
+
+  out = fopen(temp_drift_file, "w");
+  if (!out) {
+    Free(temp_drift_file);
+    LOG(LOGS_WARN, LOGF_Reference, "Could not open temporary driftfile %s.tmp for writing",
+        drift_file);
+    return;
+  }
+
+  /* Write the frequency and skew parameters in ppm */
+  fprintf(out, "%20.4f %20.4f\n", freq_ppm, 1.0e6 * skew);
+
+  fclose(out);
+
+  /* Clone the file attributes from the existing file if there is one. */
+
+  if (!stat(drift_file,&buf)) {
+    chown(temp_drift_file,buf.st_uid,buf.st_gid);
+    chmod(temp_drift_file,buf.st_mode&0777);
+  }
+
+  /* Rename the temporary file to the correct location (see rename(2) for details). */
+
+  if (rename(temp_drift_file,drift_file)) {
+    unlink(temp_drift_file);
+    Free(temp_drift_file);
+    LOG(LOGS_WARN, LOGF_Reference, "Could not replace old driftfile %s with new one %s.tmp (%d)",
+        drift_file,drift_file);
+    return;
+  }
+
+  Free(temp_drift_file);
+}
+
+/* ================================================== */
+
+#define BUFLEN 255
+#define S_MAX_USER_LEN "128"
+
+static void
+maybe_log_offset(double offset)
+{
+  double abs_offset;
+  FILE *p;
+  char buffer[BUFLEN], host[BUFLEN];
+  time_t now;
+  struct tm stm;
+
+  abs_offset = fabs(offset);
+
+  if (do_log_change &&
+      (abs_offset > log_change_threshold)) {
+    LOG(LOGS_WARN, LOGF_Reference,
+        "System clock wrong by %.6f seconds, adjustment started",
+        -offset);
+  }
+
+  if (do_mail_change &&
+      (abs_offset > mail_change_threshold)) {
+    sprintf(buffer, "%s %." S_MAX_USER_LEN "s", MAIL_PROGRAM, mail_change_user);
+    p = popen(buffer, "w");
+    if (p) {
+      if (gethostname(host, sizeof(host)) < 0) {
+        strcpy(host, "<UNKNOWN>");
+      }
+      fprintf(p, "Subject: chronyd reports change to system clock on node [%s]\n", host);
+      fputs("\n", p);
+      now = time(NULL);
+      stm = *localtime(&now);
+      strftime(buffer, sizeof(buffer), "On %A, %d %B %Y\n  with the system clock reading %H:%M:%S (%Z)", &stm);
+      fputs(buffer, p);
+      /* If offset < 0 the local clock is slow, so we are applying a
+         positive change to it to bring it into line, hence the
+         negation of 'offset' in the next statement (and earlier) */
+      fprintf(p,
+              "\n\nchronyd started to apply an adjustment of %.3f seconds to it,\n"
+              "  which exceeded the reporting threshold of %.3f seconds\n\n",
+              -offset, mail_change_threshold);
+      pclose(p);
+    } else {
+      LOG(LOGS_ERR, LOGF_Reference,
+          "Could not send mail notification to user %s\n",
+          mail_change_user);
+    }
+  }
+
+}
+
+/* ================================================== */
+
+
+void
+REF_SetReference(int stratum,
+                 NTP_Leap leap,
+                 unsigned long ref_id,
+                 struct timeval *ref_time,
+                 double offset,
+                 double frequency,
+                 double skew,
+                 double root_delay,
+                 double root_dispersion
+                 )
+{
+  double previous_skew, new_skew;
+  double previous_freq, new_freq;
+  double old_weight, new_weight, sum_weight;
+  double delta_freq1, delta_freq2;
+  double skew1, skew2;
+  double our_frequency;
+
+  double abs_freq_ppm;
+
+  assert(initialised);
+
+  /* If we get a serious rounding error in the source stats regression
+     processing, there is a remote chance that the skew argument is a
+     'not a number'.  If such a quantity gets propagated into the
+     machine's kernel clock variables, nasty things will happen ..
+     
+     To guard against this we need to check whether the skew argument
+     is a reasonable real number.  I don't think isnan, isinf etc are
+     platform independent, so the following algorithm is used. */
+
+  {
+    double t;
+    t = (skew + skew) / skew; /* Skew shouldn't be zero either */
+    if ((t < 1.9) || (t > 2.1)) {
+      LOG(LOGS_WARN, LOGF_Reference, "Bogus skew value encountered");
+      return;
+    }
+  }
+    
+
+  are_we_synchronised = 1;
+  our_stratum = stratum + 1;
+  our_leap_status = leap;
+  our_ref_id = ref_id;
+  our_ref_time = *ref_time;
+  our_offset = offset;
+  our_root_delay = root_delay;
+  our_root_dispersion = root_dispersion;
+
+  /* Eliminate updates that are based on totally unreliable frequency
+     information */
+
+  if (fabs(skew) < max_update_skew) { 
+
+    previous_skew = our_skew;
+    new_skew = skew;
+
+    previous_freq = 0.0; /* We assume that the local clock is running
+                          according to our previously determined
+                          value; note that this is a delta frequency
+                          --- absolute frequencies are only known in
+                          the local module. */
+    new_freq = frequency;
+
+    /* Set new frequency based on weighted average of old and new skew. */
+
+    old_weight = 1.0 / Sqr(previous_skew);
+    new_weight = 3.0 / Sqr(new_skew);
+
+    sum_weight = old_weight + new_weight;
+
+    our_frequency = (previous_freq * old_weight + new_freq * new_weight) / sum_weight;
+
+    delta_freq1 = previous_freq - our_frequency;
+    delta_freq2 = new_freq - our_frequency;
+
+    skew1 = sqrt((Sqr(delta_freq1) * old_weight + Sqr(delta_freq2) * new_weight) / sum_weight);
+    skew2 = (previous_skew * old_weight + new_skew * new_weight) / sum_weight;
+    our_skew = skew1 + skew2;
+
+    our_residual_freq = new_freq - our_frequency;
+
+    maybe_log_offset(our_offset);
+    LCL_AccumulateFrequencyAndOffset(our_frequency, our_offset);
+    
+  } else {
+
+#if 0    
+    LOG(LOGS_INFO, LOGF_Reference, "Skew %f too large to track, offset=%f", skew, our_offset);
+#endif
+    maybe_log_offset(our_offset);
+    LCL_AccumulateOffset(our_offset);
+
+    our_residual_freq = frequency;
+  }
+
+  abs_freq_ppm = LCL_ReadAbsoluteFrequency();
+
+  if (logfile) {
+
+    if (((logwrites++) % 32) == 0) {
+      fprintf(logfile,
+              "=======================================================================\n"
+              "   Date (UTC) Time     IP Address   St   Freq ppm   Skew ppm     Offset\n"
+              "=======================================================================\n");
+    }
+          
+    fprintf(logfile, "%s %-15s %2d %10.3f %10.3f %10.3e\n",
+            UTI_TimeToLogForm(ref_time->tv_sec),
+            UTI_IPToDottedQuad(our_ref_id),
+            our_stratum,
+            abs_freq_ppm,
+            1.0e6*our_skew,
+            our_offset);
+    
+    fflush(logfile);
+  }
+
+  if (drift_file) {
+    update_drift_file(abs_freq_ppm, our_skew);
+  }
+
+  /* And now set the freq and offset to zero */
+  our_frequency = 0.0;
+  our_offset = 0.0;
+  
+  return;
+}
+
+/* ================================================== */
+
+void
+REF_SetManualReference
+(
+ struct timeval *ref_time,
+ double offset,
+ double frequency,
+ double skew
+)
+{
+  int millisecond;
+  double abs_freq_ppm;
+
+  /* We are not synchronised to an external source, as such.  This is
+   only supposed to be used with the local source option, really
+   ... */
+  are_we_synchronised = 0;
+
+  our_skew = skew;
+  our_residual_freq = 0.0;
+
+  maybe_log_offset(offset);
+  LCL_AccumulateFrequencyAndOffset(frequency, offset);
+
+  abs_freq_ppm = LCL_ReadAbsoluteFrequency();
+
+  if (logfile) {
+    millisecond = ref_time->tv_usec / 1000;
+
+    fprintf(logfile, "%5s %-15s %2d %10.3f %10.3f %10.3e\n",
+            UTI_TimeToLogForm(ref_time->tv_sec),
+            "127.127.1.1",
+            our_stratum,
+            abs_freq_ppm,
+            1.0e6*our_skew,
+            our_offset);
+
+    fflush(logfile);
+  }
+
+  if (drift_file) {
+    update_drift_file(abs_freq_ppm, our_skew);
+  }
+}
+
+/* ================================================== */
+
+void
+REF_SetUnsynchronised(void)
+{
+  /* Variables required for logging to statistics log */
+  int millisecond;
+  struct timeval now;
+  double local_clock_err;
+
+  assert(initialised);
+
+  if (logfile) {
+    LCL_ReadCookedTime(&now, &local_clock_err);
+
+    millisecond = now.tv_usec / 1000;
+
+    fprintf(logfile, "%s %-15s  0 %10.3f %10.3f %10.3e\n",
+            UTI_TimeToLogForm(now.tv_sec),
+            "0.0.0.0",
+            LCL_ReadAbsoluteFrequency(),
+            1.0e6*our_skew,
+            0.0);
+    
+    fflush(logfile);
+  }
+
+  are_we_synchronised = 0;
+}
+
+/* ================================================== */
+
+void
+REF_GetReferenceParams
+(
+ struct timeval *local_time,
+ int *is_synchronised,
+ NTP_Leap *leap_status,
+ int *stratum,
+ unsigned long *ref_id,
+ struct timeval *ref_time,
+ double *root_delay,
+ double *root_dispersion
+)
+{
+  double elapsed;
+  double extra_dispersion;
+
+  assert(initialised);
+
+  if (are_we_synchronised) {
+
+    *is_synchronised = 1;
+
+    *stratum = our_stratum;
+
+    UTI_DiffTimevalsToDouble(&elapsed, local_time, &our_ref_time);
+    extra_dispersion = (our_skew + fabs(our_residual_freq)) * elapsed;
+
+    *leap_status = our_leap_status;
+    *ref_id = our_ref_id;
+    *ref_time = our_ref_time;
+    *root_delay = our_root_delay;
+    *root_dispersion = our_root_dispersion + extra_dispersion;
+
+  } else if (enable_local_stratum) {
+
+    *is_synchronised = 1;
+
+    *stratum = local_stratum;
+    *ref_id = LOCAL_REFERENCE_ID;
+    /* Make the reference time be now less a second - this will
+       scarcely affect the client, but will ensure that the transmit
+       timestamp cannot come before this (which would cause test 6 to
+       fail in the client's read routine) if the local system clock's
+       read routine is broken in any way. */
+    *ref_time = *local_time;
+    --ref_time->tv_sec;
+
+    /* Not much else we can do for leap second bits - maybe need to
+       have a way for the administrator to feed leap bits in */
+    *leap_status = LEAP_Normal;
+    
+    *root_delay = 0.0;
+    *root_dispersion = LCL_GetSysPrecisionAsQuantum();
+    
+  } else {
+
+    *is_synchronised = 0;
+
+    *leap_status = LEAP_Unsynchronised;
+    *stratum = 0;
+    *ref_id = 0UL;
+    ref_time->tv_sec = ref_time->tv_usec = 0;
+    /* These values seem to be standard for a client, and
+       any peer or client of ours will ignore them anyway because
+       we don't claim to be synchronised */
+    *root_dispersion = 1.0;
+    *root_delay = 1.0;
+
+  }
+}
+
+/* ================================================== */
+
+int
+REF_GetOurStratum(void)
+{
+  if (are_we_synchronised) {
+    return our_stratum;
+  } else if (enable_local_stratum) {
+    return local_stratum;
+  } else {
+    return 16;
+  }
+}
+
+/* ================================================== */
+
+void
+REF_ModifyMaxupdateskew(double new_max_update_skew)
+{
+  max_update_skew = new_max_update_skew * 1.0e-6;
+#if 0
+  LOG(LOGS_INFO, LOGF_Reference, "New max update skew = %.3fppm", new_max_update_skew);
+#endif
+}
+
+/* ================================================== */
+
+void
+REF_EnableLocal(int stratum)
+{
+  enable_local_stratum = 1;
+  local_stratum = stratum;
+}
+
+/* ================================================== */
+
+void
+REF_DisableLocal(void)
+{
+  enable_local_stratum = 0;
+}
+
+/* ================================================== */
+
+void
+REF_GetTrackingReport(RPT_TrackingReport *rep)
+{
+  double elapsed;
+  double extra_dispersion;
+  struct timeval now_raw, now_cooked;
+  double correction;
+
+  LCL_ReadRawTime(&now_raw);
+  correction = LCL_GetOffsetCorrection(&now_raw);
+  UTI_AddDoubleToTimeval(&now_raw, correction, &now_cooked);
+
+  if (are_we_synchronised) {
+    
+    UTI_DiffTimevalsToDouble(&elapsed, &now_cooked, &our_ref_time);
+    extra_dispersion = (our_skew + fabs(our_residual_freq)) * elapsed;
+    
+    rep->ref_id = our_ref_id;
+    rep->stratum = our_stratum;
+    rep->ref_time = our_ref_time;
+    UTI_DoubleToTimeval(correction, &rep->current_correction);
+    rep->freq_ppm = LCL_ReadAbsoluteFrequency();
+    rep->resid_freq_ppm = 1.0e6 * our_residual_freq;
+    rep->skew_ppm = 1.0e6 * our_skew;
+    rep->root_delay = our_root_delay;
+    rep->root_dispersion = our_root_dispersion + extra_dispersion;
+
+  } else if (enable_local_stratum) {
+
+    rep->ref_id = LOCAL_REFERENCE_ID;
+    rep->stratum = local_stratum;
+    rep->ref_time = now_cooked;
+    UTI_DoubleToTimeval(correction, &rep->current_correction);
+    rep->freq_ppm = LCL_ReadAbsoluteFrequency();
+    rep->resid_freq_ppm = 0.0;
+    rep->skew_ppm = 0.0;
+    rep->root_delay = 0.0;
+    rep->root_dispersion = LCL_GetSysPrecisionAsQuantum();
+
+  } else {
+
+    rep->ref_id = 0UL;
+    rep->stratum = 0;
+    rep->ref_time.tv_sec = 0;
+    rep->ref_time.tv_usec = 0;
+    UTI_DoubleToTimeval(correction, &rep->current_correction);
+    rep->freq_ppm = LCL_ReadAbsoluteFrequency();
+    rep->resid_freq_ppm = 0.0;
+    rep->skew_ppm = 0.0;
+    rep->root_delay = 0.0;
+    rep->root_dispersion = 0.0;
+  }
+
+}
+
+/* ================================================== */
+
+void
+REF_CycleLogFile(void)
+{
+  if (logfile && logfilename) {
+    fclose(logfile);
+    logfile = fopen(logfilename, "a");
+    if (!logfile) {
+      LOG(LOGS_WARN, LOGF_Reference, "Could not reopen logfile %s", logfilename);
+    }
+    logwrites = 0;
+  }
+}
+
+/* ================================================== */
diff --git a/reference.h b/reference.h
new file mode 100644 (file)
index 0000000..19b3f0e
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+  $Header: /cvs/src/chrony/reference.h,v 1.13 2002/02/28 23:27:12 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This is the header file for the module that keeps track of the current
+  reference.
+
+  */
+
+#ifndef GOT_REFERENCE_H
+#define GOT_REFERENCE_H
+
+#include "sysincl.h"
+
+#include "ntp.h"
+#include "reports.h"
+
+/* Init function */
+extern void REF_Initialise(void);
+
+/* Fini function */
+extern void REF_Finalise(void);
+
+/* Function which takes a local cooked time and returns the estimated
+   time of the reference.  It also returns the other parameters
+   required for forming the outgoing NTP packet.
+
+   local_time is the cooked local time returned by the LCL module
+
+   is_synchronised indicates whether we are synchronised to anything
+   at the moment.
+
+   leap indicates the current leap status
+
+   stratum is the stratum of this machine, when considered to be sync'd to the
+   reference
+   
+   ref_id is the reference_id of the source
+
+   ref_time is the time at which the we last set the reference source up
+
+   root_delay is the root delay of the sample we are using
+
+   root_dispersion is the root dispersion of the sample we are using, with all the
+   skew etc added on.
+
+   */
+
+extern void REF_GetReferenceParams
+(
+ struct timeval *local_time,
+ int *is_synchronised,
+ NTP_Leap *leap,
+ int *stratum,
+ unsigned long *ref_id,
+ struct timeval *ref_time,
+ double *root_delay,
+ double *root_dispersion
+);
+
+/* Function called by the clock selection process to register a new
+   reference source and its parameters
+
+   stratum is the stratum of the reference
+
+   leap is the leap status read from the source
+
+   ref_id is the reference id of the reference
+
+   ref_time is the time at which the parameters are assumed to be
+   correct, in terms of local time
+
+   frequency is the amount of local clock gain relative to the
+   reference per unit time interval of the local clock
+
+   skew is the maximum estimated frequency error (so we are within
+   [frequency+-skew])
+
+   root_delay is the root delay of the sample we are using
+
+   root_dispersion is the root dispersion of the sample we are using
+
+   */
+
+extern void REF_SetReference
+(
+ int stratum,
+ NTP_Leap leap,
+ unsigned long ref_id,
+ struct timeval *ref_time,
+ double offset,
+ double frequency,
+ double skew,
+ double root_delay,
+ double root_dispersion
+);
+
+extern void REF_SetManualReference
+(
+ struct timeval *ref_time,
+ double offset,
+ double frequency,
+ double skew
+);
+
+/* Mark the local clock as unsynchronised */
+extern void
+REF_SetUnsynchronised(void);
+
+/* Return the current stratum of this host or zero if the host is not
+   synchronised */
+extern int REF_GetOurStratum(void);
+
+/* Modify the setting for the maximum skew we are prepared to allow updates on (in ppm). */
+extern void REF_ModifyMaxupdateskew(double new_max_update_skew);
+
+extern void REF_EnableLocal(int stratum);
+extern void REF_DisableLocal(void);
+
+extern void REF_GetTrackingReport(RPT_TrackingReport *rep);
+
+extern void REF_CycleLogFile(void);
+
+#endif /* GOT_REFERENCE_H */
diff --git a/regress.c b/regress.c
new file mode 100644 (file)
index 0000000..4b6ab0f
--- /dev/null
+++ b/regress.c
@@ -0,0 +1,628 @@
+/*
+  $Header: /cvs/src/chrony/regress.c,v 1.31 2003/01/20 22:24:20 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Regression algorithms.
+
+  */
+
+#include <assert.h>
+#include <math.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "regress.h"
+#include "logging.h"
+#include "util.h"
+
+#define MAX_POINTS 128
+
+void
+RGR_WeightedRegression
+(double *x,                     /* independent variable */
+ double *y,                     /* measured data */
+ double *w,                     /* weightings (large => data
+                                   less reliable) */
+ int n,                         /* number of data points */
+
+ /* And now the results */
+
+ double *b0,                    /* estimated y axis intercept */
+ double *b1,                    /* estimated slope */
+ double *s2,                    /* estimated variance of data points */
+ double *sb0,                   /* estimated standard deviation of
+                                   intercept */
+ double *sb1                    /* estimated standard deviation of
+                                   slope */
+
+ /* Could add correlation stuff later if required */
+)
+{
+  double P, Q, U, V, W;
+  double diff;
+  double u, ui, aa;
+  int i;
+
+  if (n<3) {
+    CROAK("Insufficient points");
+  }
+
+  W = U = 0;
+  for (i=0; i<n; i++) {
+    U += x[i]        / w[i];
+    W += 1.0         / w[i];
+  }
+
+  u = U / W;
+
+  /* Calculate statistics from data */
+  P = Q = V = 0.0;
+  for (i=0; i<n; i++) {
+    ui = x[i] - u;
+    P += y[i]        / w[i];
+    Q += y[i] * ui   / w[i];
+    V += ui   * ui   / w[i];
+  }
+
+  *b1 = Q / V;
+  *b0 = (P / W) - (*b1) * u;
+
+  *s2 = 0.0;
+  for (i=0; i<n; i++) {
+    diff = y[i] - *b0 - *b1*x[i];
+    *s2 += diff*diff / w[i];
+  }
+
+  *s2 /= (double)(n-2);
+
+  *sb1 = sqrt(*s2 / V);
+  aa = u * (*sb1);
+  *sb0 = sqrt(*s2 / W + aa * aa);
+
+  *s2 *= (n / W); /* Giving weighted average of variances */
+
+  return;
+}
+
+/* ================================================== */
+/* Get the coefficient to multiply the standard deviation by, to get a
+   particular size of confidence interval (assuming a t-distribution) */
+  
+double
+RGR_GetTCoef(int dof)
+{
+  /* Assuming now the 99.95% quantile */
+  static double coefs[] =
+  { 636.6, 31.6, 12.92, 8.61, 6.869,
+    5.959, 5.408, 5.041, 4.781, 4.587,
+    4.437, 4.318, 4.221, 4.140, 4.073,
+    4.015, 3.965, 3.922, 3.883, 3.850,
+    3.819, 3.792, 3.768, 3.745, 3.725,
+    3.707, 3.690, 3.674, 3.659, 3.646,
+    3.633, 3.622, 3.611, 3.601, 3.591,
+    3.582, 3.574, 3.566, 3.558, 3.551};
+
+  if (dof <= 40) {
+    return coefs[dof-1];
+  } else {
+    return 3.5; /* Until I can be bothered to do something better */
+  }
+}
+
+/* ================================================== */
+/* Structure used for holding results of each regression */
+
+typedef struct {
+  double variance;
+  double slope_sd;
+  double slope;
+  double offset;
+  double offset_sd;
+  double K2; /* Variance / slope_var */
+  int n; /* Number of points */
+  int dof; /* Number of degrees of freedom */
+} RegressionResult;
+
+/* ================================================== */
+/* Critical value for number of runs of residuals with same sign.  10%
+   critical region for now */
+
+static int critical_runs10[] =
+{ 0,  0,  0,  0,  0,  0,  0,  0,  3,  3,
+  4,  4,  4,  4,  5,  5,  6,  6,  7,  7,
+  7,  8,  8,  9,  9, 10, 10, 10, 11, 11,
+ 12, 12, 13, 13, 14, 14, 14, 15, 15, 16,
+ 16, 17, 17, 18, 18, 18, 19, 19, 20, 20,
+
+  /* Note that 66 onwards are bogus - I haven't worked out the
+     critical values */
+ 21, 21, 22, 22, 23, 23, 23, 24, 24, 25,
+ 25, 26, 26, 27, 27, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28
+};
+
+/* ================================================== */
+
+static int
+n_runs_from_residuals(double *resid, int n)
+{
+  int nruns;
+  int i;
+  
+  nruns = 1;
+  for (i=1; i<n; i++) {
+    if (((resid[i-1] < 0.0) && (resid[i] < 0.0)) ||
+        ((resid[i-1] > 0.0) && (resid[i] > 0.0))) {
+      /* Nothing to do */
+    } else {
+      nruns++;
+    }
+  }
+  
+  return nruns;
+}
+
+/* ================================================== */
+/* Return a boolean indicating whether we had enough points for
+   regression */
+
+#define RESID_SIZE 1024
+#define MIN_SAMPLES_FOR_REGRESS 3
+
+int
+RGR_FindBestRegression 
+(double *x,                     /* independent variable */
+ double *y,                     /* measured data */
+ double *w,                     /* weightings (large => data
+                                   less reliable) */
+ int n,                         /* number of data points */
+
+ /* And now the results */
+
+ double *b0,                    /* estimated y axis intercept */
+ double *b1,                    /* estimated slope */
+ double *s2,                    /* estimated variance of data points */
+ double *sb0,                   /* estimated standard deviation of
+                                   intercept */
+ double *sb1,                   /* estimated standard deviation of
+                                   slope */
+
+ int *new_start,                /* the new starting index to make the
+                                   residuals pass the two tests */
+ int *n_runs,                   /* number of runs amongst the residuals */
+
+ int *dof                       /* degrees of freedom in statistics (needed
+                                   to get confidence intervals later) */
+
+)
+{
+  double P, Q, U, V, W; /* total */
+  double resid[RESID_SIZE];
+  double ss;
+  double a, b, u, ui, aa;
+
+  int start, nruns, npoints, npoints_left;
+  int i;
+
+  if (n < MIN_SAMPLES_FOR_REGRESS) {
+    return 0;
+  }
+
+  start = 0;
+  do {
+
+    W = U = 0;
+    for (i=start; i<n; i++) {
+      U += x[i]        / w[i];
+      W += 1.0         / w[i];
+    }
+
+    u = U / W;
+
+    P = Q = V = 0.0;
+    for (i=start; i<n; i++) {
+      ui = x[i] - u;
+      P += y[i]        / w[i];
+      Q += y[i] * ui   / w[i];
+      V += ui   * ui   / w[i];
+    }
+
+    npoints = n - start;
+    b = Q / V;
+    a = (P / W) - (b * u);
+
+    for (i=start; i<n; i++) {
+      resid[i] = y[i] - a - b*x[i];
+    }
+
+    /* Count number of runs */
+    nruns = n_runs_from_residuals(resid + start, npoints); 
+
+    npoints_left = n - start - 1;
+
+    if ((nruns > critical_runs10[npoints]) || (npoints_left < MIN_SAMPLES_FOR_REGRESS)) {
+      break;
+    } else {
+      /* Try dropping one sample at a time until the runs test passes. */
+      ++start;
+    }
+
+  } while (1);
+
+  /* Work out statistics from full dataset */
+  *b1 = b;
+  *b0 = a;
+
+  ss = 0.0;
+  for (i=start; i<n; i++) {
+    ss += resid[i]*resid[i] / w[i];
+  }
+
+  npoints = n - start;
+  ss /= (double)(npoints - 2);
+  *sb1 = sqrt(ss / V);
+  aa = u * (*sb1);
+  *sb0 = sqrt((ss / W) + (aa * aa));
+  *s2 = ss * (double) npoints / W;
+
+  *new_start = start;
+  *dof = npoints - 2;
+  *n_runs = nruns;
+
+  return 1;
+
+}
+
+/* ================================================== */
+
+#define EXCH(a,b) temp=(a); (a)=(b); (b)=temp
+
+/* ================================================== */
+/* Find the index'th biggest element in the array x of n elements.
+   flags is an array where a 1 indicates that the corresponding entry
+   in x is known to be sorted into its correct position and a 0
+   indicates that the corresponding entry is not sorted.  However, if
+   flags[m] = flags[n] = 1 with m<n, then x[m] must be <= x[n] and for
+   all i with m<i<n, x[m] <= x[i] <= x[n].  In practice, this means
+   flags[] has to be the result of a previous call to this routine
+   with the same array x, and is used to remember which parts of the
+   x[] array we have already sorted.
+
+   The approach used is a cut-down quicksort, where we only bother to
+   keep sorting the partition that contains the index we are after.
+   The approach comes from Numerical Recipes in C (ISBN
+   0-521-43108-5). */
+
+static double
+find_ordered_entry_with_flags(double *x, int n, int index, int *flags)
+{
+  int u, v, l, r;
+  double temp;
+  double piv;
+  int pivind;
+
+  if (index < 0) {
+    CROAK("Negative index");
+  }
+
+  /* If this bit of the array is already sorted, simple! */
+  if (flags[index]) {
+    return x[index];
+  }
+  
+  /* Find subrange to look at */
+  u = v = index;
+  while (u > 0 && !flags[u]) u--;
+  if (flags[u]) u++;
+
+  while (v < (n-1) && !flags[v]) v++;
+  if (flags[v]) v--;
+
+  do {
+    if (v - u < 2) {
+      if (x[v] < x[u]) {
+        EXCH(x[v], x[u]);
+      }
+      flags[v] = flags[u] = 1;
+      return x[index];
+    } else { 
+      pivind = (u + v) >> 1;
+      EXCH(x[u], x[pivind]);
+      piv = x[u]; /* New value */
+      l = u + 1;
+      r = v;
+      do {
+        while (x[l] < piv) l++;
+        while (x[r] > piv) r--;
+        if (r <= l) break;
+        EXCH(x[l], x[r]);
+        l++;
+        r--;
+      } while (1);
+      EXCH(x[u], x[r]);
+      flags[r] = 1; /* Pivot now in correct place */
+      if (index == r) {
+        return x[r];
+      } else if (index < r) {
+        v = r - 1;
+      } else if (index > r) {
+        u = l;
+      } else {
+        CROAK("Impossible");
+      }
+    }
+  } while (1);
+}
+
+/* ================================================== */
+
+#if 0
+/* Not used, but this is how it can be done */
+static double
+find_ordered_entry(double *x, int n, int index)
+{
+  int flags[MAX_POINTS];
+
+  bzero(flags, n * sizeof(int));
+  return find_ordered_entry_with_flags(x, n, index, flags);
+}
+#endif
+
+/* ================================================== */
+/* Find the median entry of an array x[] with n elements. */
+
+static double
+find_median(double *x, int n)
+{
+  int k;
+  int flags[MAX_POINTS];
+
+  memset(flags, 0, n*sizeof(int));
+  k = n>>1;
+  if (n&1) {
+    return find_ordered_entry_with_flags(x, n, k, flags);
+  } else {
+    return 0.5 * (find_ordered_entry_with_flags(x, n, k, flags) +
+                  find_ordered_entry_with_flags(x, n, k-1, flags));
+  }
+}
+
+/* ================================================== */
+/* This function evaluates the equation
+
+   \sum_{i=0}^{n-1} x_i sign(y_i - a - b x_i)
+
+   and chooses the value of a that minimises the absolute value of the
+   result.  (See pp703-704 of Numerical Recipes in C). */
+
+static void
+eval_robust_residual
+(double *x,                     /* The independent points */
+ double *y,                     /* The dependent points */
+ int n,                         /* Number of points */
+ double b,                      /* Slope */
+ double *aa,                    /* Intercept giving smallest absolute
+                                   value for the above equation */
+ double *rr                     /* Corresponding value of equation */
+)
+{
+  int i;
+  double a, res, del;
+  double d[MAX_POINTS];
+
+  for (i=0; i<n; i++) {
+    d[i] = y[i] - b * x[i];
+  }
+  
+  a = find_median(d, n);
+
+  res = 0.0;
+  for (i=0; i<n; i++) {
+    del = y[i] - a - b * x[i];
+    if (del > 0.0) {
+      res += x[i];
+    } else if (del < 0.0) {
+      res -= x[i];
+    }
+  }
+
+  *aa = a;
+  *rr = res;
+}
+
+/* ================================================== */
+/* This routine performs a 'robust' regression, i.e. one which has low
+   susceptibility to outliers amongst the data.  If one thinks of a
+   normal (least squares) linear regression in 2D being analogous to
+   the arithmetic mean in 1D, this algorithm in 2D is roughly
+   analogous to the median in 1D.  This algorithm seems to work quite
+   well until the number of outliers is approximately half the number
+   of data points.
+
+   The return value is a status indicating whether there were enough
+   data points to run the routine or not. */
+
+int
+RGR_FindBestRobustRegression
+(double *x,                     /* The independent axis points */
+ double *y,                     /* The dependent axis points (which
+                                   may contain outliers). */
+ int n,                         /* The number of points */
+ double tol,                    /* The tolerance required in
+                                   determining the value of b1 */
+ double *b0,                    /* The estimated Y-axis intercept */
+ double *b1,                    /* The estimated slope */
+ int *n_runs,                   /* The number of runs of residuals */
+ int *best_start                /* The best starting index */
+)
+{
+  int i;
+  int start;
+  int n_points;
+  double a, b;
+  double P, U, V, W, X;
+  double resid, resids[MAX_POINTS];
+  double blo, bhi, bmid, rlo, rhi, rmid;
+  double s2, sb, incr;
+  double mx, dx, my, dy;
+  int nruns = 0;
+
+  if (n < 2) {
+    return 0;
+  } else if (n == 2) {
+    /* Just a straight line fit (we need this for the manual mode) */
+    *b1 = (y[1] - y[0]) / (x[1] - x[0]);
+    *b0 = y[0] - (*b1) * x[0];
+    *n_runs = 0;
+    *best_start = 0;
+    return 1;
+  }
+
+  /* else at least 3 points, apply normal algorithm */
+
+  start = 0;
+
+  /* Loop to strip oldest points that cause the regression residuals
+     to fail the number of runs test */
+  do {
+
+    n_points = n - start;
+
+    /* Use standard least squares regression to get starting estimate */
+
+    P = U = 0.0;
+    for (i=start; i<n; i++) {
+      P += y[i];
+      U += x[i];
+    }
+
+    W = (double) n_points;
+
+    my = P/W;
+    mx = U/W;
+
+    X = V = 0.0;
+    for (i=start; i<n; i++) {
+      dy = y[i] - my;
+      dx = x[i] - mx;
+      X += dy * dx;
+      V += dx * dx;
+    }
+
+    b = X / V;
+    a = my - b*mx;
+
+
+#if 0
+    printf("my=%20.12f mx=%20.12f a=%20.12f b=%20.12f\n", my, mx, a, b);
+#endif
+
+    s2 = 0.0;
+    for (i=start; i<n; i++) {
+      resid = y[i] - a - b * x[i];
+      s2 += resid * resid;
+    }
+
+    /* Need to expand range of b to get a root in the interval.
+       Estimate standard deviation of b and expand range about b based
+       on that. */
+    sb = sqrt(s2 * W/V);
+    if (sb > 0.0) {
+      incr = 3.0 * sb;
+    } else {
+      incr = 3.0 * tol;
+    }
+  
+    blo = b;
+    bhi = b;
+
+    do {
+      blo -= incr;
+      bhi += incr;
+      
+      /* We don't want 'a' yet */
+      eval_robust_residual(x + start, y + start, n_points, blo, &a, &rlo);
+      eval_robust_residual(x + start, y + start, n_points, bhi, &a, &rhi);
+
+    } while (rlo * rhi > 0.0); /* fn vals have same sign, i.e. root not
+                                  in interval. */
+
+    /* OK, so the root for b lies in (blo, bhi). Start bisecting */
+    do {
+      bmid = 0.5 * (blo + bhi);
+      eval_robust_residual(x + start, y + start, n_points, bmid, &a, &rmid);
+      if (rmid == 0.0) {
+        break;
+      } else if (rmid * rlo > 0.0) {
+        blo = bmid;
+        rlo = rmid;
+      } else if (rmid * rhi > 0.0) {
+        bhi = bmid;
+        rhi = rmid;
+      } else {
+        CROAK("Impossible");
+      }
+    } while ((bhi - blo) > tol);
+
+    *b0 = a;
+    *b1 = bmid;
+
+    /* Number of runs test, but not if we're already down to the
+       minimum number of points */
+    if (n_points == MIN_SAMPLES_FOR_REGRESS) {
+      break;
+    }
+
+    for (i=start; i<n; i++) {
+      resids[i] = y[i] - a - bmid * x[i];
+    }
+
+    nruns = n_runs_from_residuals(resids + start, n_points);
+
+    if (nruns > critical_runs10[n_points]) {
+      break;
+    } else {
+      start++;
+    }
+
+  } while (1);
+
+  *n_runs = nruns;
+  *best_start = start;
+
+  return 1;
+
+}
+
+/* ================================================== */
diff --git a/regress.h b/regress.h
new file mode 100644 (file)
index 0000000..fb443ff
--- /dev/null
+++ b/regress.h
@@ -0,0 +1,108 @@
+/*
+  $Header: /cvs/src/chrony/regress.h,v 1.13 2002/02/28 23:27:13 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for regression routine(s)
+
+  */
+
+#ifndef GOT_REGRESS_H
+#define GOT_REGRESS_H
+
+extern void
+RGR_WeightedRegression
+(double *x,                     /* independent variable */
+ double *y,                     /* measured data */
+ double *w,                     /* weightings (large => data
+                                   less reliable) */
+ int n,                         /* number of data points */
+
+ /* And now the results */
+
+ double *b0,                    /* estimated y axis intercept */
+ double *b1,                    /* estimated slope */
+ double *s2,                    /* estimated variance (weighted) of
+                                   data points */
+ double *sb0,                   /* estimated standard deviation of
+                                   intercept */
+ double *sb1                    /* estimated standard deviation of
+                                   slope */
+
+ /* Could add correlation stuff later if required */
+);
+
+/* Return the weighting to apply to the standard deviation to get a
+   given size of confidence interval assuming a T distribution */
+
+extern double RGR_GetTCoef(int dof);
+
+/* Return a status indicating whether there were enough points to
+   carry out the regression */
+
+extern int
+RGR_FindBestRegression 
+(double *x,                     /* independent variable */
+ double *y,                     /* measured data */
+ double *w,                     /* weightings (large => data
+                                   less reliable) */
+ int n,                         /* number of data points */
+
+ /* And now the results */
+
+ double *b0,                    /* estimated y axis intercept */
+ double *b1,                    /* estimated slope */
+ double *s2,                    /* estimated variance of data points */
+ double *sb0,                   /* estimated standard deviation of
+                                   intercept */
+ double *sb1,                   /* estimated standard deviation of
+                                   slope */
+
+ int *new_start,                /* the new starting index to make the
+                                   residuals pass the two tests */
+
+ int *n_runs,                   /* number of runs amongst the residuals */
+
+ int *dof                       /* degrees of freedom in statistics (needed
+                                   to get confidence intervals later) */
+
+);
+
+int
+RGR_FindBestRobustRegression
+(double *x,
+ double *y,
+ int n,
+ double tol,
+ double *b0,
+ double *b1,
+ int *n_runs,
+ int *best_start);
+
+#endif /* GOT_REGRESS_H */
diff --git a/reports.h b/reports.h
new file mode 100644 (file)
index 0000000..a2bdb97
--- /dev/null
+++ b/reports.h
@@ -0,0 +1,121 @@
+/*
+  $Header: /cvs/src/chrony/reports.h,v 1.17 2002/02/28 23:27:13 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Data structure definitions within the daemon for various reports that
+  can be generated */
+
+#ifndef GOT_REPORTS_H
+#define GOT_REPORTS_H
+
+#include "sysincl.h"
+
+#define REPORT_INVALID_OFFSET 0x80000000
+
+typedef struct {
+  unsigned long ip_addr;
+  int stratum;
+  int poll;
+  enum {RPT_NTP_CLIENT, RPT_NTP_PEER, RPT_LOCAL_REFERENCE} mode;
+  enum {RPT_SYNC, RPT_UNREACH, RPT_FALSETICKER, RPT_JITTERY, RPT_OTHER} state;
+
+  unsigned long latest_meas_ago; /* seconds */
+  long orig_latest_meas; /* microseconds (us) */
+  long latest_meas; /* us */
+  unsigned long latest_meas_err; /* us */
+  long est_offset; /* us */
+  unsigned long est_offset_err; /* us */
+  long resid_freq; /* ppm * 1000 */
+  unsigned long resid_skew; /* ppm * 1000 */
+} RPT_SourceReport ;
+
+typedef struct {
+  unsigned long ref_id;
+  unsigned long stratum;
+  struct timeval ref_time;
+  struct timeval current_correction;
+  double freq_ppm;
+  double resid_freq_ppm;
+  double skew_ppm;
+  double root_delay;
+  double root_dispersion;
+} RPT_TrackingReport;
+
+typedef struct {
+  unsigned long ip_addr;
+  unsigned long n_samples;
+  unsigned long n_runs;
+  unsigned long span_seconds;
+  double resid_freq_ppm;
+  double skew_ppm;
+  double sd_us;
+} RPT_SourcestatsReport;
+
+typedef struct {
+  unsigned long ref_time;
+  unsigned short n_samples;
+  unsigned short n_runs;
+  unsigned long span_seconds;
+  double rtc_seconds_fast;
+  double rtc_gain_rate_ppm;
+} RPT_RTC_Report;
+
+typedef struct {
+  unsigned long client_hits;
+  unsigned long peer_hits;
+  unsigned long cmd_hits_auth;
+  unsigned long cmd_hits_normal;
+  unsigned long cmd_hits_bad;
+  unsigned long last_ntp_hit_ago;
+  unsigned long last_cmd_hit_ago;
+} RPT_ClientAccess_Report;
+
+typedef struct {
+  unsigned long ip_addr;
+  unsigned long client_hits;
+  unsigned long peer_hits;
+  unsigned long cmd_hits_auth;
+  unsigned long cmd_hits_normal;
+  unsigned long cmd_hits_bad;
+  unsigned long last_ntp_hit_ago;
+  unsigned long last_cmd_hit_ago;
+} RPT_ClientAccessByIndex_Report;
+
+typedef struct {
+  time_t when;
+  double slewed_offset;
+  double orig_offset;
+  double residual;
+} RPT_ManualSamplesReport;
+
+typedef struct {
+  int online;
+  int offline;
+  int burst_online;
+  int burst_offline;
+} RPT_ActivityReport;
+
+#endif /* GOT_REPORTS_H */
diff --git a/rtc.c b/rtc.c
new file mode 100644 (file)
index 0000000..fbb8884
--- /dev/null
+++ b/rtc.c
@@ -0,0 +1,222 @@
+/*
+  $Header: /cvs/src/chrony/rtc.c,v 1.13 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  */
+
+#include "sysincl.h"
+
+#include "rtc.h"
+#include "logging.h"
+#include "conf.h"
+
+#if defined LINUX
+#include "rtc_linux.h"
+#endif /* defined LINUX */
+
+/* ================================================== */
+
+static int driver_initialised = 0;
+
+static struct {
+  int  (*init)(void);
+  void (*fini)(void);
+  void (*time_pre_init)(void);
+  void (*time_init)(void (*after_hook)(void*), void *anything);
+  void (*start_measurements)(void);
+  int  (*write_parameters)(void);
+  int  (*get_report)(RPT_RTC_Report *report);
+  int  (*trim)(void);
+  void (*cycle_logfile)(void);
+} driver =
+{
+#if defined LINUX
+  RTC_Linux_Initialise,
+  RTC_Linux_Finalise,
+  RTC_Linux_TimePreInit,
+  RTC_Linux_TimeInit,
+  RTC_Linux_StartMeasurements,
+  RTC_Linux_WriteParameters,
+  RTC_Linux_GetReport,
+  RTC_Linux_Trim,
+  RTC_Linux_CycleLogFile
+#else
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+#endif 
+};
+     
+/* ================================================== */
+
+void
+RTC_Initialise(void)
+{
+  char *file_name;
+  int ok;
+
+  /* This is how we tell whether the user wants to load the RTC
+     driver, if he is on a machine where it is an option. */
+  file_name = CNF_GetRtcFile();
+
+  if (file_name) {
+    if (driver.init) {
+      if ((driver.init)()) {
+        ok = 1;
+      } else {
+        ok = 0;
+      }
+    } else {
+      ok = 0;
+    }
+
+    if (ok) {
+      driver_initialised = 1;
+    } else {
+      driver_initialised = 0;
+      LOG(LOGS_ERR, LOGF_Rtc, "Real time clock not supported on this operating system");
+    }
+
+  } else {
+    driver_initialised = 0;
+  }
+
+  return;
+
+}
+
+/* ================================================== */
+
+void
+RTC_Finalise(void)
+{
+  if (driver.fini) {
+    (driver.fini)();
+  }
+}
+
+/* ================================================== */
+/* Start the processing to get a single measurement from the real time
+   clock, and use it to trim the system time, based on knowing the
+   drift rate of the RTC and the error the last time we set it.  The
+   TimePreInit routine has already run, so we can be sure that the
+   trim required is not *too* large.
+
+   We are called with a hook to a function to be called after the
+   initialisation is complete.  We also call this if we cannot do the
+   initialisation. */
+
+void
+RTC_TimeInit(void (*after_hook)(void *), void *anything)
+{
+  if (driver_initialised) {
+    (driver.time_init)(after_hook, anything);
+  } else {
+    LOG(LOGS_ERR, LOGF_Rtc, "Can't initialise from real time clock, driver not loaded");
+    (after_hook)(anything);
+  }
+}
+
+/* ================================================== */
+/* Do an initial read of the RTC and set the system time to it.  This
+   is analogous to what /sbin/clock -s -u would do on Linux. */
+
+void
+RTC_TimePreInit(void)
+{
+  if (driver.time_pre_init) {
+    (driver.time_pre_init)();
+  }
+}
+
+/* ================================================== */
+/* Start the RTC measurement process */
+
+void
+RTC_StartMeasurements(void)
+{
+  if (driver_initialised) {
+    (driver.start_measurements)();
+  }
+  /* Benign if driver not present */
+}
+
+/* ================================================== */
+/* Write RTC information out to RTC file.  Return 0 for success, 1 if
+   RTC driver not running, or 2 if the file cannot be written. */
+
+int
+RTC_WriteParameters(void)
+{
+  if (driver_initialised) {
+    return (driver.write_parameters)();
+  } else {
+    return RTC_ST_NODRV;
+  }
+}
+
+/* ================================================== */
+
+int
+RTC_GetReport(RPT_RTC_Report *report)
+{
+  if (driver_initialised) {
+    return (driver.get_report)(report);
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+int
+RTC_Trim(void)
+{
+  if (driver_initialised) {
+    return (driver.trim)();
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+
+void
+RTC_CycleLogFile(void)
+{
+  if (driver_initialised) {
+    (driver.cycle_logfile)();
+  }
+}
+
+/* ================================================== */
+
diff --git a/rtc.h b/rtc.h
new file mode 100644 (file)
index 0000000..3cfdb87
--- /dev/null
+++ b/rtc.h
@@ -0,0 +1,52 @@
+/*
+  $Header: /cvs/src/chrony/rtc.h,v 1.9 2002/02/28 23:27:13 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  */
+
+#ifndef GOT_RTC_H
+#define GOT_RTC_H
+
+#include "reports.h"
+
+extern void RTC_Initialise(void);
+extern void RTC_Finalise(void);
+extern void RTC_TimePreInit(void);
+extern void RTC_TimeInit(void (*after_hook)(void *), void *anything);
+extern void RTC_StartMeasurements(void);
+extern int  RTC_GetReport(RPT_RTC_Report *report);
+
+#define RTC_ST_OK 0
+#define RTC_ST_NODRV 1
+#define RTC_ST_BADFILE 2
+
+extern int RTC_WriteParameters(void);
+
+extern int RTC_Trim(void);
+
+extern void RTC_CycleLogFile(void);
+
+#endif /* GOT_RTC_H */
diff --git a/rtc_linux.c b/rtc_linux.c
new file mode 100644 (file)
index 0000000..b01e584
--- /dev/null
@@ -0,0 +1,1174 @@
+/*
+  $Header: /cvs/src/chrony/rtc_linux.c,v 1.30 2003/04/01 20:07:20 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Real-time clock driver for linux.  This interfaces the program with
+  the clock that keeps time when the machine is turned off.
+
+  */
+
+#if defined LINUX
+
+#ifdef sparc
+#define __KERNEL__
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#ifdef HAS_SPINLOCK_H
+#include <linux/spinlock.h>
+#else
+/* Include dummy definition of spinlock_t to cope with earlier kernels. */
+typedef int spinlock_t;
+#endif
+
+/* This is a complete hack since the alpha sys/io.h needs these types
+ * but does not arrange them to be defined.  This is almost certainly
+ * not how one should do these things.  -- broonie
+ */
+#include <linux/types.h>
+#ifdef __alpha__
+typedef __u8  u8;
+typedef __u16 u16;
+typedef __u32 u32;
+typedef __u64 u64;
+#endif
+
+#if defined(__i386__) /* || defined(__sparc__) */
+#include <linux/mc146818rtc.h>
+#else
+#include <linux/rtc.h>
+#define RTC_UIE 0x10           /* update-finished interrupt enable */
+#endif
+
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+
+#include "logging.h"
+#include "sched.h"
+#include "local.h"
+#include "util.h"
+#include "sys_linux.h"
+#include "regress.h"
+#include "rtc.h"
+#include "rtc_linux.h"
+#include "conf.h"
+#include "memory.h"
+#include "mkdirpp.h"
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void measurement_timeout(void *any);
+
+static void read_from_device(void *any);
+
+/* ================================================== */
+
+typedef enum {
+  OM_NORMAL,
+  OM_INITIAL,
+  OM_AFTERTRIM
+} OperatingMode;
+
+static OperatingMode operating_mode = OM_NORMAL;
+
+/* ================================================== */
+
+static int fd = -1;
+
+#define LOWEST_MEASUREMENT_PERIOD 15
+#define HIGHEST_MEASUREMENT_PERIOD 480
+
+/* Try to avoid doing regression after _every_ sample we accumulate */
+#define N_SAMPLES_PER_REGRESSION 4
+
+static int measurement_period = LOWEST_MEASUREMENT_PERIOD;
+
+static int timeout_running = 0;
+static SCH_TimeoutID timeout_id;
+
+/* ================================================== */
+
+/* Maximum number of samples held */
+#define MAX_SAMPLES 64
+
+/* Real time clock samples.  We store the seconds count as originally
+   measured, together with a 'trim' that compensates these values for
+   any steps made to the RTC to bring it back into line
+   occasionally.  The trim is in seconds. */
+static time_t rtc_sec[MAX_SAMPLES];
+static double rtc_trim[MAX_SAMPLES];
+
+/* Reference time, against which delta times on the RTC scale are measured */
+static time_t rtc_ref;
+
+
+/* System clock (gettimeofday) samples associated with the above
+   samples. */
+static struct timeval system_times[MAX_SAMPLES];
+
+/* Number of samples currently stored. */
+static int n_samples;   
+
+/* Number of new samples since last regression */
+static int n_samples_since_regression;
+
+/* Number of runs of residuals in last regression (for logging) */
+static int n_runs;
+
+/* Coefficients */
+/* Whether they are valid */
+static int coefs_valid;
+
+/* Reference time */
+static time_t coef_ref_time;
+/* Number of seconds by which RTC was fast of the system time at coef_ref_time */
+static double coef_seconds_fast;
+
+/* Estimated number of seconds that RTC gains relative to system time
+   for each second of ITS OWN time */
+static double coef_gain_rate;
+
+/* Gain rate saved just before we step the RTC to correct it to the
+   nearest second, so that we can write a useful set of coefs to the
+   RTC data file once we have reacquired its offset after the step */
+static double saved_coef_gain_rate;
+
+/* Filename supplied by config file where RTC coefficients are
+   stored. */
+static char *coefs_file_name;
+
+/* ================================================== */
+/* Coefficients read from file at start of run. */
+
+/* Whether we have tried to load the coefficients */
+static int tried_to_load_coefs = 0;
+
+/* Whether valid coefficients were read */
+static int valid_coefs_from_file = 0;
+
+/* Coefs read in */
+static time_t file_ref_time;
+static double file_ref_offset, file_rate_ppm;
+
+/* ================================================== */
+
+/* Flag to remember whether to assume the RTC is running on UTC */
+static int rtc_on_utc = 0;
+
+/* ================================================== */
+
+static FILE *logfile=NULL;
+static char *logfilename = NULL;
+static unsigned long logwrites=0;
+
+#define RTC_LOG "rtc.log"
+
+/* ================================================== */
+
+static void (*after_init_hook)(void *) = NULL;
+static void *after_init_hook_arg = NULL;
+
+/* ================================================== */
+
+static void
+discard_samples(int new_first)
+{
+  int n_to_save;
+
+  if (!(new_first < n_samples)) {
+    CROAK("new_first should be < n_samples");
+  }
+  if (!(new_first >= 0)) {
+    CROAK("new_first should be non-negative");
+  }
+
+  n_to_save = n_samples - new_first;
+
+  memmove(rtc_sec, rtc_sec + new_first, n_to_save * sizeof(time_t));
+  memmove(rtc_trim, rtc_trim + new_first, n_to_save * sizeof(double));
+  memmove(system_times, system_times + new_first, n_to_save * sizeof(struct timeval));
+
+  n_samples = n_to_save;
+  return;
+}
+
+/* ================================================== */
+
+#define NEW_FIRST_WHEN_FULL 4
+
+static void
+accumulate_sample(time_t rtc, struct timeval *sys)
+{
+
+  if (n_samples == MAX_SAMPLES) {
+    /* Discard oldest samples */
+    discard_samples(NEW_FIRST_WHEN_FULL);
+  }
+
+  rtc_sec[n_samples] = rtc;
+
+  /* Always use most recent sample as reference */
+  rtc_ref = rtc;
+
+  rtc_trim[n_samples] = 0.0;
+  system_times[n_samples] = *sys;
+  ++n_samples;
+  ++n_samples_since_regression;
+  return;
+  
+}
+
+/* ================================================== */
+/* The new_sample flag is to indicate whether to adjust the
+   measurement period depending on the behaviour of the standard
+   deviation. */
+
+static void
+run_regression(int new_sample,
+               int *valid,
+               time_t *ref,
+               double *fast,
+               double *slope)
+{
+  double rtc_rel[MAX_SAMPLES]; /* Relative times on RTC axis */
+  double offsets[MAX_SAMPLES]; /* How much the RTC is fast of the system clock */
+  int i, n;
+  double est_intercept, est_slope;
+  int best_new_start;
+
+  if (n_samples > 0) {
+
+    n = n_samples - 1;
+
+    for (i=0; i<n_samples; i++) {
+      rtc_rel[i] = rtc_trim[i] + (double)(rtc_sec[i] - rtc_ref);
+      offsets[i] = ((double) (rtc_ref - system_times[i].tv_sec) -
+                    (1.0e-6 * (double) system_times[i].tv_usec) +
+                    rtc_rel[i]);
+
+    }
+
+    if (RGR_FindBestRobustRegression
+        (rtc_rel, offsets,
+         n_samples, 1.0e-9,
+         &est_intercept, &est_slope,
+         &n_runs,
+         &best_new_start)) {
+
+      /* Calculate and store coefficients.  We don't do any error
+         bounds processing on any of these. */
+      *valid = 1;
+      *ref = rtc_ref;
+      *fast = est_intercept;
+      *slope = est_slope;
+
+      if (best_new_start > 0) {
+        discard_samples(best_new_start);
+      }
+
+
+    } else {
+      /* Keep existing coefficients. */
+    }
+  } else {
+    /* Keep existing coefficients. */
+  }
+
+}
+
+/* ================================================== */
+
+static void
+slew_samples
+(struct timeval *raw, struct timeval *cooked,
+ double dfreq, double afreq_ppm,
+ double doffset, int is_step_change,
+ void *anything)
+{
+  int i;
+  double elapsed;
+  double new_freq;
+  double old_freq;
+  double delta_time;
+  double old_seconds_fast, old_gain_rate;
+
+  new_freq = 1.0e-6 * afreq_ppm;
+  old_freq = (new_freq - dfreq) / (1.0 - dfreq);
+
+  for (i=0; i<n_samples; i++) {
+    UTI_DiffTimevalsToDouble(&elapsed, cooked, system_times + i);
+
+    delta_time = -(elapsed * dfreq) - doffset;
+
+    UTI_AddDoubleToTimeval(system_times + i, delta_time, system_times + i);
+
+  }
+
+  old_seconds_fast = coef_seconds_fast;
+  old_gain_rate = coef_gain_rate;
+
+  if (coefs_valid) {
+    coef_seconds_fast += doffset;
+    coef_gain_rate = 1.0 - ((1.0 + new_freq) / (1.0 + old_freq)) * (1.0 - coef_gain_rate);
+  }
+
+#if 0
+  LOG(LOGS_INFO, LOGF_RtcLinux, "dfreq=%.8f doffset=%.6f new_freq=%.3f old_freq=%.3f old_fast=%.6f old_rate=%.3f new_fast=%.6f new_rate=%.3f",
+      dfreq, doffset, 1.0e6*new_freq, 1.0e6*old_freq,
+      old_seconds_fast, 1.0e6 * old_gain_rate,
+      coef_seconds_fast, 1.0e6 * coef_gain_rate);
+#endif
+
+}
+
+/* ================================================== */
+
+/* Function to convert from a time_t value represenging UTC to the
+   corresponding real time clock 'DMY HMS' form, taking account of
+   whether the user runs his RTC on the local time zone or UTC */
+
+static struct tm *
+rtc_from_t(const time_t *t)
+{
+  if (rtc_on_utc) {
+    return gmtime(t);
+  } else {
+    return localtime(t);
+  }
+}
+
+/* ================================================== */
+
+/* Inverse function to get back from RTC 'DMY HMS' form to time_t UTC
+   form.  This essentially uses mktime(), but involves some awful
+   complexity to cope with timezones.  The problem is that mktime's
+   behaviour with regard to the daylight saving flag in the 'struct
+   tm' does not seem to be reliable across all systems, unless that
+   flag is set to zero. 
+
+   tm_isdst = -1 does not seem to work with all libc's - it is treated
+   as meaning there is DST, or fails completely.  (It is supposed to
+   use the timezone info to work out whether summer time is active at
+   the specified epoch).
+
+   tm_isdst = 1 fails if the local timezone has no summer time defined.
+
+   The approach taken is as follows.  Suppose the RTC is on localtime.
+   We perform all mktime calls with the tm_isdst field set to zero.
+
+   Let y be the RTC reading in 'DMY HMS' form.  Let M be the mktime
+   function with tm_isdst=0 and L be the localtime function.
+
+   We seek x such that y = L(x).  Now there will exist a value Z(t)
+   such that M(L(t)) = t + Z(t) for all t, where Z(t) depends on
+   whether daylight saving is active at time t.
+
+   We want L(x) = y.  Therefore M(L(x)) = x + Z = M(y).  But
+   M(L(M(y))) = M(y) + Z.  Therefore x = M(y) - Z = M(y) - (M(L(M(y)))
+   - M(y)).
+
+   The case for the RTC running on UTC is identical but without the
+   potential complication that Z depends on t.
+*/
+
+static time_t
+t_from_rtc(struct tm *stm) {
+  struct tm temp1, temp2;
+  long diff;
+  time_t t1, t2;
+
+  temp1 = *stm;
+  temp1.tm_isdst = 0;
+  
+  t1 = mktime(&temp1);
+  if (rtc_on_utc) {
+    temp2 = *gmtime(&t1);
+  } else {
+    temp2 = *localtime(&t1);
+  }
+  
+  temp2.tm_isdst = 0;
+  t2 = mktime(&temp2);
+  diff = t2 - t1;
+
+  return t1 - diff;
+}
+
+/* ================================================== */
+
+static void
+setup_config(void)
+{
+  if (CNF_GetRTCOnUTC()) {
+    rtc_on_utc = 1;
+  } else {
+    rtc_on_utc = 0;
+  }
+}
+
+/* ================================================== */
+/* Read the coefficients from the file where they were saved
+   the last time the program was run. */
+
+static void
+read_coefs_from_file(void)
+{
+  FILE *in;
+  char line[256];
+
+  if (!tried_to_load_coefs) {
+
+    valid_coefs_from_file = 0; /* only gets set true if we succeed */
+
+    tried_to_load_coefs = 1;
+
+    in = fopen(coefs_file_name, "r");
+    if (in) {
+      if (fgets(line, sizeof(line), in)) {
+        if (sscanf(line, "%d%ld%lf%lf",
+                   &valid_coefs_from_file,
+                   &file_ref_time,
+                   &file_ref_offset,
+                   &file_rate_ppm) == 4) {
+        } else {
+          LOG(LOGS_WARN, LOGF_RtcLinux, "Could not parse coefficients line from RTC file %s",
+              coefs_file_name);
+        }
+      } else {
+        LOG(LOGS_WARN, LOGF_RtcLinux, "Could not read first line from RTC file %s",
+            coefs_file_name);
+      }
+      fclose(in);
+    } else {
+      LOG(LOGS_WARN, LOGF_RtcLinux, "Could not open RTC file %s for reading",
+          coefs_file_name);
+    }
+  }
+}
+
+/* ================================================== */
+/* Write the coefficients to the file where they will be read
+   the next time the program is run. */
+
+static int
+write_coefs_to_file(int valid,time_t ref_time,double offset,double rate)
+{
+  struct stat buf;
+  char *temp_coefs_file_name;
+  FILE *out;
+
+  /* Create a temporary file with a '.tmp' extension. */
+
+  temp_coefs_file_name = (char*) Malloc(strlen(coefs_file_name)+8);
+
+  if(!temp_coefs_file_name) {
+    return RTC_ST_BADFILE;
+  }
+
+  strcpy(temp_coefs_file_name,coefs_file_name);
+  strcat(temp_coefs_file_name,".tmp");
+
+  out = fopen(temp_coefs_file_name, "w");
+  if (!out) {
+    Free(temp_coefs_file_name);
+    LOG(LOGS_WARN, LOGF_RtcLinux, "Could not open temporary RTC file %s.tmp for writing",
+        coefs_file_name);
+    return RTC_ST_BADFILE;
+  }
+
+  /* Gain rate is written out in ppm */
+  fprintf(out, "%1d %ld %.6f %.3f\n",
+          valid,ref_time, offset, 1.0e6 * rate);
+
+  fclose(out);
+
+  /* Clone the file attributes from the existing file if there is one. */
+
+  if (!stat(coefs_file_name,&buf)) {
+    chown(temp_coefs_file_name,buf.st_uid,buf.st_gid);
+    chmod(temp_coefs_file_name,buf.st_mode&0777);
+  }
+
+  /* Rename the temporary file to the correct location (see rename(2) for details). */
+
+  if (rename(temp_coefs_file_name,coefs_file_name)) {
+    unlink(temp_coefs_file_name);
+    Free(temp_coefs_file_name);
+    LOG(LOGS_WARN, LOGF_RtcLinux, "Could not replace old RTC file %s.tmp with new one %s",
+        coefs_file_name, coefs_file_name);
+    return RTC_ST_BADFILE;
+  }
+
+  Free(temp_coefs_file_name);
+
+  return RTC_ST_OK;
+}
+
+
+/* ================================================== */
+/* file_name is the name of the file where we save the RTC params
+   between executions.  Return status is whether we could initialise
+   on this version of the system. */
+
+int
+RTC_Linux_Initialise(void)
+{
+  int major, minor, patch;
+  char *direc;
+
+  /* Check whether we can support the real time clock.
+
+     Linux 1.2.x - haven't checked yet
+
+     Linux 1.3.x - don't know, haven't got a system to look at
+
+     Linux 2.0.x - For x<=31, using any variant of the adjtimex() call
+     sets the kernel into a mode where the RTC was updated every 11
+     minutes.  The only way to escape this is to use settimeofday().
+     Since we need to have sole control over the RTC to be able to
+     measure its drift rate, and there is no 'notify' callback to warn
+     you that the kernel is going to do this, I can't see a way to
+     support this.
+
+     Linux 2.0.x - For x>=32 the adjtimex()/RTC behaviour was
+     modified, so that as long as the STA_UNSYNC flag is set the RTC
+     is left alone.  This is the mode we exploit here, so that the RTC
+     continues to go its own sweet way, unless we make updates to it
+     from this module.
+
+     Linux 2.1.x - don't know, haven't got a system to look at.
+
+     Linux 2.2.x, 2.3.x and 2.4.x are believed to be OK for all
+     patch levels
+
+     */
+
+  SYS_Linux_GetKernelVersion(&major, &minor, &patch);
+
+  /* Obviously this test can get more elaborate when we know about
+     more system types. */
+  if (major != 2) {
+    return 0;
+  } else {
+    switch (minor) {
+      case 0:
+        if (patch <= 31) {
+          return 0;
+        }
+        break;
+      case 1:
+        return 0;
+        break;
+      case 2:
+      case 3:
+      case 4:
+        break; /* OK for all patch levels */
+    } 
+  }
+
+  /* Setup details depending on configuration options */
+  setup_config();
+
+  /* In case it didn't get done by pre-init */
+  coefs_file_name = CNF_GetRtcFile();
+
+  /* Try to open device */
+
+  fd = open (CNF_GetRtcDevice(), O_RDWR);
+  if (fd < 0) {
+    LOG(LOGS_ERR, LOGF_RtcLinux, "Could not open %s, %s", CNF_GetRtcDevice(), strerror(errno));
+    return 0;
+  }
+
+  n_samples = 0;
+  n_samples_since_regression = 0;
+  n_runs = 0;
+  coefs_valid = 0;
+
+  measurement_period = LOWEST_MEASUREMENT_PERIOD;
+
+  operating_mode = OM_NORMAL;
+
+  /* Register file handler */
+  SCH_AddInputFileHandler(fd, read_from_device, NULL);
+
+  /* Register slew handler */
+  LCL_AddParameterChangeHandler(slew_samples, NULL);
+
+  if (CNF_GetLogRtc()) {
+    direc = CNF_GetLogDir();
+    if (!mkdir_and_parents(direc)) {
+      LOG(LOGS_ERR, LOGF_RtcLinux, "Could not create directory %s", direc);
+      logfile = NULL;
+    } else {
+      logfilename = MallocArray(char, 2 + strlen(direc) + strlen(RTC_LOG));
+      strcpy(logfilename, direc);
+      strcat(logfilename, "/");
+      strcat(logfilename, RTC_LOG);
+      logfile = fopen(logfilename, "a");
+      if (!logfile) {
+        LOG(LOGS_WARN, LOGF_RtcLinux, "Couldn't open logfile %s for update", logfilename);
+      }
+    }
+  }
+
+  return 1;
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_Finalise(void)
+{
+  if (timeout_running) {
+    SCH_RemoveTimeout(timeout_id);
+    timeout_running = 0;
+  }
+
+  /* Remove input file handler */
+  if (fd >= 0) {
+    SCH_RemoveInputFileHandler(fd);
+    close(fd);
+
+    /* Save the RTC data */
+    (void) RTC_Linux_WriteParameters();
+
+  }
+
+  if (logfile) {
+    fclose(logfile);
+  }
+
+}
+
+/* ================================================== */
+
+static void
+switch_interrupts(int onoff)
+{
+  int status;
+
+  if (onoff) {
+    status = ioctl(fd, RTC_UIE_ON, 0);
+    if (status < 0) {
+      LOG(LOGS_ERR, LOGF_RtcLinux, "Could not start measurement : %s", strerror(errno));
+      return;
+    }
+  } else {
+    status = ioctl(fd, RTC_UIE_OFF, 0);
+    if (status < 0) {
+      LOG(LOGS_ERR, LOGF_RtcLinux, "Could not stop measurement : %s", strerror(errno));
+      return;
+    }
+  }
+}    
+
+/* ================================================== */
+
+static void
+measurement_timeout(void *any)
+{
+  timeout_running = 0;
+  switch_interrupts(1);
+}
+
+/* ================================================== */
+
+static void
+set_rtc(time_t new_rtc_time)
+{
+  struct tm rtc_tm;
+  struct rtc_time rtc_raw;
+  int status;
+
+  rtc_tm = *rtc_from_t(&new_rtc_time);
+
+  rtc_raw.tm_sec = rtc_tm.tm_sec;
+  rtc_raw.tm_min = rtc_tm.tm_min;
+  rtc_raw.tm_hour = rtc_tm.tm_hour;
+  rtc_raw.tm_mday = rtc_tm.tm_mday;
+  rtc_raw.tm_mon = rtc_tm.tm_mon;
+  rtc_raw.tm_year = rtc_tm.tm_year;
+
+  status = ioctl(fd, RTC_SET_TIME, &rtc_raw);
+  if (status < 0) {
+    LOG(LOGS_ERR, LOGF_RtcLinux, "Could not set RTC time");
+  }
+
+}
+
+/* ================================================== */
+
+static void
+handle_initial_trim(void)
+{
+  double rate;
+  long delta_time;
+  double rtc_error_now, sys_error_now;
+
+    /* The idea is to accumulate some number of samples at 1 second
+       intervals, then do a robust regression fit to this.  This
+       should give a good fix on the intercept (=system clock error
+       rel to RTC) at a particular time, removing risk of any
+       particular sample being an outlier.  We can then look at the
+       elapsed interval since the epoch recorded in the RTC file,
+       and correct the system time accordingly. */
+    
+  run_regression(1, &coefs_valid, &coef_ref_time, &coef_seconds_fast, &coef_gain_rate);
+
+  n_samples_since_regression = 0;
+  n_samples = 0;
+
+  read_coefs_from_file();
+
+  if (valid_coefs_from_file) {
+    /* Can process data */
+    delta_time = coef_ref_time - file_ref_time;
+    rate = 1.0e-6 * file_rate_ppm;
+    rtc_error_now = file_ref_offset + rate * (double) delta_time;
+          
+    /* sys_error_now is positive if the system clock is fast */
+    sys_error_now = rtc_error_now - coef_seconds_fast;
+          
+    LOG(LOGS_INFO, LOGF_RtcLinux, "System trim from RTC = %f", sys_error_now);
+    LCL_AccumulateOffset(sys_error_now);
+  } else {
+    LOG(LOGS_WARN, LOGF_RtcLinux, "No valid file coefficients, cannot trim system time");
+  }
+  
+  coefs_valid = 0;
+  
+  (after_init_hook)(after_init_hook_arg);
+  
+  operating_mode = OM_NORMAL;
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+handle_relock_after_trim(void)
+{
+  int valid;
+  time_t ref;
+  double fast, slope;
+
+  run_regression(1, &valid, &ref, &fast, &slope);
+
+  if (valid) {
+    write_coefs_to_file(1,ref,fast,saved_coef_gain_rate);
+  } else {
+    LOG(LOGS_WARN, LOGF_RtcLinux, "Could not do regression after trim");
+  }
+
+  n_samples = 0;
+  n_samples_since_regression = 0;
+  operating_mode = OM_NORMAL;
+  measurement_period = LOWEST_MEASUREMENT_PERIOD;
+}
+
+/* ================================================== */
+
+/* Day number of 1 Jan 1970 */
+#define MJD_1970 40587
+
+static void
+process_reading(time_t rtc_time, struct timeval *system_time)
+{
+  double rtc_fast;
+
+  accumulate_sample(rtc_time, system_time);
+
+  switch (operating_mode) {
+    case OM_NORMAL:
+
+      if (n_samples_since_regression >= /* 4 */ 1 ) {
+        run_regression(1, &coefs_valid, &coef_ref_time, &coef_seconds_fast, &coef_gain_rate);
+        n_samples_since_regression = 0;
+      }
+      
+      break;
+    case OM_INITIAL:
+      if (n_samples_since_regression >= 8) {
+        handle_initial_trim();
+      }
+      break;
+    case OM_AFTERTRIM:
+      if (n_samples_since_regression >= 8) {
+        handle_relock_after_trim();
+      }
+      break;
+    default:
+      CROAK("Impossible");
+      break;
+  }  
+
+
+  if (logfile) {
+    rtc_fast = (double)(rtc_time - system_time->tv_sec) - 1.0e-6 * (double) system_time->tv_usec;
+
+    if (((logwrites++) % 32) == 0) {
+      fprintf(logfile,
+              "===============================================================================\n"
+              "   Date (UTC) Time   RTC fast (s) Val   Est fast (s)   Slope (ppm)  Ns  Nr Meas\n"
+              "===============================================================================\n");
+    }
+    
+    fprintf(logfile, "%s %14.6f %1d  %14.6f  %12.3f  %2d  %2d %4d\n",
+            UTI_TimeToLogForm(system_time->tv_sec),
+            rtc_fast,
+            coefs_valid,
+            coef_seconds_fast, coef_gain_rate * 1.0e6, n_samples, n_runs, measurement_period);
+
+    fflush(logfile);
+  }    
+
+}
+
+/* ================================================== */
+
+static void
+read_from_device(void *any)
+{
+  int status;
+  unsigned long data;
+  struct timeval sys_time;
+  struct rtc_time rtc_raw;
+  struct tm rtc_tm;
+  time_t rtc_t;
+  double read_err;
+  int error = 0;
+
+  status = read(fd, &data, sizeof(data));
+  if (status < 0) {
+    LOG(LOGS_ERR, LOGF_RtcLinux, "Could not read flags %s : %s", CNF_GetRtcDevice(), strerror(errno));
+    error = 1;
+    goto turn_off_interrupt;
+  }    
+
+  if ((data & RTC_UIE) == RTC_UIE) {
+    /* Update interrupt detected */
+    
+    /* Read RTC time, sandwiched between two polls of the system clock
+       so we can bound any error. */
+
+    LCL_ReadCookedTime(&sys_time, &read_err);
+
+    status = ioctl(fd, RTC_RD_TIME, &rtc_raw);
+    if (status < 0) {
+      LOG(LOGS_ERR, LOGF_RtcLinux, "Could not read time from %s : %s", CNF_GetRtcDevice(), strerror(errno));
+      error = 1;
+      goto turn_off_interrupt;
+    }
+
+    /* Convert RTC time into a struct timeval */
+    rtc_tm.tm_sec = rtc_raw.tm_sec;
+    rtc_tm.tm_min = rtc_raw.tm_min;
+    rtc_tm.tm_hour = rtc_raw.tm_hour;
+    rtc_tm.tm_mday = rtc_raw.tm_mday;
+    rtc_tm.tm_mon = rtc_raw.tm_mon;
+    rtc_tm.tm_year = rtc_raw.tm_year;
+
+    rtc_t = t_from_rtc(&rtc_tm);
+
+    if (rtc_t == (time_t)(-1)) {
+      LOG(LOGS_ERR, LOGF_RtcLinux, "Could not convert RTC time to timeval");
+      error = 1;
+      goto turn_off_interrupt;
+    }      
+
+    process_reading(rtc_t, &sys_time);
+
+    if (n_samples < 4) {
+      measurement_period = LOWEST_MEASUREMENT_PERIOD;
+    } else if (n_samples < 6) {
+      measurement_period = LOWEST_MEASUREMENT_PERIOD << 1;
+    } else if (n_samples < 10) {
+      measurement_period = LOWEST_MEASUREMENT_PERIOD << 2;
+    } else if (n_samples < 14) {
+      measurement_period = LOWEST_MEASUREMENT_PERIOD << 3;
+    } else {
+      measurement_period = LOWEST_MEASUREMENT_PERIOD << 4;
+    }
+
+  }
+
+turn_off_interrupt:
+
+  switch (operating_mode) {
+    case OM_INITIAL:
+      if (error) {
+        LOG(LOGS_WARN, LOGF_RtcLinux, "Could not complete initial step due to errors");
+        operating_mode = OM_NORMAL;
+        (after_init_hook)(after_init_hook_arg);
+
+        switch_interrupts(0);
+    
+        timeout_running = 1;
+        timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+      }
+
+      break;
+
+    case OM_AFTERTRIM:
+      if (error) {
+        LOG(LOGS_WARN, LOGF_RtcLinux, "Could not complete after trim relock due to errors");
+        operating_mode = OM_NORMAL;
+
+        switch_interrupts(0);
+    
+        timeout_running = 1;
+        timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+      }
+      
+      break;
+
+    case OM_NORMAL:
+      switch_interrupts(0);
+    
+      timeout_running = 1;
+      timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+
+      break;
+    default:
+      CROAK("Impossible");
+      break;
+  }
+
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_TimeInit(void (*after_hook)(void *), void *anything)
+{
+  after_init_hook = after_hook;
+  after_init_hook_arg = anything;
+
+  operating_mode = OM_INITIAL;
+  timeout_running = 0;
+  switch_interrupts(1);
+
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_StartMeasurements(void)
+{
+  timeout_running = 0;
+  measurement_timeout(NULL);
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_WriteParameters(void)
+{
+  int retval;
+
+  if (fd < 0) {
+    return RTC_ST_NODRV;
+  }
+  
+  if (coefs_valid) {
+    retval = write_coefs_to_file(1,coef_ref_time, coef_seconds_fast, coef_gain_rate);
+  } else {
+   /* Don't change the existing file, it may not be 100% valid but is our
+      current best guess. */
+    retval = RTC_ST_OK; /*write_coefs_to_file(0,0,0.0,0.0); */
+  }
+
+  return(retval);
+}
+
+/* ================================================== */
+/* Try to set the system clock from the RTC, in the same manner as
+   /sbin/clock -s -u would do.  We're not as picky about OS version
+   etc in this case, since we have fewer requirements regarding the
+   RTC behaviour than we do for the rest of the module. */
+
+void
+RTC_Linux_TimePreInit(void)
+{
+  int fd, status;
+  struct rtc_time rtc_raw;
+  struct tm rtc_tm;
+  time_t rtc_t, estimated_correct_rtc_t;
+  long interval;
+  double accumulated_error = 0.0;
+  struct timeval new_sys_time;
+
+  coefs_file_name = CNF_GetRtcFile();
+
+  setup_config();
+  read_coefs_from_file();
+
+  fd = open(CNF_GetRtcDevice(), O_RDONLY);
+
+  if (fd < 0) {
+    return; /* Can't open it, and won't be able to later */
+  }
+
+  status = ioctl(fd, RTC_RD_TIME, &rtc_raw);
+
+  if (status >= 0) {
+    /* Convert to seconds since 1970 */
+    rtc_tm.tm_sec = rtc_raw.tm_sec;
+    rtc_tm.tm_min = rtc_raw.tm_min;
+    rtc_tm.tm_hour = rtc_raw.tm_hour;
+    rtc_tm.tm_mday = rtc_raw.tm_mday;
+    rtc_tm.tm_mon = rtc_raw.tm_mon;
+    rtc_tm.tm_year = rtc_raw.tm_year;
+    
+    rtc_t = t_from_rtc(&rtc_tm);
+
+    if (rtc_t != (time_t)(-1)) {
+
+      /* Work out approximatation to correct time (to about the
+         nearest second) */
+      if (valid_coefs_from_file) {
+        interval = rtc_t - file_ref_time;
+        accumulated_error = file_ref_offset + (double)(interval) * 1.0e-6 * file_rate_ppm;
+
+        /* Correct time */
+        LOG(LOGS_INFO, LOGF_RtcLinux, "Set system time, error in RTC = %f",
+            accumulated_error);
+        estimated_correct_rtc_t = rtc_t - (long)(0.5 + accumulated_error);
+      } else {
+        estimated_correct_rtc_t = rtc_t - (long)(0.5 + accumulated_error);
+      }
+
+      new_sys_time.tv_sec = estimated_correct_rtc_t;
+      new_sys_time.tv_usec = 0;
+
+      /* Tough luck if this fails */
+      if (settimeofday(&new_sys_time, NULL) < 0) {
+        LOG(LOGS_WARN, LOGF_RtcLinux, "Could not settimeofday");
+      }
+    } else {
+      LOG(LOGS_WARN, LOGF_RtcLinux, "Could not convert RTC reading to seconds since 1/1/1970");
+    }
+  }
+
+  close(fd);
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_GetReport(RPT_RTC_Report *report)
+{
+  report->ref_time = (unsigned long) coef_ref_time;
+  report->n_samples = n_samples;
+  report->n_runs = n_runs;
+  if (n_samples > 1) {
+    report->span_seconds = ((rtc_sec[n_samples-1] - rtc_sec[0]) +
+                            (long)(rtc_trim[n_samples-1] - rtc_trim[0]));
+  } else {
+    report->span_seconds = 0;
+  }
+  report->rtc_seconds_fast = coef_seconds_fast;
+  report->rtc_gain_rate_ppm = 1.0e6 * coef_gain_rate;
+  return 1;
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_Trim(void)
+{
+  struct timeval now;
+  double local_clock_err;
+
+
+  /* Remember the slope coefficient - we won't be able to determine a
+     good one in a few seconds when we determine the new offset! */
+  saved_coef_gain_rate = coef_gain_rate;
+
+  if (fabs(coef_seconds_fast) > 1.0) {
+
+    LOG(LOGS_INFO, LOGF_RtcLinux, "Trimming RTC, error = %.3f seconds", coef_seconds_fast);
+
+    /* Do processing to set clock.  Let R be the value we set the
+       RTC to, then in 500ms the RTC ticks (R+1) (see comments in
+       arch/i386/kernel/time.c about the behaviour of the real time
+       clock chip).  If S is the system time now, the error at the
+       next RTC tick is given by E = (R+1) - (S+0.5).  Ideally we
+       want |E| <= 0.5, which implies R <= S <= R+1, i.e. R is just
+       the rounded down part of S, i.e. the seconds part. */
+
+    LCL_ReadCookedTime(&now, &local_clock_err);
+    
+    set_rtc(now.tv_sec);
+
+    /* All old samples will now look bogus under the new
+           regime. */
+    n_samples = 0;
+    operating_mode = OM_AFTERTRIM;
+
+    /* And start rapid sampling, interrupts on now */
+    if (timeout_running) {
+      SCH_RemoveTimeout(timeout_id);
+      timeout_running = 0;
+    }
+    switch_interrupts(1);
+  }
+
+  return 1;
+  
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_CycleLogFile(void)
+{
+  if (logfile && logfilename) {
+    fclose(logfile);
+    logfile = fopen(logfilename, "a");
+    if (!logfile) {
+      LOG(LOGS_WARN, LOGF_RtcLinux, "Could not reopen logfile %s", logfilename);
+    }
+    logwrites = 0;
+  }
+}
+
+/* ================================================== */
+
+#endif /* defined LINUX */
diff --git a/rtc_linux.h b/rtc_linux.h
new file mode 100644 (file)
index 0000000..7c0025a
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+  $Header: /cvs/src/chrony/rtc_linux.h,v 1.13 2002/02/28 23:27:13 richard Exp $
+
+  ======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  ======================================================================
+
+  */
+
+#ifndef _GOT_RTC_LINUX_H
+#define _GOT_RTC_LINUX_H
+
+#include "reports.h"
+
+#if defined LINUX
+
+extern int RTC_Linux_Initialise(void);
+extern void RTC_Linux_Finalise(void);
+extern void RTC_Linux_TimePreInit(void);
+extern void RTC_Linux_TimeInit(void (*after_hook)(void *), void *anything);
+extern void RTC_Linux_StartMeasurements(void);
+
+/* 0=success, 1=no driver, 2=can't write file */
+extern int RTC_Linux_WriteParameters(void);
+
+extern int RTC_Linux_GetReport(RPT_RTC_Report *report);
+extern int RTC_Linux_Trim(void);
+
+extern void RTC_Linux_CycleLogFile(void);
+
+#endif /* defined LINUX */
+
+#endif /* _GOT_RTC_LINUX_H */
diff --git a/sched.c b/sched.c
new file mode 100644 (file)
index 0000000..2843a64
--- /dev/null
+++ b/sched.c
@@ -0,0 +1,601 @@
+/*
+  $Header: /cvs/src/chrony/sched.c,v 1.16 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This file contains the scheduling loop and the timeout queue.
+
+  */
+
+#include "sysincl.h"
+
+#include "sched.h"
+#include "memory.h"
+#include "util.h"
+#include "local.h"
+#include "logging.h"
+
+/* ================================================== */
+
+/* Flag indicating that we are initialised */
+static int initialised = 0;
+
+/* ================================================== */
+
+/* Variables to handle the capability to dispatch on particular file
+   handles becoming readable */
+
+/* Each bit set in this fd set corresponds to a read descriptor that
+   we are watching and with which we have a handler associated in the
+   file_handlers array */
+static fd_set read_fds;
+
+/* This is the number of bits that we have set in read_fds */
+static unsigned int n_read_fds;
+
+/* One more than the highest file descriptor that is registered */
+static unsigned int one_highest_fd;
+
+/* This assumes that fd_set is implemented as a fixed size array of
+   bits, possibly embedded inside a record.  It might therefore
+   somewhat non-portable. */
+
+#define FD_SET_SIZE (sizeof(fd_set) * 8)
+
+typedef struct {
+  SCH_FileHandler       handler;
+  SCH_ArbitraryArgument arg;
+} FileHandlerEntry;
+
+static FileHandlerEntry file_handlers[FD_SET_SIZE];
+
+/* ================================================== */
+
+/* Variables to handler the timer queue */
+
+typedef struct _TimerQueueEntry
+{
+  struct _TimerQueueEntry *next; /* Forward and back links in the list */
+  struct _TimerQueueEntry *prev;
+  struct timeval tv;            /* Local system time at which the
+                                   timeout is to expire.  Clearly this
+                                   must be in terms of what the
+                                   operating system thinks of as
+                                   system time, because it will be an
+                                   argument to select().  Therefore,
+                                   any fudges etc that our local time
+                                   driver module would apply to time
+                                   that we pass to clients etc doesn't
+                                   apply to this. */
+  SCH_TimeoutID id;             /* ID to allow client to delete
+                                   timeout */
+  SCH_TimeoutClass class;       /* The class that the epoch is in */
+  SCH_TimeoutHandler handler;   /* The handler routine to use */
+  SCH_ArbitraryArgument arg;    /* The argument to pass to the handler */
+
+} TimerQueueEntry;
+
+/* The timer queue.  We only use the next and prev entries of this
+   record, these chain to the real entries. */
+static TimerQueueEntry timer_queue;
+static unsigned long n_timer_queue_entries;
+static SCH_TimeoutID next_tqe_id;
+
+/* Pointer to head of free list */
+static TimerQueueEntry *tqe_free_list = NULL;
+
+/* ================================================== */
+
+static int need_to_exit;
+
+/* ================================================== */
+
+static void
+handle_slew(struct timeval *raw,
+            struct timeval *cooked,
+            double dfreq,
+            double afreq,
+            double doffset,
+            int is_step_change,
+            void *anything);
+
+/* ================================================== */
+
+void
+SCH_Initialise(void)
+{
+
+  FD_ZERO(&read_fds);
+  n_read_fds = 0;
+
+  n_timer_queue_entries = 0;
+  next_tqe_id = 0;
+
+  timer_queue.next = &timer_queue;
+  timer_queue.prev = &timer_queue;
+
+  need_to_exit = 0;
+
+  LCL_AddParameterChangeHandler(handle_slew, NULL);
+
+  initialised = 1;
+
+  return;
+}
+
+
+/* ================================================== */
+
+void
+SCH_Finalise(void) {
+  initialised = 0;
+  return; /* Nothing to do for now */
+}
+
+/* ================================================== */
+
+void
+SCH_AddInputFileHandler
+(int fd, SCH_FileHandler handler, SCH_ArbitraryArgument arg)
+{
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+  
+  /* Don't want to allow the same fd to register a handler more than
+     once without deleting a previous association - this suggests
+     a bug somewhere else in the program. */
+  if (FD_ISSET(fd, &read_fds)) {
+    CROAK("File handler already registered");
+  }
+
+  ++n_read_fds;
+  
+  file_handlers[fd].handler = handler;
+  file_handlers[fd].arg     = arg;
+
+  FD_SET(fd, &read_fds);
+
+  if ((fd + 1) > one_highest_fd) {
+    one_highest_fd = fd + 1;
+  }
+
+  return;
+}
+
+
+/* ================================================== */
+
+void
+SCH_RemoveInputFileHandler(int fd)
+{
+  int fds_left, fd_to_check;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  /* Check that a handler was registered for the fd in question */
+  if (!FD_ISSET(fd, &read_fds)) {
+    CROAK("File handler not registered");
+  }
+
+  --n_read_fds;
+
+  FD_CLR(fd, &read_fds);
+
+  /* Find new highest file descriptor */
+  fds_left = n_read_fds;
+  fd_to_check = 0;
+  while (fds_left > 0) {
+    if (FD_ISSET(fd_to_check, &read_fds)) {
+      --fds_left;
+    }
+    ++fd_to_check;
+  }
+
+  one_highest_fd = fd_to_check;
+
+  return;
+
+}
+
+/* ================================================== */
+
+#define TQE_ALLOC_QUANTUM 32
+
+static TimerQueueEntry *
+allocate_tqe(void)
+{
+  TimerQueueEntry *new_block;
+  TimerQueueEntry *result;
+  int i;
+  if (tqe_free_list == NULL) {
+    new_block = MallocArray(TimerQueueEntry, TQE_ALLOC_QUANTUM);
+    for (i=1; i<TQE_ALLOC_QUANTUM; i++) {
+      new_block[i].next = &(new_block[i-1]);
+    }
+    new_block[0].next = NULL;
+    tqe_free_list = &(new_block[TQE_ALLOC_QUANTUM - 1]);
+  }
+
+  result = tqe_free_list;
+  tqe_free_list = tqe_free_list->next;
+  return result;
+}
+
+/* ================================================== */
+
+static void
+release_tqe(TimerQueueEntry *node)
+{
+  node->next = tqe_free_list;
+  tqe_free_list = node;
+  return;
+}
+
+/* ================================================== */
+
+SCH_TimeoutID
+SCH_AddTimeout(struct timeval *tv, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg)
+{
+  TimerQueueEntry *new_tqe;
+  TimerQueueEntry *ptr;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  new_tqe = allocate_tqe();
+
+  new_tqe->id = next_tqe_id++;
+  new_tqe->handler = handler;
+  new_tqe->arg = arg;
+  new_tqe->tv = *tv;
+  new_tqe->class = SCH_ReservedTimeoutValue;
+
+  /* Now work out where to insert the new entry in the list */
+  for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+    if (UTI_CompareTimevals(&new_tqe->tv, &ptr->tv) == -1) {
+      /* If the new entry comes before the current pointer location in
+         the list, we want to insert the new entry just before ptr. */
+      break;
+    }
+  }
+
+  /* At this stage, we want to insert the new entry immediately before
+     the entry identified by 'ptr' */
+
+  new_tqe->next = ptr;
+  new_tqe->prev = ptr->prev;
+  ptr->prev->next = new_tqe;
+  ptr->prev = new_tqe;
+
+  n_timer_queue_entries++;
+
+  return new_tqe->id;
+}
+
+/* ================================================== */
+/* This queues a timeout to elapse at a given delta time relative to
+   the current (raw) time */
+
+SCH_TimeoutID
+SCH_AddTimeoutByDelay(double delay, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg)
+{
+  struct timeval now, then;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  LCL_ReadRawTime(&now);
+  UTI_AddDoubleToTimeval(&now, delay, &then);
+  return SCH_AddTimeout(&then, handler, arg);
+
+}
+
+/* ================================================== */
+
+SCH_TimeoutID
+SCH_AddTimeoutInClass(double min_delay, double separation,
+                      SCH_TimeoutClass class,
+                      SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg)
+{
+  TimerQueueEntry *new_tqe;
+  TimerQueueEntry *ptr;
+  struct timeval now;
+  double diff;
+  double new_min_delay;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+  
+  LCL_ReadRawTime(&now);
+  new_min_delay = min_delay;
+
+  /* Scan through list for entries in the same class and increase min_delay
+     if necessary to keep at least the separation away */
+  for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+    if (ptr->class == class) {
+      UTI_DiffTimevalsToDouble(&diff, &ptr->tv, &now);
+      if (new_min_delay > diff) {
+        if (new_min_delay - diff < separation) {
+          new_min_delay = diff + separation;
+        }
+      }
+      if (new_min_delay < diff) {
+        if (diff - new_min_delay < separation) {
+          new_min_delay = diff + separation;
+        }
+      }
+    }
+  }
+
+  for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+    UTI_DiffTimevalsToDouble(&diff, &ptr->tv, &now);
+    if (diff > new_min_delay) {
+      break;
+    }
+  }
+
+  /* We have located the insertion point */
+  new_tqe = allocate_tqe();
+
+  new_tqe->id = next_tqe_id++;
+  new_tqe->handler = handler;
+  new_tqe->arg = arg;
+  UTI_AddDoubleToTimeval(&now, new_min_delay, &new_tqe->tv);
+  new_tqe->class = class;
+
+  new_tqe->next = ptr;
+  new_tqe->prev = ptr->prev;
+  ptr->prev->next = new_tqe;
+  ptr->prev = new_tqe;
+  n_timer_queue_entries++;
+
+  return new_tqe->id;
+}
+
+/* ================================================== */
+
+void
+SCH_RemoveTimeout(SCH_TimeoutID id)
+{
+  TimerQueueEntry *ptr;
+  int ok;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  ok = 0;
+
+  for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+
+    if (ptr->id == id) {
+      /* Found the required entry */
+      
+      /* Unlink from the queue */
+      ptr->next->prev = ptr->prev;
+      ptr->prev->next = ptr->next;
+      
+      /* Decrement entry count */
+      --n_timer_queue_entries;
+      
+      /* Release memory back to the operating system */
+      release_tqe(ptr);
+
+      ok = 1;
+
+      break;
+    }
+  }
+
+  assert(ok);
+
+}
+
+/* ================================================== */
+/* The current time (now) has to be passed in from the
+   caller to avoid race conditions */
+
+static int
+dispatch_timeouts(struct timeval *now) {
+  TimerQueueEntry *ptr;
+  int n_done = 0;
+
+  while ((n_timer_queue_entries > 0) &&
+         (UTI_CompareTimevals(now, &(timer_queue.next->tv)) >= 0)) {
+    ptr = timer_queue.next;
+
+    /* Dispatch the handler */
+    (ptr->handler)(ptr->arg);
+
+    /* Increment count of timeouts handled */
+    ++n_done;
+
+    /* Unlink entry from the queue */
+    ptr->prev->next = ptr->next;
+    ptr->next->prev = ptr->prev;
+
+    /* Decrement count of entries in queue */
+    --n_timer_queue_entries;
+
+    /* Delete entry */
+    release_tqe(ptr);
+  }
+
+  return n_done;
+
+}
+
+/* ================================================== */
+
+/* nfh is the number of bits set in fhs */
+
+static void
+dispatch_filehandlers(int nfh, fd_set *fhs)
+{
+  int fh = 0;
+  
+  while (nfh > 0) {
+    if (FD_ISSET(fh, fhs)) {
+
+      /* This descriptor can be read from, dispatch its handler */
+      (file_handlers[fh].handler)(file_handlers[fh].arg);
+
+      /* Decrement number of readable files still to find */
+      --nfh;
+    }
+
+    ++fh;
+  }
+
+}
+
+/* ================================================== */
+
+static void
+handle_slew(struct timeval *raw,
+            struct timeval *cooked,
+            double dfreq,
+            double afreq,
+            double doffset,
+            int is_step_change,
+            void *anything)
+{
+  TimerQueueEntry *ptr;
+  struct timeval T1;
+
+  if (is_step_change) {
+    /* We're not interested in anything else - it won't affect the
+       functionality of timer event dispatching.  If a step change
+       occurs, just shift all the timeouts by the offset */
+    
+    for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+      UTI_AddDoubleToTimeval(&ptr->tv, -doffset, &T1);
+      ptr->tv = T1;
+    }
+
+  }
+}
+
+/* ================================================== */
+
+void
+SCH_MainLoop(void)
+{
+  fd_set rd, wr, ex;
+  int status;
+  struct timeval tv, *ptv;
+  struct timeval now;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  while (!need_to_exit) {
+
+    /* Copy current set of read file descriptors */
+    memcpy((void *) &rd, (void *) &read_fds, sizeof(fd_set));
+    
+    /* Blank the write and exception descriptors - we aren't very
+       interested */
+    FD_ZERO(&wr);
+    FD_ZERO(&ex);
+
+    /* Try to dispatch any timeouts that have already gone by, and
+       keep going until all are done.  (The earlier ones may take so
+       long to do that the later ones come around by the time they are
+       completed). */
+
+    do {
+      LCL_ReadRawTime(&now);
+    } while (dispatch_timeouts(&now) > 0);
+    
+    /* Check whether there is a timeout and set it up */
+    if (n_timer_queue_entries > 0) {
+
+      UTI_DiffTimevals(&tv, &(timer_queue.next->tv), &now);
+      ptv = &tv;
+
+    } else {
+      ptv = NULL;
+    }
+
+    /* if there are no file descriptors being waited on and no
+       timeout set, this is clearly ridiculous, so stop the run */
+
+    if (!ptv && (n_read_fds == 0)) {
+      LOG_FATAL(LOGF_Scheduler, "No descriptors or timeout to wait for");
+    }
+
+    status = select(one_highest_fd, &rd, &wr, &ex, ptv);
+
+    if (status < 0) {
+      CROAK("Status < 0 after select");
+    } else if (status > 0) {
+      /* A file descriptor is ready to read */
+
+      dispatch_filehandlers(status, &rd);
+
+    } else {
+      if (status != 0) {
+        CROAK("Unexpected value from select");
+      }
+
+      /* No descriptors readable, timeout must have elapsed.
+       Therefore, tv must be non-null */
+      if (!ptv) {
+        CROAK("No descriptors or timeout?");
+      }
+
+      /* There's nothing to do here, since the timeouts
+         will be dispatched at the top of the next loop
+         cycle */
+
+    }
+  }         
+
+  return;
+
+}
+
+/* ================================================== */
+
+void
+SCH_QuitProgram(void)
+{
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+  need_to_exit = 1;
+}
+
+/* ================================================== */
+
diff --git a/sched.h b/sched.h
new file mode 100644 (file)
index 0000000..de62430
--- /dev/null
+++ b/sched.h
@@ -0,0 +1,83 @@
+/*
+  $Header: /cvs/src/chrony/sched.h,v 1.10 2002/02/28 23:27:14 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Exported header file for sched.c
+  */
+
+#ifndef GOT_SCHED_H
+#define GOT_SCHED_H
+
+#include "sysincl.h"
+
+typedef unsigned long SCH_TimeoutID;
+
+typedef unsigned long SCH_TimeoutClass;
+static const SCH_TimeoutClass SCH_ReservedTimeoutValue = 0;
+static const SCH_TimeoutClass SCH_NtpSamplingClass = 1;
+static const SCH_TimeoutClass SCH_NtpBroadcastClass = 2;
+
+typedef void* SCH_ArbitraryArgument;
+typedef void (*SCH_FileHandler)(SCH_ArbitraryArgument);
+typedef void (*SCH_TimeoutHandler)(SCH_ArbitraryArgument);
+
+/* Exported functions */
+
+/* Initialisation function for the module */
+extern void SCH_Initialise(void);
+
+/* Finalisation function for the module */
+extern void SCH_Finalise(void);
+
+/* Register a handler for when select goes true on a file descriptor */
+extern void SCH_AddInputFileHandler
+(int fd,                        /* The file descriptor */
+ SCH_FileHandler,               /* The handler routine */
+ SCH_ArbitraryArgument          /* An arbitrary passthrough argument to the handler */
+);
+extern void SCH_RemoveInputFileHandler(int fd);
+
+/* This queues a timeout to elapse at a given (raw) local time */
+extern SCH_TimeoutID SCH_AddTimeout(struct timeval *tv, SCH_TimeoutHandler, SCH_ArbitraryArgument);
+
+/* This queues a timeout to elapse at a given delta time relative to the current (raw) time */
+extern SCH_TimeoutID SCH_AddTimeoutByDelay(double delay, SCH_TimeoutHandler, SCH_ArbitraryArgument);
+
+/* This queues a timeout in a particular class, ensuring that the
+   expiry time is at least a given separation away from any other
+   timeout in the same class */
+extern SCH_TimeoutID SCH_AddTimeoutInClass(double min_delay, double separation,
+                                           SCH_TimeoutClass class,
+                                           SCH_TimeoutHandler handler, SCH_ArbitraryArgument);
+
+/* The next one probably ought to return a status code */
+extern void SCH_RemoveTimeout(SCH_TimeoutID);
+
+extern void SCH_MainLoop(void);
+
+extern void SCH_QuitProgram(void);
+
+#endif /* GOT_SCHED_H */
diff --git a/sources.c b/sources.c
new file mode 100644 (file)
index 0000000..1dae611
--- /dev/null
+++ b/sources.c
@@ -0,0 +1,938 @@
+/*
+  $Header: /cvs/src/chrony/sources.c,v 1.31 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  The routines in this file manage the complete pool of sources that
+  we might be synchronizing to.  This includes NTP sources and others
+  (e.g. local reference clocks, eyeball + wristwatch etc).
+
+  */
+
+#include "sysincl.h"
+
+#include "sources.h"
+#include "sourcestats.h"
+#include "memory.h"
+#include "ntp.h" /* For NTP_Leap */
+#include "local.h"
+#include "reference.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "reports.h"
+#include "nameserv.h"
+#include "mkdirpp.h"
+
+/* ================================================== */
+/* Flag indicating that we are initialised */
+static int initialised = 0;
+
+/* ================================================== */
+/* Structure used to hold info for selecting between sources */
+struct SelectInfo {
+  int stratum;
+  int select_ok;
+  double variance;
+  double root_delay;
+  double root_dispersion; 
+  double root_distance;
+  double best_offset;
+  double lo_limit;
+  double hi_limit;
+};
+
+/* ================================================== */
+/* This enum contains the flag values that are used to label
+   each source */
+typedef enum {
+  SRC_OK,                       /* OK so far */
+  SRC_UNREACHABLE,              /* Source is not reachable */
+  SRC_BAD_STATS,                /* Stats driver could not supply valid
+                                   data */
+  SRC_FALSETICKER,              /* Source is found to be a falseticker */
+  SRC_JITTERY,                  /* Source scatter worse than other's dispersion */
+  SRC_SELECTABLE,               /* Source is acceptable candidate */
+  SRC_SYNC                      /* Current synchronisation source */
+} SRC_Status;
+
+/* ================================================== */
+/* Define the instance structure used to hold information about each
+   source */
+struct SRC_Instance_Record {
+  SST_Stats stats;
+  NTP_Leap leap_status;         /* Leap status */
+  int index;                    /* Index back into the array of source */
+  unsigned long ref_id;         /* The reference ID of this source
+                                   (i.e. its IP address, NOT the
+                                   reference _it_ is sync'd to) */
+
+  /* Flag indicating that we are receiving packets with valid headers
+     from this source and can use it as a reference */
+  int reachable;
+
+  /* Flag indicating the status of the source */
+  SRC_Status status;
+
+  struct SelectInfo sel_info;
+};
+
+/* ================================================== */
+/* Structure used to build the sort list for finding falsetickers */
+struct Sort_Element {
+  int index;
+  double offset;
+  enum {LOW=-1, CENTRE=0, HIGH=1} tag;
+};
+
+/* ================================================== */
+/* Table of sources */
+static struct SRC_Instance_Record **sources;
+static struct Sort_Element *sort_list;
+static int *sel_sources;
+static int n_sources; /* Number of sources currently in the table */
+static int max_n_sources; /* Capacity of the table */
+
+#define INVALID_SOURCE (-1)
+static int selected_source_index; /* Which source index is currently
+                                     selected (set to INVALID_SOURCE
+                                     if no current valid reference) */
+
+/* ================================================== */
+/* Forward prototype */
+
+static void
+slew_sources(struct timeval *raw, struct timeval *cooked, double dfreq, double afreq,
+             double doffset, int is_step_change, void *anything);
+
+/* ================================================== */
+/* Initialisation function */
+void SRC_Initialise(void) {
+  sources = NULL;
+  sort_list = NULL;
+  n_sources = 0;
+  max_n_sources = 0;
+  selected_source_index = INVALID_SOURCE;
+  initialised = 1;
+
+  LCL_AddParameterChangeHandler(slew_sources, NULL);
+
+  return;
+}
+
+/* ================================================== */
+/* Finalisation function */
+void SRC_Finalise(void)
+{
+  LCL_RemoveParameterChangeHandler(slew_sources, NULL);
+  initialised = 0;
+  return;
+}
+
+/* ================================================== */
+/* Function to create a new instance.  This would be called by one of
+   the individual source-type instance creation routines. */
+
+SRC_Instance SRC_CreateNewInstance(unsigned long ref_id)
+{
+  SRC_Instance result;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  result = MallocNew(struct SRC_Instance_Record);
+  result->stats = SST_CreateInstance(ref_id);
+
+  if (n_sources == max_n_sources) {
+    /* Reallocate memory */
+    max_n_sources += 32;
+    if (sources) {
+      sources = ReallocArray(struct SRC_Instance_Record *, max_n_sources, sources);
+      sort_list = ReallocArray(struct Sort_Element, 3*max_n_sources, sort_list);
+      sel_sources = ReallocArray(int, max_n_sources, sel_sources);
+    } else {
+      sources = MallocArray(struct SRC_Instance_Record *, max_n_sources);
+      sort_list = MallocArray(struct Sort_Element, 3*max_n_sources);
+      sel_sources = MallocArray(int, max_n_sources);
+    }
+  }
+
+  sources[n_sources] = result;
+  result->index = n_sources;
+  result->leap_status = LEAP_Normal;
+  result->ref_id = ref_id;
+  result->reachable = 0;
+  result->status = SRC_BAD_STATS;
+
+  n_sources++;
+
+  return result;
+}
+
+/* ================================================== */
+/* Function to get rid of a source when it is being unconfigured.
+   This may cause the current reference source to be reselected, if this
+   was the reference source or contributed significantly to a
+   falseticker decision. */
+
+void SRC_DestroyInstance(SRC_Instance instance)
+{
+  int dead_index, i;
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  if (instance->index == selected_source_index) {
+    instance->reachable = 0;
+    SRC_SelectSource(0);
+  }
+
+  SST_DeleteInstance(instance->stats);
+  dead_index = instance->index;
+  for (i=dead_index; i<n_sources-1; i++) {
+    sources[i] = sources[i+1];
+    sources[i]->index = i;
+  }
+  --n_sources;
+  Free(instance);
+
+  if (selected_source_index > dead_index) {
+    --selected_source_index;
+  }
+}
+
+/* ================================================== */
+/* Function to get the range of frequencies, relative to the given
+   source, that we believe the local clock lies within.  The return
+   values are in terms of the number of seconds fast (+ve) or slow
+   (-ve) relative to the source that the local clock becomes after a
+   given amount of local time has elapsed.
+
+   Suppose the initial offset relative to the source is U (fast +ve,
+   slow -ve) and a time interval T elapses measured in terms of the
+   local clock.  Then the error relative to the source at the end of
+   the interval should lie in the interval [U+T*lo, U+T*hi]. */
+
+void SRC_GetFrequencyRange(SRC_Instance instance, double *lo, double *hi)
+{
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  SST_GetFrequencyRange(instance->stats, lo, hi);
+  return;
+}
+
+/* ================================================== */
+
+/* This function is called by one of the source drivers when it has
+   a new sample that is to be accumulated.
+
+   This function causes the frequency estimation to be re-run for the
+   designated source, and the clock selection procedure to be re-run
+   afterwards.
+
+   Parameters are described in sources.h
+
+   */
+
+void SRC_AccumulateSample
+(SRC_Instance inst, 
+ struct timeval *sample_time, 
+ double offset, 
+ double peer_delay,
+ double peer_dispersion,
+ double root_delay, 
+ double root_dispersion, 
+ int stratum,
+ NTP_Leap leap_status)
+{
+
+  if (!initialised) {
+    CROAK("Should be initialised");
+  }
+
+  inst->leap_status = leap_status;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_Sources, "ip=[%s] t=%s ofs=%f del=%f disp=%f str=%d",
+      UTI_IPToDottedQuad(inst->ref_id), UTI_TimevalToString(sample_time), -offset, root_delay, root_dispersion, stratum);
+#endif
+
+  /* WE HAVE TO NEGATE OFFSET IN THIS CALL, IT IS HERE THAT THE SENSE OF OFFSET
+     IS FLIPPED */
+  SST_AccumulateSample(inst->stats, sample_time, -offset, peer_delay, peer_dispersion, root_delay, root_dispersion, stratum);
+  SST_DoNewRegression(inst->stats);
+  /* And redo clock selection */
+  SRC_SelectSource(inst->ref_id);
+
+  return;
+}
+
+/* ================================================== */
+
+void
+SRC_SetReachable(SRC_Instance inst)
+{
+  inst->reachable = 1;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_Sources, "%s", UTI_IPToDottedQuad(inst->ref_id));
+#endif
+
+  /* Don't do selection at this point, though - that will come about
+     in due course when we get some useful data from the source */
+}
+
+/* ================================================== */
+
+void
+SRC_UnsetReachable(SRC_Instance inst)
+{
+  inst->reachable = 0;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_Sources, "%s%s", UTI_IPToDottedQuad(inst->ref_id),
+      (inst->index == selected_source_index) ? "(REF)":"");
+#endif
+
+  /* If this was the previous reference source, we have to reselect!  */
+
+  if (inst->index == selected_source_index) {
+    SRC_SelectSource(0);
+  }
+
+}
+
+/* ================================================== */
+
+static int
+compare_sort_elements(const void *a, const void *b)
+{
+  const struct Sort_Element *u = (const struct Sort_Element *) a;
+  const struct Sort_Element *v = (const struct Sort_Element *) b;
+
+  if (u->offset < v->offset) {
+    return -1;
+  } else if (u->offset > v->offset) {
+    return +1;
+  } else if (u->tag < v->tag) {
+    return -1;
+  } else if (u->tag > v->tag) {
+    return +1;
+  } else {
+    return 0;
+  }
+}
+
+/* ================================================== */
+/* This function selects the current reference from amongst the pool
+   of sources we are holding. 
+   
+   Updates are only made to the local reference if match_addr is zero or is
+   equal to the selected reference source address */
+
+void
+SRC_SelectSource(unsigned long match_addr)
+{
+  int i, j, index;
+  struct timeval now;
+  double local_clock_err;
+  int src_select_ok;
+  double src_offset, src_offset_sd, src_frequency, src_skew;
+  double src_accrued_dispersion;
+  int n_endpoints, j1, j2;
+  double best_lo, best_hi;
+  int depth, best_depth;
+  int n_sel_sources;
+  double distance, min_distance;
+  int stratum, min_stratum;
+  int min_distance_index;
+  struct SelectInfo *si;
+  double total_root_dispersion;
+  int n_reachable_sources;
+
+  NTP_Leap leap_status = LEAP_Normal;
+
+  if (n_sources == 0) {
+    /* In this case, we clearly cannot synchronise to anything */
+    if (selected_source_index != INVALID_SOURCE) {
+      LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no sources");
+    }
+    selected_source_index = INVALID_SOURCE;
+    REF_SetUnsynchronised();
+    return;
+  }
+
+  LCL_ReadCookedTime(&now, &local_clock_err);
+
+  /* Step 1 - build intervals about each source */
+  n_endpoints = 0;
+  n_reachable_sources = 0;
+  for (i=0; i<n_sources; i++) {
+
+    if (sources[i]->reachable) {
+
+      ++n_reachable_sources;
+
+      si = &(sources[i]->sel_info);
+      SST_GetSelectionData(sources[i]->stats, &now,
+                           &(si->stratum),
+                           &(si->best_offset),
+                           &(si->root_delay),
+                           &(si->root_dispersion),
+                           &(si->variance),
+                           &(si->select_ok));
+
+      /* Eventually this might be a flag indicating whether the get
+         selection data call was successful.  For now it always is. */
+      src_select_ok = 1;
+
+      si->root_distance = si->root_dispersion + 0.5 * fabs(si->root_delay);
+      si->lo_limit = si->best_offset - si->root_distance;
+      si->hi_limit = si->best_offset + si->root_distance;
+
+#if 0
+      LOG(LOGS_INFO, LOGF_Sources, "%s off=%f dist=%f lo=%f hi=%f",
+          UTI_IPToDottedQuad(sources[i]->ref_id),
+          si->best_offset, si->root_distance,
+          si->lo_limit, si->hi_limit);
+#endif
+      
+      if (src_select_ok) {
+
+        sources[i]->status = SRC_OK; /* For now */
+
+        /* Otherwise it will be hard to pick this one later!  However,
+           this test might be too strict, we might want to dump it */
+        j1 = n_endpoints;
+        j2 = j1 + 1;
+
+        sort_list[j1].index = i;
+        sort_list[j1].offset = si->lo_limit;
+        sort_list[j1].tag = LOW;
+
+        sort_list[j2].index = i;
+        sort_list[j2].offset = si->hi_limit;
+        sort_list[j2].tag = HIGH;
+
+        n_endpoints += 2;
+
+      } else {
+        sources[i]->status = SRC_BAD_STATS;
+      }
+    } else {
+      /* If the source is not reachable, there is no way we will pick
+         it. */
+      sources[i]->status = SRC_UNREACHABLE;
+    }
+  }
+
+#if 0
+  LOG(LOGS_INFO, LOGF_Sources, "n_endpoints=%d", n_endpoints);
+#endif
+
+  /* Now sort the endpoint list */
+  if (n_endpoints > 0) {
+
+    /* Sort the list into order */
+    qsort((void *) sort_list, n_endpoints, sizeof(struct Sort_Element), compare_sort_elements);
+    
+    /* Now search for the interval which is contained in the most
+       individual source intervals.  Any source which overlaps this
+       will be a candidate.
+
+       If we get a case like
+
+       <----------------------->
+           <-->   
+                    <-->
+           <===========>
+
+       we will build the interval as shown with '=', whereas with an extra source we get
+       <----------------------->
+          <------->
+           <-->
+                    <-->
+           <==>
+
+       The first case is just bad luck - we need extra sources to
+       detect the falseticker, so just make an arbitrary choice based
+       on stratum & stability etc.
+       */
+
+    depth = best_depth = 0;
+    best_lo = best_hi = 0.0;
+
+    for (i=0; i<n_endpoints; i++) {
+#if 0
+      LOG(LOGS_INFO, LOGF_Sources, "i=%d t=%f tag=%d addr=%s", i, sort_list[i].offset, sort_list[i].tag,
+          UTI_IPToDottedQuad(sources[sort_list[i].index]->ref_id));
+#endif
+      switch(sort_list[i].tag) {
+        case LOW:
+          depth++;
+          if (depth > best_depth) {
+            best_depth = depth;
+            best_lo = sort_list[i].offset;
+          }
+          break;
+
+        case CENTRE:
+          CROAK("CENTRE cannot occur");
+          break;
+
+        case HIGH:
+          if (depth == best_depth) {
+            best_hi = sort_list[i].offset;
+          }
+          depth--;
+          break;
+
+      }
+    }
+
+#if 0
+    LOG(LOGS_INFO, LOGF_Sources, "best_depth=%d best_lo=%f best_hi=%f",
+        best_depth, best_lo, best_hi);
+#endif
+
+    if (best_depth <= n_reachable_sources/2) {
+      /* Could not even get half the reachable sources to agree -
+         clearly we can't synchronise.
+
+         srcs     #to agree
+           1          1
+           2          2
+           3          2
+           4          3 etc
+
+           */
+
+      if (selected_source_index != INVALID_SOURCE) {
+        LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no majority");
+      }
+      selected_source_index = INVALID_SOURCE;
+      REF_SetUnsynchronised();
+
+      /* .. and mark all sources as falsetickers (so they appear thus
+         on the outputs from the command client) */
+
+      for (i=0; i<n_sources; i++) {
+        sources[i]->status = SRC_FALSETICKER;
+      }
+
+    } else {
+      
+      /* We have our interval, now work out which source are in it,
+         i.e. build list of admissible sources. */
+      
+      n_sel_sources = 0;
+      for (i=0; i<n_sources; i++) {
+        if (sources[i]->status == SRC_OK) {
+          /* This should be the same condition to get into the endpoint
+             list */
+          /* Check if source's interval contains the best interval, or
+             is wholly contained within it */
+          if (((sources[i]->sel_info.lo_limit <= best_lo) &&
+               (sources[i]->sel_info.hi_limit >= best_hi)) ||
+              ((sources[i]->sel_info.lo_limit >= best_lo) &&
+               (sources[i]->sel_info.hi_limit <= best_hi))) {
+
+            sel_sources[n_sel_sources++] = i;
+#if 0
+            LOG(LOGS_INFO, LOGF_Sources, "i=%d addr=%s is valid", i, UTI_IPToDottedQuad(sources[i]->ref_id));
+#endif
+          } else {
+            sources[i]->status = SRC_FALSETICKER;
+#if 0
+            LOG(LOGS_INFO, LOGF_Sources, "i=%d addr=%s is a falseticker", i, UTI_IPToDottedQuad(sources[i]->ref_id));
+#endif
+          }
+        }
+      }
+
+      /* We now have a list of indices for the sources which pass the
+         false-ticker test.  Now go on to reject those whose variance is
+         greater than the minimum distance of any other */
+
+      /* Find minimum distance */
+      index = sel_sources[0];
+      min_distance = sources[index]->sel_info.root_distance;
+      for (i=1; i<n_sel_sources; i++) {
+        index = sel_sources[i];
+        distance = sources[index]->sel_info.root_distance;
+        if (distance < min_distance) {
+          min_distance = distance;
+        }
+      }
+
+#if 0
+      LOG(LOGS_INFO, LOGF_Sources, "min_distance=%f", min_distance);
+#endif
+
+      /* Now go through and prune any sources that have excessive
+         variance */
+      for (i=0; i<n_sel_sources; i++) {
+        index = sel_sources[i];
+        if (sources[index]->sel_info.variance > min_distance) {
+          sel_sources[i] = INVALID_SOURCE;
+          sources[index]->status = SRC_JITTERY;
+#if 0
+          LOG(LOGS_INFO, LOGF_Sources, "i=%d addr=%s has too much variance", i, UTI_IPToDottedQuad(sources[i]->ref_id));
+#endif
+        }
+      }
+
+      /* Now crunch the list and mark all sources as selectable */
+      for (i=j=0; i<n_sel_sources; i++) {
+        index = sel_sources[i];
+        if (index != INVALID_SOURCE) {
+          sources[index]->status = SRC_SELECTABLE;
+          sel_sources[j++] = sel_sources[i];
+          index++;
+        }
+      }
+      n_sel_sources = j;
+
+      /* Now find minimum stratum.  If none are left now,
+         tough. RFC1305 is not so harsh on pruning sources due to
+         excess variance, which prevents this from happening */
+
+      if (n_sel_sources > 0) {
+        index = sel_sources[0];
+        min_stratum = sources[index]->sel_info.stratum;
+        for (i=1; i<n_sel_sources; i++) {
+          index = sel_sources[i];
+          stratum = sources[index]->sel_info.stratum;
+          if (stratum < min_stratum) min_stratum = stratum;
+        }
+
+#if 0
+        LOG(LOGS_INFO, LOGF_Sources, "min_stratum=%d", min_stratum);
+#endif
+
+        /* Does the current source have this stratum and is it still a
+           survivor? */
+
+        if ((selected_source_index == INVALID_SOURCE) ||
+            (sources[selected_source_index]->status != SRC_SELECTABLE) ||
+            (sources[selected_source_index]->sel_info.stratum > min_stratum)) {
+          
+          /* We have to elect a new synchronisation source */
+          min_distance_index = INVALID_SOURCE;
+          for (i=0; i<n_sel_sources; i++) {
+            index = sel_sources[i];
+            if (sources[index]->sel_info.stratum == min_stratum) {
+              if ((min_distance_index == INVALID_SOURCE) ||
+                  (sources[index]->sel_info.root_distance < min_distance)) {
+                min_distance = sources[index]->sel_info.root_distance;
+                min_distance_index = index;
+              }
+            }
+          }
+
+          selected_source_index = min_distance_index;
+          LOG(LOGS_INFO, LOGF_Sources, "Selected source %s",
+              UTI_IPToDottedQuad(sources[selected_source_index]->ref_id));
+                                 
+
+#if 0
+          LOG(LOGS_INFO, LOGF_Sources, "new_sel_index=%d", min_distance_index);
+#endif
+        } else {
+          /* We retain the existing sync source, see p40 of RFC1305b.ps */
+#if 0
+          LOG(LOGS_INFO, LOGF_Sources, "existing reference retained", min_distance_index);
+#endif
+          
+        }
+
+        sources[selected_source_index]->status = SRC_SYNC;
+
+        /* Now just use the statistics of the selected source for
+           trimming the local clock */
+
+        LCL_ReadCookedTime(&now, &local_clock_err);
+
+        SST_GetTrackingData(sources[selected_source_index]->stats, &now,
+                            &src_offset, &src_offset_sd,
+                            &src_accrued_dispersion,
+                            &src_frequency, &src_skew);
+
+        total_root_dispersion = (src_accrued_dispersion +
+                                 sources[selected_source_index]->sel_info.root_dispersion);
+
+        if ((match_addr == 0) ||
+            (match_addr == sources[selected_source_index]->ref_id)) {
+
+          REF_SetReference(min_stratum, leap_status,
+                           sources[selected_source_index]->ref_id,
+                           &now,
+                           src_offset,
+                           src_frequency,
+                           src_skew,
+                           sources[selected_source_index]->sel_info.root_delay,
+                           total_root_dispersion);
+        }
+
+      } else {
+        if (selected_source_index != INVALID_SOURCE) {
+          LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no selectable sources");
+        }
+        selected_source_index = INVALID_SOURCE;
+        REF_SetUnsynchronised();
+
+      }
+
+
+    }
+
+  } else {
+    /* No sources provided valid endpoints */
+    if (selected_source_index != INVALID_SOURCE) {
+      LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no reachable sources");
+    }
+    selected_source_index = INVALID_SOURCE;
+    REF_SetUnsynchronised();
+  }
+
+}
+
+/* ================================================== */
+
+double
+SRC_PredictOffset(SRC_Instance inst, struct timeval *when)
+{
+  return SST_PredictOffset(inst->stats, when);
+}
+
+/* ================================================== */
+
+double
+SRC_MinRoundTripDelay(SRC_Instance inst)
+{
+  return SST_MinRoundTripDelay(inst->stats);
+}
+
+/* ================================================== */
+/* This routine is registered as a callback with the local clock
+   module, to be called whenever the local clock changes frequency or
+   is slewed.  It runs through all the existing source statistics, and
+   adjusts them to make them look as though they were sampled under
+   the new regime. */
+
+static void
+slew_sources(struct timeval *raw,
+             struct timeval *cooked,
+             double dfreq,
+             double afreq,
+             double doffset,
+             int is_step_change,
+             void *anything)
+{
+  int i;
+
+  for (i=0; i<n_sources; i++) {
+    SST_SlewSamples(sources[i]->stats, cooked, dfreq, doffset);
+  }
+  
+}
+
+/* ================================================== */
+/* This is called to dump out the source measurement registers */
+
+void
+SRC_DumpSources(void)
+{
+  FILE *out;
+  int direc_len;
+  char *filename;
+  unsigned int a, b, c, d;
+  int i;
+  char *direc;
+
+  direc = CNF_GetDumpDir();
+  direc_len = strlen(direc);
+  filename = MallocArray(char, direc_len+24); /* a bit of slack */
+  if (mkdir_and_parents(direc)) {
+    for (i=0; i<n_sources; i++) {
+      a = (sources[i]->ref_id) >> 24;
+      b = ((sources[i]->ref_id) >> 16) & 0xff;
+      c = ((sources[i]->ref_id) >> 8) & 0xff;
+      d = ((sources[i]->ref_id)) & 0xff;
+      
+      sprintf(filename, "%s/%d.%d.%d.%d.dat", direc, a, b, c, d);
+      out = fopen(filename, "w");
+      if (!out) {
+        LOG(LOGS_WARN, LOGF_Sources, "Could not open dump file %s", filename);
+      } else {
+        SST_SaveToFile(sources[i]->stats, out);
+        fclose(out);
+      }
+    }
+  } else {
+    LOG(LOGS_ERR, LOGF_Sources, "Could not create directory %s", direc);
+  }
+  Free(filename);
+}
+
+/* ================================================== */
+
+void
+SRC_ReloadSources(void)
+{
+  FILE *in;
+  char *filename;
+  unsigned int a, b, c, d;
+  int i;
+  char *dumpdir;
+  int dumpdirlen;
+
+  for (i=0; i<n_sources; i++) {
+    a = (sources[i]->ref_id) >> 24;
+    b = ((sources[i]->ref_id) >> 16) & 0xff;
+    c = ((sources[i]->ref_id) >> 8) & 0xff;
+    d = ((sources[i]->ref_id)) & 0xff;
+
+    dumpdir = CNF_GetDumpDir();
+    dumpdirlen = strlen(dumpdir);
+    filename = MallocArray(char, dumpdirlen+24);
+    sprintf(filename, "%s/%d.%d.%d.%d.dat", dumpdir, a, b, c, d);
+    in = fopen(filename, "r");
+    if (!in) {
+      LOG(LOGS_WARN, LOGF_Sources, "Could not open dump file %s", filename);
+    } else {
+      if (SST_LoadFromFile(sources[i]->stats, in)) {
+        /* We might want to use SST_DoUpdateRegression here, but we
+           need to check it has the same functionality */
+        SST_DoNewRegression(sources[i]->stats);
+      } else {
+        LOG(LOGS_WARN, LOGF_Sources, "Problem loading from file %s", filename);
+      }
+      fclose(in);
+    }
+    Free(filename);
+  }
+}
+
+/* ================================================== */
+
+int
+SRC_IsSyncPeer(SRC_Instance inst)
+{
+  if (inst->index == selected_source_index) {
+    return 1;
+  } else {
+    return 0;
+  }
+
+}
+
+/* ================================================== */
+
+int
+SRC_ReadNumberOfSources(void)
+{
+  return n_sources;
+}
+
+/* ================================================== */
+
+int
+SRC_ReportSource(int index, RPT_SourceReport *report, struct timeval *now)
+{
+  SRC_Instance src;
+  if ((index >= n_sources) || (index < 0)) {
+    return 0;
+  } else {
+    src = sources[index];
+    report->ip_addr = src->ref_id;
+    switch (src->status) {
+      case SRC_SYNC:
+        report->state = RPT_SYNC;
+        break;
+      case SRC_JITTERY:
+        report->state = RPT_JITTERY;
+        break;
+      case SRC_UNREACHABLE:
+        report->state = RPT_UNREACH;
+        break;
+      case SRC_FALSETICKER:
+        report->state = RPT_FALSETICKER;
+        break;
+      default:
+        report->state = RPT_OTHER;
+        break;
+    }
+    /* Call stats module to fill out estimates */
+    SST_DoSourceReport(src->stats, report, now);
+
+    return 1;
+  }
+
+}
+
+/* ================================================== */
+
+int
+SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report)
+{ 
+  SRC_Instance src;
+
+  if ((index >= n_sources) || (index < 0)) {
+    return 0;
+  } else {
+    src = sources[index];
+    report->ip_addr = src->ref_id;
+    SST_DoSourcestatsReport(src->stats, report);
+    return 1;
+  }
+}
+
+/* ================================================== */
+
+SRC_Skew_Direction SRC_LastSkewChange(SRC_Instance inst)
+{
+  SRC_Skew_Direction result = SRC_Skew_Nochange;
+  
+  switch (SST_LastSkewChange(inst->stats)) {
+    case SST_Skew_Decrease:
+      result = SRC_Skew_Decrease;
+      break;
+    case SST_Skew_Nochange:
+      result = SRC_Skew_Nochange;
+      break;
+    case SST_Skew_Increase:
+      result = SRC_Skew_Increase;
+      break;
+  }
+
+  return result;
+}
+
+/* ================================================== */
diff --git a/sources.h b/sources.h
new file mode 100644 (file)
index 0000000..c895cac
--- /dev/null
+++ b/sources.h
@@ -0,0 +1,155 @@
+/*
+  $Header: /cvs/src/chrony/sources.h,v 1.15 2002/02/28 23:27:14 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This is the header for the module that manages the collection of all
+  sources that we are making measurements from.  This include all NTP
+  servers & peers, locally connected reference sources, eye/wristwatch
+  drivers etc */
+
+#ifndef GOT_SOURCES_H
+#define GOT_SOURCES_H
+
+#include "sysincl.h"
+
+#include "ntp.h"
+#include "reports.h"
+
+/* This datatype is used to hold information about sources.  The
+   instance must be passed when calling many of the interface
+   functions */
+
+typedef struct SRC_Instance_Record *SRC_Instance;
+
+/* Initialisation function */
+extern void SRC_Initialise(void);
+
+/* Finalisation function */
+extern void SRC_Finalise(void);
+
+/* Function to create a new instance.  This would be called by one of
+   the individual source-type instance creation routines. */
+
+extern SRC_Instance SRC_CreateNewInstance(unsigned long ref_id);
+
+/* Function to get rid of a source when it is being unconfigured.
+   This may cause the current reference source to be reselected, if this
+   was the reference source or contributed significantly to a
+   falseticker decision. */
+
+extern void SRC_DestroyInstance(SRC_Instance instance);
+
+
+/* Function to get the range of frequencies, relative to the given
+   source, that we believe the local clock lies within.  The return
+   values are in terms of the number of seconds fast (+ve) or slow
+   (-ve) relative to the source that the local clock becomes after a
+   given amount of local time has elapsed.
+
+   Suppose the initial offset relative to the source is U (fast +ve,
+   slow -ve) and a time interval T elapses measured in terms of the
+   local clock.  Then the error relative to the source at the end of
+   the interval should lie in the interval [U+T*lo, U+T*hi]. */
+
+extern void SRC_GetFrequencyRange(SRC_Instance instance, double *lo, double *hi);
+
+/* This function is called by one of the source drivers when it has
+   a new sample that is to be accumulated.
+
+   This function causes the frequency estimation to be re-run for the
+   designated source, and the clock selection procedure to be re-run
+   afterwards.
+
+   sample_time is the local time at which the sample is to be
+   considered to have been made, in terms of doing a regression fit of
+   offset against local time.
+
+   offset is the offset at the time, in seconds.  Positive indicates
+   that the local clock is SLOW relative to the source, negative
+   indicates that the local clock is FAST relative to it.
+
+   root_delay and root_dispersion are in seconds, and are as per
+   RFC1305.  root_dispersion only includes the peer's root dispersion
+   + local sampling precision + skew dispersion accrued during the
+   measurement.  It is the job of the source statistics algorithms +
+   track.c to add on the extra dispersion due to the residual standard
+   deviation of the offsets from this source after regression, to form
+   the root_dispersion field in the packets transmitted to clients or
+   peers.
+
+   stratum is the stratum of the source that supplied the sample.
+
+   */
+
+extern void SRC_AccumulateSample(SRC_Instance instance, struct timeval *sample_time, double offset, double peer_delay, double peer_dispersion, double root_delay, double root_dispersion, int stratum, NTP_Leap leap_status);
+
+/* This routine indicates that packets with valid headers are being
+   received from the designated source */
+extern void SRC_SetReachable(SRC_Instance instance);
+
+/* This routine indicates that we are no longer receiving packets with
+   valid headers from the designated source */
+extern void SRC_UnsetReachable(SRC_Instance instance);
+
+/* This routine is used to select the best source from amongst those
+   we currently have valid data on, and use it as the tracking base
+   for the local time.  If match_addr is zero it means we must start
+   tracking the (newly) selected reference unconditionally, otherwise
+   it is equal to the address we should track if it turns out to be
+   the best reference.  (This avoids updating the frequency tracking
+   for every sample from other sources - only the ones from the
+   selected reference make a difference) */
+extern void SRC_SelectSource(unsigned long match_addr);
+
+/* Predict the offset of the local clock relative to a given source at
+   a given local cooked time. Positive indicates local clock is FAST
+   relative to reference. */
+extern double SRC_PredictOffset(SRC_Instance inst, struct timeval *when);
+
+/* Return the minimum peer delay amongst the previous samples
+   currently held in the register */
+extern double SRC_MinRoundTripDelay(SRC_Instance inst);
+
+extern void SRC_DumpSources(void);
+
+extern void SRC_ReloadSources(void);
+
+extern int SRC_IsSyncPeer(SRC_Instance inst);
+extern int SRC_ReadNumberOfSources(void);
+extern int SRC_ReportSource(int index, RPT_SourceReport *report, struct timeval *now);
+
+extern int SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report);
+
+typedef enum {
+  SRC_Skew_Decrease,
+  SRC_Skew_Nochange,
+  SRC_Skew_Increase
+} SRC_Skew_Direction;
+
+extern SRC_Skew_Direction SRC_LastSkewChange(SRC_Instance inst);
+
+#endif /* GOT_SOURCES_H */
+
diff --git a/sourcestats.c b/sourcestats.c
new file mode 100644 (file)
index 0000000..1cedbb5
--- /dev/null
@@ -0,0 +1,931 @@
+/*
+  $Header: /cvs/src/chrony/sourcestats.c,v 1.39 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This file contains the routines that do the statistical
+  analysis on the samples obtained from the sources,
+  to determined frequencies and error bounds. */
+
+#include "sysincl.h"
+
+#include "sourcestats.h"
+#include "memory.h"
+#include "regress.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "local.h"
+#include "mkdirpp.h"
+
+/* ================================================== */
+/* Define the maxumum number of samples that we want
+   to store per source */
+#define MAX_SAMPLES 64
+
+/* This is the assumed worst case bound on an unknown frequency,
+   2000ppm, which would be pretty bad */
+#define WORST_CASE_FREQ_BOUND (2000.0/1.0e6)
+
+/* Day number of 1 Jan 1970 */
+#define MJD_1970 40587
+
+/* ================================================== */
+/* File to which statistics are logged, NULL if none */
+static FILE *logfile = NULL;
+static char *logfilename = NULL;
+static unsigned long logwrites = 0;
+
+#define STATISTICS_LOG "statistics.log"
+
+/* ================================================== */
+/* This data structure is used to hold the history of data from the
+   source */
+
+struct SST_Stats_Record {
+
+  /* Reference ID of source, used for logging to statistics log */
+  unsigned long refid;
+
+  /* Number of samples currently stored.  sample[n_samples-1] is the
+     newest.  The samples are expected to be sorted in order, but that
+     probably doesn't matter.  */
+  int n_samples;
+
+  /* The index in the registers of the best individual sample that we
+     are holding, in terms of the minimum root distance at the present
+     time */
+  int best_single_sample;
+
+  /* This is the estimated offset (+ve => local fast) at a particular time */
+  double estimated_offset;
+  double estimated_offset_sd;
+  struct timeval offset_time;
+
+  /* Number of runs of the same sign amongst the residuals */
+  int nruns;
+
+  /* This value contains the estimated frequency.  This is the number
+     of seconds that the local clock gains relative to the reference
+     source per unit local time.  (Positive => local clock fast,
+     negative => local clock slow) */
+  double estimated_frequency;
+
+  /* This is the assumed worst case bounds on the estimated frequency.
+     We assume that the true frequency lies within +/- half this much
+     about estimated_frequency */
+  double skew;
+
+  /* This is the direction the skew went in at the last sample */
+  SST_Skew_Direction skew_dirn;
+
+  /* This is the estimated residual variance of the data points */
+  double variance;
+
+  /* This array contains the sample epochs, in terms of the local
+     clock. */
+  struct timeval sample_times[MAX_SAMPLES];
+
+  /* This is an array of offsets, in seconds, corresponding to the
+     sample times.  In this module, we use the convention that
+     positive means the local clock is FAST of the source and negative
+     means it is SLOW.  This is contrary to the convention in the NTP
+     stuff; that part of the code is written to correspond with
+     RFC1305 conventions. */
+  double offsets[MAX_SAMPLES];
+
+  /* This is an array of the offsets as originally measured.  Local
+     clock fast of real time is indicated by positive values.  This
+     array is not slewed to adjust the readings when we apply
+     adjustments to the local clock, as is done for the array
+     'offset'. */
+  double orig_offsets[MAX_SAMPLES];
+
+  /* This is an array of peer delays, in seconds, being the roundtrip
+     measurement delay to the peer */
+  double peer_delays[MAX_SAMPLES];
+
+  /* This is an array of peer dispersions, being the skew and local
+     precision dispersion terms from sampling the peer */
+  double peer_dispersions[MAX_SAMPLES];
+
+  /* This array contains the root delays of each sample, in seconds */
+  double root_delays[MAX_SAMPLES];
+
+  /* This array contains the root dispersions of each sample at the
+     time of the measurements */
+  double root_dispersions[MAX_SAMPLES];
+
+  /* This array contains the weights to be used in the regression
+     analysis for each of the samples. */
+  double weights[MAX_SAMPLES];
+
+  /* This array contains the strata that were associated with the sources
+     at the times the samples were generated */
+  int strata[MAX_SAMPLES];
+
+};
+
+/* ================================================== */
+
+void
+SST_Initialise(void)
+{
+  char *direc;
+
+  if (CNF_GetLogStatistics()) {
+    direc = CNF_GetLogDir();
+    if (!mkdir_and_parents(direc)) {
+      LOG(LOGS_ERR, LOGF_SourceStats, "Could not create directory %s", direc);
+      logfile = NULL;
+    } else {
+      logfilename = MallocArray(char, 2 + strlen(direc) + strlen(STATISTICS_LOG));
+      strcpy(logfilename, direc);
+      strcat(logfilename, "/");
+      strcat(logfilename, STATISTICS_LOG);
+      logfile = fopen(logfilename, "a");
+      if (!logfile) {
+        LOG(LOGS_WARN, LOGF_SourceStats, "Couldn't open logfile %s for update", logfilename);
+      }
+    }
+  }
+}
+
+/* ================================================== */
+
+void
+SST_Finalise(void)
+{
+  if (logfile) {
+    fclose(logfile);
+  }
+}
+
+/* ================================================== */
+/* This function creates a new instance of the statistics handler */
+
+SST_Stats
+SST_CreateInstance(unsigned long refid)
+{
+  SST_Stats inst;
+  inst = MallocNew(struct SST_Stats_Record);
+  inst->refid = refid;
+  inst->n_samples = 0;
+  inst->estimated_frequency = 0;
+  inst->skew = 2000.0e-6;
+  inst->skew_dirn = SST_Skew_Nochange;
+  inst->estimated_offset = 0.0;
+  inst->estimated_offset_sd = 86400.0; /* Assume it's at least within a day! */
+  inst->variance = 16.0;
+  inst->nruns = 0;
+  return inst;
+}
+
+/* ================================================== */
+/* This function deletes an instance of the statistics handler. */
+
+void
+SST_DeleteInstance(SST_Stats inst)
+{
+  Free(inst);
+  return;
+}
+
+/* ================================================== */
+
+static void
+move_stats_entry(SST_Stats inst, int src, int dest)
+{
+  inst->sample_times[dest] = inst->sample_times[src];
+  inst->offsets[dest] = inst->offsets[src];
+  inst->orig_offsets[dest] = inst->orig_offsets[src];
+  inst->peer_delays[dest] = inst->peer_delays[src];
+  inst->peer_dispersions[dest] = inst->peer_dispersions[src];
+  inst->root_delays[dest] = inst->root_delays[src];
+  inst->root_dispersions[dest] = inst->root_dispersions[src];
+  inst->weights[dest] = inst->weights[src];
+  inst->strata[dest] = inst->strata[src];
+}
+
+/* ================================================== */
+/* This function is called to prune the register down when it is full.
+   For now, just discard the oldest sample.  */
+
+static void
+prune_register(SST_Stats inst, int new_oldest, int *bad_points)
+{
+  int i, j;
+
+  if (!(new_oldest < inst->n_samples)) {
+    CROAK("new_oldest should be < n_samples");
+  }
+   
+  for (i=0, j=new_oldest; j<inst->n_samples; j++) {
+    if (!bad_points || !bad_points[j]) {
+      if (j != i) {
+        move_stats_entry(inst, j, i);
+      }
+      i++;
+    }
+  }
+  inst->n_samples = i;
+
+}
+
+/* ================================================== */
+
+void
+SST_AccumulateSample(SST_Stats inst, struct timeval *sample_time,
+                     double offset,
+                     double peer_delay, double peer_dispersion,
+                     double root_delay, double root_dispersion,
+                     int stratum)
+{
+  int n;
+#if 0
+  double root_distance;
+#endif
+
+  if (inst->n_samples == MAX_SAMPLES) {
+    prune_register(inst, 1, NULL);
+  }
+
+  n = inst->n_samples;
+
+  inst->sample_times[n] = *sample_time;
+  inst->offsets[n] = offset;
+  inst->orig_offsets[n] = offset;
+  inst->peer_delays[n] = peer_delay;
+  inst->peer_dispersions[n] = peer_dispersion;
+  inst->root_delays[n] = root_delay;
+  inst->root_dispersions[n] = root_dispersion;
+
+#if 0
+  /* The weight is worked out when we run the regression algorithm */
+  root_distance = root_dispersion + 0.5 * fabs(root_delay);
+  
+  /* For now, this is the formula for the weight functions */
+  inst->weights[n] = root_distance * root_distance;
+#endif
+
+  inst->strata[n] = stratum;
+  ++inst->n_samples;
+
+}
+
+/* ================================================== */
+/* This function is used by both the regression routines to find the
+   time interval between each historical sample and the most recent
+   one */
+
+static void
+convert_to_intervals(SST_Stats inst, double *times_back)
+{
+  struct timeval *newest_tv;
+  int i;
+
+  newest_tv = &(inst->sample_times[inst->n_samples - 1]);
+  for (i=0; i<inst->n_samples; i++) {
+    /* The entries in times_back[] should end up negative */
+    UTI_DiffTimevalsToDouble(&(times_back[i]), &(inst->sample_times[i]), newest_tv);
+  }
+}
+
+/* ================================================== */
+
+static void
+find_best_sample_index(SST_Stats inst, double *times_back)
+{
+  /* With the value of skew that has been computed, see which of the
+     samples offers the tightest bound on root distance */
+
+  double root_distance, best_root_distance;
+  double elapsed;
+  int i, n, best_index;
+
+  n = inst->n_samples - 1;
+  best_root_distance = inst->root_dispersions[n] + 0.5 * fabs(inst->root_delays[n]);
+  best_index = n;
+#if 0
+  LOG(LOGS_INFO, LOGF_SourceStats, "n=%d brd=%f", n, best_root_distance);
+#endif
+  for (i=0; i<n; i++) {
+    elapsed = -times_back[i];
+#if 0
+    LOG(LOGS_INFO, LOGF_SourceStats, "n=%d i=%d latest=[%s] doing=[%s] elapsed=%f", n, i, 
+        UTI_TimevalToString(&(inst->sample_times[n])),
+        UTI_TimevalToString(&(inst->sample_times[i])),
+        elapsed);
+#endif
+
+    /* Because the loop does not consider the most recent sample, this assertion must hold */
+    if (elapsed <= 0.0) {
+      LOG(LOGS_ERR, LOGF_SourceStats, "Elapsed<0! n=%d i=%d latest=[%s] doing=[%s] elapsed=%f",
+          n, i, 
+          UTI_TimevalToString(&(inst->sample_times[n])),
+          UTI_TimevalToString(&(inst->sample_times[i])),
+          elapsed);
+
+      elapsed = fabs(elapsed);
+    }
+
+    root_distance = inst->root_dispersions[i] + elapsed * inst->skew + 0.5 * fabs(inst->root_delays[i]);
+    if (root_distance < best_root_distance) {
+      best_root_distance = root_distance;
+      best_index = i;
+    }
+  }
+  
+  inst->best_single_sample = best_index;
+
+#if 0
+  LOG(LOGS_INFO, LOGF_SourceStats, "n=%d best_index=%d", n, best_index);
+#endif
+
+  return;
+}
+
+/* ================================================== */
+
+/* This defines the assumed ratio between the standard deviation of
+   the samples and the peer distance as measured from the round trip
+   time.  E.g. a value of 4 means that we think the standard deviation
+   is a quarter of the peer distance */
+
+#define SD_TO_DIST_RATIO 8.0
+
+/* ================================================== */
+/* This function runs the linear regression operation on the data.  It
+   finds the set of most recent samples that give the tightest
+   confidence interval for the frequency, and truncates the register
+   down to that number of samples */
+
+void
+SST_DoNewRegression(SST_Stats inst)
+{
+  double times_back[MAX_SAMPLES];
+  double peer_distances[MAX_SAMPLES];
+
+  int bad_points[MAX_SAMPLES];
+  int degrees_of_freedom;
+  int best_start;
+  double est_intercept, est_slope, est_var, est_intercept_sd, est_slope_sd;
+  int i, nruns;
+  double min_distance;
+  double sd_weight;
+  double old_skew, old_freq, stress;
+
+  int regression_ok;
+
+  convert_to_intervals(inst, times_back);
+
+  if (inst->n_samples > 0) {
+    for (i=0; i<inst->n_samples; i++) {
+      peer_distances[i] = 0.5 * fabs(inst->peer_delays[i]) + inst->peer_dispersions[i];
+    }
+  
+    min_distance = peer_distances[0];
+    for (i=1; i<inst->n_samples; i++) {
+      if (peer_distances[i] < min_distance) {
+        min_distance = peer_distances[i];
+      }
+    }
+
+    /* And now, work out the weight vector */
+
+    for (i=0; i<inst->n_samples; i++) {
+      sd_weight = 1.0 + SD_TO_DIST_RATIO * (peer_distances[i] - min_distance) / min_distance;
+      inst->weights[i] = sd_weight * sd_weight;
+    } 
+  }         
+
+  regression_ok = RGR_FindBestRegression(times_back, inst->offsets, inst->weights,
+                                         inst->n_samples,
+                                         &est_intercept, &est_slope, &est_var,
+                                         &est_intercept_sd, &est_slope_sd,
+                                         &best_start, &nruns, &degrees_of_freedom);
+
+  /* This is a legacy of when the regression routine found outliers
+     for us.  We don't use it anymore. */
+  memset((void *) bad_points, 0, MAX_SAMPLES * sizeof(int));
+
+  if (regression_ok) {
+
+    old_skew = inst->skew;
+    old_freq = inst->estimated_frequency;
+  
+    inst->estimated_frequency = est_slope;
+    inst->skew = est_slope_sd * RGR_GetTCoef(degrees_of_freedom);
+    inst->estimated_offset = est_intercept;
+    inst->offset_time = inst->sample_times[inst->n_samples - 1];
+    inst->estimated_offset_sd = est_intercept_sd;
+    inst->variance = est_var;
+    inst->nruns = nruns;
+
+    stress = fabs(old_freq - inst->estimated_frequency) / old_skew;
+
+    if (best_start > 0) {
+      /* If we are throwing old data away, retain the current
+         assumptions about the skew */
+      inst->skew_dirn = SST_Skew_Nochange;
+    } else {
+      if (inst->skew < old_skew) {
+        inst->skew_dirn = SST_Skew_Decrease;
+      } else {
+        inst->skew_dirn = SST_Skew_Increase;
+      }
+    }
+
+    if (logfile) {
+
+      if (((logwrites++) % 32) == 0) {
+        fprintf(logfile,
+                "==============================================================================================================\n"
+                "   Date (UTC) Time     IP Address    Std dev'n Est offset  Offset sd  Diff freq   Est skew  Stress  Ns  Bs  Nr\n"
+                "==============================================================================================================\n");
+      }
+      
+            
+      fprintf(logfile, "%s %-15s %10.3e %10.3e %10.3e %10.3e %10.3e %7.1e %3d %3d %3d\n",
+              UTI_TimeToLogForm(inst->offset_time.tv_sec),
+              UTI_IPToDottedQuad(inst->refid),
+              sqrt(inst->variance),
+              inst->estimated_offset,
+              inst->estimated_offset_sd,
+              inst->estimated_frequency,
+              inst->skew,
+              stress,
+              inst->n_samples,
+              best_start, nruns);
+
+      fflush(logfile);
+    }
+
+    prune_register(inst, best_start, bad_points);
+
+  } else {
+#if 0
+    LOG(LOGS_INFO, LOGF_SourceStats, "too few points (%d) for regression", inst->n_samples);
+#endif
+    inst->estimated_frequency = 0.0;
+    inst->skew = WORST_CASE_FREQ_BOUND;
+  }
+
+  find_best_sample_index(inst, times_back);
+
+}
+
+/* ================================================== */
+/* This function does a simple regression on what is in the register,
+   without trying to optimise the error bounds on the frequency by
+   deleting old samples */
+
+void
+SST_DoUpdateRegression(SST_Stats inst)
+{
+  double times_back[MAX_SAMPLES];
+  double freq_error_bound;
+  double est_intercept, est_slope, est_var_base, est_intercept_sd, est_slope_sd;
+
+  convert_to_intervals(inst, times_back);
+
+  if (inst->n_samples >= 3) { /* Otherwise, we're wasting our time - we
+                                 can't do a useful linear regression
+                                 with less than 3 points */
+    
+    RGR_WeightedRegression(times_back, inst->offsets, inst->weights,
+                           inst->n_samples,
+                           &est_intercept, &est_slope, &est_var_base,
+                           &est_intercept_sd, &est_slope_sd);
+
+    freq_error_bound = est_slope_sd * RGR_GetTCoef(inst->n_samples - 2);
+
+    inst->estimated_frequency = est_slope;
+    inst->skew = freq_error_bound;
+
+  } else {
+    inst->estimated_frequency = 0.0;
+    inst->skew = WORST_CASE_FREQ_BOUND;
+  }
+
+  find_best_sample_index(inst, times_back);
+
+}
+
+/* ================================================== */
+
+void
+SST_GetReferenceData(SST_Stats inst, struct timeval *now, 
+                     int *stratum, double *offset,
+                     double *root_delay, double *root_dispersion,
+                     double *frequency, double *skew)
+{
+
+  double elapsed;
+  int n;
+
+  *frequency = inst->estimated_frequency;
+  *skew = inst->skew;
+
+  n = inst->best_single_sample;
+
+  UTI_DiffTimevalsToDouble(&elapsed, now, &(inst->sample_times[n]));
+  *root_delay = inst->root_delays[n];
+  *root_dispersion = inst->root_dispersions[n] + elapsed * inst->skew;
+  *offset = inst->offsets[n] + elapsed * inst->estimated_frequency;
+  *stratum = inst->strata[n];
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_SourceStats, "n=%d freq=%f skew=%f del=%f disp=%f ofs=%f str=%d",
+      n, *frequency, *skew, *root_delay, *root_dispersion, *offset, *stratum);
+#endif
+
+  return;
+}
+
+/* ================================================== */
+/* Return the assumed worst case range of values that this source's
+   frequency lies within.  Frequency is defined as the amount of time
+   the local clock gains relative to the source per unit local clock
+   time. */
+void
+SST_GetFrequencyRange(SST_Stats inst,
+                      double *lo, double *hi)
+{
+  double freq, skew;
+  freq = inst->estimated_frequency;
+  skew = inst->skew;
+  *lo = freq - skew;
+  *hi = freq + skew;
+}
+
+/* ================================================== */
+
+/* ================================================== */
+
+void
+SST_GetSelectionData(SST_Stats inst, struct timeval *now,
+                     int *stratum,
+                     double *best_offset, double *best_root_delay,
+                     double *best_root_dispersion,
+                     double *variance, int *average_ok)
+{
+  double average_offset;
+  double sample_elapsed;
+  double elapsed;
+  int n;
+  double peer_distance;
+  
+  n = inst->best_single_sample;
+  *stratum = inst->strata[n];
+  *variance = inst->variance;
+
+  peer_distance = inst->peer_dispersions[n] + 0.5 * fabs(inst->peer_delays[n]);
+  UTI_DiffTimevalsToDouble(&elapsed, now, &(inst->offset_time));
+
+  UTI_DiffTimevalsToDouble(&sample_elapsed, now, &(inst->sample_times[n]));
+  *best_offset = inst->offsets[n] + sample_elapsed * inst->estimated_frequency;
+  *best_root_delay = inst->root_delays[n];
+  *best_root_dispersion = inst->root_dispersions[n] + sample_elapsed * inst->skew;
+
+  average_offset = inst->estimated_offset + inst->estimated_frequency * elapsed;
+  if (fabs(average_offset - *best_offset) <= peer_distance) {
+    *average_ok = 1;
+  } else {
+    *average_ok = 0;
+  }
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_SourceStats, "n=%d off=%f del=%f dis=%f var=%f pdist=%f avoff=%f avok=%d",
+      n, *best_offset, *best_root_delay, *best_root_dispersion, *variance,
+      peer_distance, average_offset, *average_ok);
+#endif
+
+  return;
+}
+
+/* ================================================== */
+
+void
+SST_GetTrackingData(SST_Stats inst, struct timeval *now,
+                    double *average_offset, double *offset_sd,
+                    double *accrued_dispersion,
+                    double *frequency, double *skew)
+{
+  int n;
+  double peer_distance;
+  double elapsed_offset, elapsed_sample;
+
+  n = inst->best_single_sample;
+
+  *frequency = inst->estimated_frequency;
+  *skew = inst->skew;
+
+  peer_distance = inst->peer_dispersions[n] + 0.5 * fabs(inst->peer_delays[n]);
+  UTI_DiffTimevalsToDouble(&elapsed_offset, now, &(inst->offset_time));
+  *average_offset = inst->estimated_offset + inst->estimated_frequency * elapsed_offset;
+  *offset_sd = inst->estimated_offset_sd + elapsed_offset * inst->skew;
+
+  UTI_DiffTimevalsToDouble(&elapsed_sample, now, &(inst->sample_times[n]));
+  *accrued_dispersion = inst->skew * elapsed_sample;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_SourceStats, "n=%d freq=%f (%.3fppm) skew=%f (%.3fppm) pdist=%f avoff=%f offsd=%f accrdis=%f",
+      n, *frequency, 1.0e6* *frequency, *skew, 1.0e6* *skew, peer_distance, *average_offset, *offset_sd, *accrued_dispersion);
+#endif
+
+}
+
+/* ================================================== */
+
+void
+SST_SlewSamples(SST_Stats inst, struct timeval *when, double dfreq, double doffset)
+{
+  int n, i;
+  double elapsed;
+  double delta_time;
+  struct timeval *sample, prev;
+  double prev_offset, prev_freq;
+
+  n = inst->n_samples;
+
+  for (i=0; i<n; i++) {
+    sample = &(inst->sample_times[i]);
+    prev = *sample;
+#if 0
+    UTI_AdjustTimeval(sample, when, sample, dfreq, doffset);
+    /* Can't easily use this because we need to slew offset */
+#endif
+    UTI_DiffTimevalsToDouble(&elapsed, when, sample);
+    delta_time = elapsed * dfreq - doffset;
+    UTI_AddDoubleToTimeval(sample, delta_time, sample);
+    prev_offset = inst->offsets[i];
+    inst->offsets[i] += delta_time;
+#ifdef TRACEON
+    LOG(LOGS_INFO, LOGF_SourceStats, "i=%d old_st=[%s] new_st=[%s] old_off=%f new_off=%f",
+        i, UTI_TimevalToString(&prev), UTI_TimevalToString(sample),
+        prev_offset, inst->offsets[i]);
+#endif
+  }
+
+  /* Do a half-baked update to the regression estimates */
+  UTI_DiffTimevalsToDouble(&elapsed, when, &(inst->offset_time));
+  prev = inst->offset_time;
+  delta_time = elapsed * dfreq - doffset;
+  UTI_AddDoubleToTimeval(&(inst->offset_time), delta_time, &(inst->offset_time));
+  prev_offset = inst->estimated_offset;
+  prev_freq = inst->estimated_frequency;
+  inst->estimated_offset += delta_time;
+  inst->estimated_frequency -= dfreq;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_SourceStats, "old_off_time=[%s] new=[%s] old_off=%f new_off=%f old_freq=%.3fppm new_freq=%.3fppm",
+      UTI_TimevalToString(&prev), UTI_TimevalToString(&(inst->offset_time)),
+      prev_offset, inst->estimated_offset,
+      1.0e6*prev_freq, 1.0e6*inst->estimated_frequency);
+#endif
+
+  return;
+}
+
+/* ================================================== */
+
+double
+SST_PredictOffset(SST_Stats inst, struct timeval *when)
+{
+  double elapsed;
+  
+  if (inst->n_samples < 3) {
+    /* We don't have any useful statistics, and presumably the poll
+       interval is minimal.  We can't do any useful prediction other
+       than use the latest sample */
+    return inst->offsets[inst->n_samples - 1];
+  } else {
+    UTI_DiffTimevalsToDouble(&elapsed, when, &inst->offset_time);
+    return inst->estimated_offset + elapsed * inst->estimated_frequency;
+  }
+
+}
+
+/* ================================================== */
+
+double
+SST_MinRoundTripDelay(SST_Stats inst)
+{
+  double min_delay, delay;
+  int i;
+
+  if (inst->n_samples == 0) {
+    return DBL_MAX;
+  } else {
+    min_delay = fabs(inst->peer_delays[0]);
+    for (i=1; i<inst->n_samples; i++) {
+      delay = fabs(inst->peer_delays[i]);
+      if (delay < min_delay) {
+        min_delay = delay;
+      }
+    }
+    return min_delay;
+  }
+}
+
+/* ================================================== */
+/* This is used to save the register to a file, so that we can reload
+   it after restarting the daemon */
+
+void
+SST_SaveToFile(SST_Stats inst, FILE *out)
+{
+  int i;
+
+  fprintf(out, "%d\n", inst->n_samples);
+
+  for(i=0; i<inst->n_samples; i++) {
+
+    fprintf(out, "%08lx %08lx %.6e %.6e %.6e %.6e %.6e %.6e %.6e %d\n",
+            (unsigned long) inst->sample_times[i].tv_sec,
+            (unsigned long) inst->sample_times[i].tv_usec,
+            inst->offsets[i],
+            inst->orig_offsets[i],
+            inst->peer_delays[i],
+            inst->peer_dispersions[i],
+            inst->root_delays[i],
+            inst->root_dispersions[i],
+            inst->weights[i],
+            inst->strata[i]);
+
+  }
+}
+
+/* ================================================== */
+/* This is used to reload samples from a file */
+
+int
+SST_LoadFromFile(SST_Stats inst, FILE *in)
+{
+  int i, line_number;
+  char line[1024];
+  unsigned long sec, usec;
+
+  if (fgets(line, sizeof(line), in) &&
+      (sscanf(line, "%d", &inst->n_samples) == 1)) {
+
+    line_number = 2;
+
+    for (i=0; i<inst->n_samples; i++) {
+      if (!fgets(line, sizeof(line), in) ||
+          (sscanf(line, "%lx%lx%lf%lf%lf%lf%lf%lf%lf%d\n",
+                  &(sec), &(usec),
+                  &(inst->offsets[i]),
+                  &(inst->orig_offsets[i]),
+                  &(inst->peer_delays[i]),
+                  &(inst->peer_dispersions[i]),
+                  &(inst->root_delays[i]),
+                  &(inst->root_dispersions[i]),
+                  &(inst->weights[i]),
+                  &(inst->strata[i])) != 10)) {
+
+        /* This is the branch taken if the read FAILED */
+
+        LOG(LOGS_WARN, LOGF_SourceStats, "Failed to read data from line %d of dump file", line_number);
+        inst->n_samples = 0; /* Load abandoned if any sign of corruption */
+        return 0;
+      } else {
+
+        /* This is the branch taken if the read is SUCCESSFUL */
+        inst->sample_times[i].tv_sec = sec;
+        inst->sample_times[i].tv_usec = usec;
+
+        line_number++;
+      }
+    }
+  } else {
+    LOG(LOGS_WARN, LOGF_SourceStats, "Could not read number of samples from dump file");
+    inst->n_samples = 0; /* Load abandoned if any sign of corruption */
+    return 0;
+  }
+
+  return 1;
+
+}
+
+/* ================================================== */
+
+void
+SST_DoSourceReport(SST_Stats inst, RPT_SourceReport *report, struct timeval *now)
+{
+  int n, nb;
+  double est_offset, est_err, elapsed, sample_elapsed;
+  struct timeval ago;
+
+  if (inst->n_samples > 0) {
+    n = inst->n_samples - 1;
+    report->orig_latest_meas = (long)(0.5 + 1.0e6 * inst->orig_offsets[n]);
+    report->latest_meas = (long)(0.5 + 1.0e6 * inst->offsets[n]);
+    report->latest_meas_err = (unsigned long)(0.5 + 1.0e6 * (0.5*inst->root_delays[n] + inst->root_dispersions[n]));
+    report->stratum = inst->strata[n];
+
+    UTI_DiffTimevals(&ago, now, &inst->sample_times[n]);
+    report->latest_meas_ago = ago.tv_sec;
+
+    if (inst->n_samples > 3) {
+      UTI_DiffTimevalsToDouble(&elapsed, now, &inst->offset_time);
+      nb = inst->best_single_sample;
+      UTI_DiffTimevalsToDouble(&sample_elapsed, now, &(inst->sample_times[nb]));
+      est_offset = inst->estimated_offset + elapsed * inst->estimated_frequency;
+      est_err = (inst->estimated_offset_sd +
+                 sample_elapsed * inst->skew +
+                 (0.5*inst->root_delays[nb] + inst->root_dispersions[nb]));
+      report->est_offset = (long)(0.5 + 1.0e6 * est_offset);
+      report->est_offset_err = (unsigned long) (0.5 + 1.0e6 * est_err);
+      report->resid_freq = (long) (0.5 * 1.0e9 * inst->estimated_frequency);
+      report->resid_skew = (unsigned long) (0.5 + 1.0e9 * inst->skew);
+    } else {
+      report->est_offset = report->latest_meas;
+      report->est_offset_err = report->latest_meas_err;
+      report->resid_freq = 0;
+      report->resid_skew = 0;
+    }
+  } else {
+    report->orig_latest_meas = 0;
+    report->latest_meas = 0;
+    report->latest_meas_err = 0;
+    report->stratum = 0;
+    report->est_offset = 0;
+    report->est_offset_err = 0;
+    report->resid_freq = 0;
+    report->resid_skew = 0;
+  }
+}
+
+
+/* ================================================== */
+
+SST_Skew_Direction SST_LastSkewChange(SST_Stats inst)
+{
+  return inst->skew_dirn;
+}
+
+/* ================================================== */
+
+void
+SST_DoSourcestatsReport(SST_Stats inst, RPT_SourcestatsReport *report)
+{
+  double dspan;
+  int n;
+
+  report->n_samples = inst->n_samples;
+  report->n_runs = inst->nruns;
+
+  if (inst->n_samples > 1) {
+    n = inst->n_samples - 1;
+    UTI_DiffTimevalsToDouble(&dspan, &inst->sample_times[n], &inst->sample_times[0]);
+    report->span_seconds = (unsigned long) (dspan + 0.5);
+  } else {
+    report->span_seconds = 0;
+  }
+
+  report->resid_freq_ppm = 1.0e6 * inst->estimated_frequency;
+  report->skew_ppm = 1.0e6 * inst->skew;
+  report->sd_us = 1.0e6 * sqrt(inst->variance);
+}
+
+/* ================================================== */
+
+void
+SST_CycleLogFile(void)
+{
+  if (logfile && logfilename) {
+    fclose(logfile);
+    logfile = fopen(logfilename, "a");
+    if (!logfile) {
+      LOG(LOGS_WARN, LOGF_SourceStats, "Could not reopen logfile %s", logfilename);
+    }
+    logwrites = 0;
+  }
+}
+
+/* ================================================== */
diff --git a/sourcestats.h b/sourcestats.h
new file mode 100644 (file)
index 0000000..e335e2d
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+  $Header: /cvs/src/chrony/sourcestats.h,v 1.13 2002/02/28 23:27:14 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for module that deals with the measurements and statistics of
+  each of the sources. */
+
+#ifndef GOT_SOURCESTATS_H
+#define GOT_SOURCESTATS_H
+
+#include "sysincl.h"
+
+#include "reports.h"
+
+typedef struct SST_Stats_Record *SST_Stats;
+
+/* Init and fini functions */
+extern void SST_Initialise(void);
+extern void SST_Finalise(void);
+
+/* This function creates a new instance of the statistics handler */
+extern SST_Stats SST_CreateInstance(unsigned long refid);
+
+/* This function deletes an instance of the statistics handler. */
+extern void SST_DeleteInstance(SST_Stats inst);
+
+/* This function accumulates a single sample into the statistics handler
+
+   sample_time is the epoch at which the sample is to be considered to
+   have been made.
+
+   offset is the offset of the local clock relative to the source in
+   seconds.  Positive indicates that the local clock if FAST (contrary
+   to the NTP parts of the software)
+
+   root_distance is the Lambda+Delta/2 term in RFC1305, but excluding
+   the extra dispersion due to the residual standard deviation after
+   we have done the regression fit.
+
+   stratum is the stratum of the source from which the sample came.
+  */
+
+extern void SST_AccumulateSample(SST_Stats inst, struct timeval *sample_time, double offset, double peer_delay, double peer_dispersion, double root_delay, double root_dispersion, int stratum);
+
+/* This function runs the linear regression operation on the data.  It
+   finds the set of most recent samples that give the tightest
+   confidence interval for the frequency, and truncates the register
+   down to that number of samples. */
+extern void SST_DoNewRegression(SST_Stats inst);
+
+/* This function does a simple regression on what is in the register,
+   without trying to optimise the error bounds on the frequency by
+   deleting old samples */
+extern void SST_DoUpdateRegression(SST_Stats inst);
+
+/* Return the assumed worst case range of values that this source's
+   frequency lies within.  Frequency is defined as the amount of time
+   the local clock gains relative to the source per unit local clock
+   time. */
+extern void SST_GetFrequencyRange(SST_Stats inst, double *lo, double *hi);
+
+/* Get data needed for selection */
+extern void
+SST_GetSelectionData(SST_Stats inst, struct timeval *now,
+                     int *stratum,
+                     double *best_offset, double *best_root_delay,
+                     double *best_root_dispersion,
+                     double *variance,
+                     int *average_ok);
+
+/* Get data needed when setting up tracking on this source */
+extern void
+SST_GetTrackingData(SST_Stats inst, struct timeval *now,
+                    double *average_offset, double *offset_sd,
+                    double *accrued_dispersion,
+                    double *frequency, double *skew);
+
+/* Get parameters for using this source as the reference */
+extern void
+SST_GetReferenceData(SST_Stats inst, struct timeval *now, 
+                     int *stratum, double *offset,
+                     double *root_delay, double *root_dispersion,
+                     double *frequency, double *skew);
+
+
+
+/* This routine is called when the local machine clock parameters are
+   changed.  It adjusts all existing samples that we are holding for
+   each peer so that it looks like they were made under the new clock
+   regime rather than the old one.
+
+   when = cooked local time when the change occurs
+
+   dfreq = delta frequency. positive means the clock has been adjusted
+   because it was previously gaining time relative to the external
+   reference(s).
+
+   doffset = offset slewed onto local clock.  positive => local clock
+   has been made fast by that amount.
+
+*/
+
+extern void SST_SlewSamples(SST_Stats inst, struct timeval *when, double dfreq, double doffset);
+
+
+/* Predict the offset of the local clock relative to a given source at
+   a given local cooked time. Positive indicates local clock is FAST
+   relative to reference. */
+extern double SST_PredictOffset(SST_Stats inst, struct timeval *when);
+
+/* Find the minimum round trip delay in the register */
+extern double SST_MinRoundTripDelay(SST_Stats inst);
+
+extern void SST_SaveToFile(SST_Stats inst, FILE *out);
+
+extern int SST_LoadFromFile(SST_Stats inst, FILE *in);
+
+extern void SST_DoSourceReport(SST_Stats inst, RPT_SourceReport *report, struct timeval *now);
+
+extern void SST_DoSourcestatsReport(SST_Stats inst, RPT_SourcestatsReport *report);
+
+typedef enum {
+  SST_Skew_Decrease,
+  SST_Skew_Nochange,
+  SST_Skew_Increase
+} SST_Skew_Direction;
+
+extern SST_Skew_Direction SST_LastSkewChange(SST_Stats inst);
+
+extern void SST_CycleLogFile(void);
+
+#endif /* GOT_SOURCESTATS_H */
+
diff --git a/srcparams.h b/srcparams.h
new file mode 100644 (file)
index 0000000..5d273b9
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+  $Header: /cvs/src/chrony/srcparams.h,v 1.10 2002/02/28 23:27:14 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file defining parameters that can be set on a per source basis
+  */
+
+#ifndef GOT_SRCPARAMS_H
+#define GOT_SRCPARAMS_H
+
+typedef struct {
+  int minpoll;
+  int maxpoll;
+  int online;
+  int auto_offline;
+  int presend_minpoll;
+  unsigned long authkey;
+  double max_delay;
+  double max_delay_ratio;
+} SourceParameters;
+
+#define INACTIVE_AUTHKEY 0UL
+
+#endif /* GOT_SRCPARAMS_H */
diff --git a/strerror.c b/strerror.c
new file mode 100644 (file)
index 0000000..85d14e9
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+  $Header: /cvs/src/chrony/strerror.c,v 1.8 2002/02/28 23:27:14 richard Exp $
+
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Replacement strerror function for systems that don't have it
+  */
+
+#ifdef SUNOS
+
+#include <errno.h>
+extern char *sys_errlist[];
+
+char *strerror(int n) {
+  return sys_errlist[n];
+}
+
+#endif /* SUNOS */
diff --git a/sys.c b/sys.c
new file mode 100644 (file)
index 0000000..9052cf7
--- /dev/null
+++ b/sys.c
@@ -0,0 +1,104 @@
+/*
+  $Header: /cvs/src/chrony/sys.c,v 1.11 2002/02/28 23:27:14 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This file contains all the conditionally compiled bits that pull
+  in the various operating-system specific modules
+  */
+
+#include "sys.h"
+
+#if defined (LINUX)
+#include "sys_linux.h"
+#endif
+
+#if defined (SOLARIS)
+#include "sys_solaris.h"
+#endif
+
+#if defined (SUNOS)
+#include "sys_sunos.h"
+#endif
+
+#if defined (__NetBSD__)
+#include "sys_netbsd.h"
+#endif
+
+/* ================================================== */
+
+void
+SYS_Initialise(void)
+{
+
+#if defined(LINUX)
+  SYS_Linux_Initialise();
+#endif
+
+#if defined(SOLARIS)
+  SYS_Solaris_Initialise();
+#endif
+
+#if defined(SUNOS)
+  SYS_SunOS_Initialise();
+#endif
+
+#if defined(__NetBSD__)
+  SYS_NetBSD_Initialise();
+#endif
+
+}
+
+/* ================================================== */
+
+void
+SYS_Finalise(void)
+{
+  
+#if defined(LINUX)
+  SYS_Linux_Finalise();
+#endif
+
+#if defined(SOLARIS)
+  SYS_Solaris_Finalise();
+#endif
+
+#if defined(SUNOS)
+  SYS_SunOS_Finalise();
+#endif
+
+#if defined(__NetBSD__)
+  SYS_NetBSD_Finalise();
+#endif
+
+  return;
+}
+
+/* ================================================== */
+/* ================================================== */
+/* ================================================== */
+
+
+
diff --git a/sys.h b/sys.h
new file mode 100644 (file)
index 0000000..973da42
--- /dev/null
+++ b/sys.h
@@ -0,0 +1,42 @@
+/*
+  $Header: /cvs/src/chrony/sys.h,v 1.7 2002/02/28 23:27:14 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This is the header for the file that links in the operating system-
+  specific parts of the software
+
+*/
+
+#ifndef GOT_SYS_H
+#define GOT_SYS_H
+
+/* Called at the start of the run to do initialisation */
+extern void SYS_Initialise(void);
+
+/* Called at the end of the run to do final clean-up */
+extern void SYS_Finalise(void);
+
+#endif /* GOT_SYS_H */
diff --git a/sys_linux.c b/sys_linux.c
new file mode 100644 (file)
index 0000000..3daa201
--- /dev/null
@@ -0,0 +1,850 @@
+/*
+  $Header: /cvs/src/chrony/sys_linux.c,v 1.41 2003/07/01 20:56:23 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This is the module specific to the Linux operating system.
+
+  */
+
+#ifdef LINUX
+
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <math.h>
+#include <ctype.h>
+#include <assert.h>
+#include <sys/utsname.h>
+
+#include "localp.h"
+#include "sys_linux.h"
+#include "sched.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "wrap_adjtimex.h"
+
+static long current_tick;
+
+/* This is the value of tick, in seconds, including the current vernier
+   frequency term */
+static double current_total_tick;
+
+/* This is the uncompensated system tick value */
+static int nominal_tick;
+
+/* This is the scaling required to go between absolute ppm and the
+   scaled ppm used as an argument to adjtimex.  Because chronyd is to an extent
+   'closed loop' maybe it doesn't matter if this is wrongly determined, UNLESS
+   the system's ppm error is close to a multiple of HZ, in which case the
+   relationship between changing the frequency and changing the value of 'tick'
+   will be wrong.  This would (I imagine) cause the system to thrash between
+   two states.
+   
+   However..., if this effect was not corrected, and the system is left offline
+   for a long period, a substantial error would build up.  e.g. with HZ==100,
+   the correction required is 128/128.125, giving a drift of about 84 seconds
+   per day). */
+static double freq_scale;
+
+/* The HZ value from the kernel header file (may be over-ridden from config
+   file, e.g. if chronyd binary is moved to a box whose kernel was built with a
+   different HZ value). */
+static int hz;
+static double dhz; /* And dbl prec version of same for arithmetic */
+
+
+/* ================================================== */
+
+/* The operating system kernel version */
+static int version_major;
+static int version_minor;
+static int version_patchlevel;
+
+/* Flag indicating whether adjtimex() with txc.modes equal to zero
+returns the remaining time adjustment or not.  If not we have to read
+the outstanding adjustment by setting it to zero, examining the return
+value and setting the outstanding adjustment back again. */
+
+static int have_readonly_adjtime;
+
+/* ================================================== */
+
+static void handle_end_of_slew(void *anything);
+
+/* ================================================== */
+
+inline static int
+round(double x) {
+  int y;
+  y = (int)(x + 0.5);
+  while ((double)y < x - 0.5) y++;
+  while ((double)y > x + 0.5) y--;
+  return y;
+}
+
+/* ================================================== */
+/* Amount of outstanding offset to process */
+static double offset_register;
+
+/* Flag set true if a fast slew (one done by altering tick) is being
+   run at the moment */
+static int fast_slewing;
+
+/* The amount by which the fast slew was supposed to slew the clock */
+static double fast_slew_wanted;
+
+/* The value programmed into the kernel's 'tick' variable whilst
+   slewing a large offset */
+static long slewing_tick;
+
+/* The timeval (raw) at which a fast slew was started.  We need to
+   know this for two reasons.  First, if we want to change the
+   frequency midway through, we will want to abort the slew and return
+   the unprocessed portion to the offset register to start again
+   later.  Second, when the end of the slew expires, we need to know
+   precisely how long we have been slewing for, so that we can negate
+   the excess and slew it back the other way. */
+static struct timeval slew_start_tv;
+
+/* This is the ID returned to use by the scheduler's timeout handler.
+   We need this if we subsequently wish to abort a slew, because we will have to
+   dequeue the timeout */
+static SCH_TimeoutID slew_timeout_id;
+
+/* The adjustment that we apply to 'tick', in seconds, whilst applying
+   a fast slew */
+static double delta_total_tick;
+
+/* Max amount of time that we wish to slew by using adjtime (or its
+   equivalent).  If more than this is outstanding, we alter the value
+   of tick instead, for a set period.  Set this according to the
+   amount of time that a dial-up clock might need to be shifted
+   assuming it is resync'ed about once per day. (TBC) */
+#define MAX_ADJUST_WITH_ADJTIME (0.2)
+
+/* The amount by which we alter 'tick' when doing a large slew */
+static int slew_delta_tick;
+
+/* The maximum amount by which 'tick' can be biased away from 'nominal_tick'
+   (sys_adjtimex() in the kernel bounds this to 10%) */
+static int max_tick_bias;
+
+/* ================================================== */
+/* This routine stops a fast slew, determines how long the slew has
+   been running for, and consequently how much adjustment has actually
+   been applied. It can be used both when a slew finishes naturally
+   due to a timeout, and when a slew is being aborted. */
+
+static void
+stop_fast_slew(void)
+{
+  struct timeval T1, T1d, T1a;
+  struct timezone tz;
+  double end_window;
+  double fast_slew_done;
+  double slew_duration;
+  double introduced_dispersion;
+
+  /* Should never get here unless this is true */
+  if (!fast_slewing) {
+    CROAK("Should be fast slewing");
+  }
+  
+  /* Now set the thing off */
+  if (gettimeofday(&T1, &tz) < 0) {
+    CROAK("gettimeofday() failed in stop_fast_slew");
+  }
+  
+  if (TMX_SetTick(current_tick) < 0) {
+    CROAK("adjtimex() failed in stop_fast_slew");
+  }
+  
+  if (gettimeofday(&T1d, &tz) < 0) {
+    CROAK("gettimeofday() failed in stop_fast_slew");
+  }
+
+  fast_slewing = 0;
+
+  UTI_AverageDiffTimevals(&T1, &T1d, &T1a, &end_window);
+  UTI_DiffTimevalsToDouble(&slew_duration, &T1a, &slew_start_tv);
+  
+  /* Compute the dispersion we have introduced by changing tick this
+     way.  If the two samples of gettimeofday differ, there is an
+     uncertainty window wrt when the frequency change actually applies
+     from.  We handle this by adding dispersion to all statistics held
+     at higher levels in the system. */
+
+  introduced_dispersion = end_window * delta_total_tick;
+  lcl_InvokeDispersionNotifyHandlers(introduced_dispersion);
+
+  fast_slew_done = delta_total_tick * slew_duration /
+    (current_total_tick + delta_total_tick);
+
+  offset_register += (fast_slew_wanted + fast_slew_done);
+
+}
+
+
+/* ================================================== */
+/* This routine is called to start a clock offset adjustment */
+
+static void
+initiate_slew(void)
+{
+  double dseconds;
+  long tick_adjust;
+  long offset;
+  struct timeval T0, T0d, T0a;
+  struct timeval end_of_slew;
+  struct timezone tz;
+  double start_window;
+  double introduced_dispersion;
+
+  /* Don't want to get here if we already have an adjust on the go! */
+  if (fast_slewing) {
+    CROAK("Should not be fast slewing");
+  }
+
+  if (offset_register == 0.0) {
+    return;
+  }
+
+  if (fabs(offset_register) < MAX_ADJUST_WITH_ADJTIME) {
+    /* Use adjtime to do the shift */
+    offset = (long)(0.5 + 1.0e6*(-offset_register));
+
+    if (TMX_ApplyOffset(&offset) < 0) {
+      CROAK("adjtimex() failed in initiate_slew");
+    }
+
+    offset_register = 0.0;
+
+  } else {
+
+    /* If the system clock has a high drift rate, the combination of
+       current_tick + slew_delta_tick could be outside the range that adjtimex
+       will accept.  To prevent this, the tick adjustment that is used to slew
+       an error off the clock is clamped according to what tick_adjust is.
+    */
+
+    long min_allowed_tick, max_allowed_tick;
+
+    min_allowed_tick = nominal_tick - max_tick_bias;
+    max_allowed_tick = nominal_tick + max_tick_bias;
+
+    if (offset_register > 0) {
+      slewing_tick = current_tick - slew_delta_tick;
+      if (slewing_tick <= min_allowed_tick) {
+        slewing_tick = min_allowed_tick + 1;
+      }
+    } else {
+      slewing_tick = current_tick + slew_delta_tick;
+      if (slewing_tick >= max_allowed_tick) {
+        slewing_tick = max_allowed_tick - 1;
+      }
+    }
+
+    tick_adjust = slewing_tick - current_tick;
+
+    delta_total_tick = (double) tick_adjust / 1.0e6;
+    dseconds = - offset_register * (current_total_tick + delta_total_tick) / delta_total_tick;
+
+    /* Now set the thing off */
+    if (gettimeofday(&T0, &tz) < 0) {
+      CROAK("gettimeofday() failed in initiate_slew");
+    }
+
+    if (TMX_SetTick(slewing_tick) < 0) {
+      LOG(LOGS_INFO, LOGF_SysLinux, "c_t=%ld ta=%ld sl_t=%ld dtt=%e",
+          current_tick, tick_adjust, slewing_tick, delta_total_tick);
+      CROAK("adjtimex() failed to start big slew");
+    }
+
+    if (gettimeofday(&T0d, &tz) < 0) {
+      CROAK("gettimeofday() failed in initiate_slew");
+    }
+
+    /* Now work out the uncertainty in when we actually started the
+       slew. */
+    
+    UTI_AverageDiffTimevals(&T0, &T0d, &T0a, &start_window);
+
+    /* Compute the dispersion we have introduced by changing tick this
+    way.  If the two samples of gettimeofday differ, there is an
+    uncertainty window wrt when the frequency change actually applies
+    from.  We handle this by adding dispersion to all statistics held
+    at higher levels in the system. */
+
+    introduced_dispersion = start_window * delta_total_tick;
+    lcl_InvokeDispersionNotifyHandlers(introduced_dispersion);
+
+    fast_slewing = 1;
+    slew_start_tv = T0a;
+
+    /* Set up timeout for end of slew */
+    UTI_AddDoubleToTimeval(&T0a, dseconds, &end_of_slew);
+
+    slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
+
+    fast_slew_wanted = offset_register;
+    offset_register = 0.0;
+
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+/* This is the callback routine invoked by the scheduler at the end of
+   a slew. */
+
+static void
+handle_end_of_slew(void *anything)
+{
+  stop_fast_slew();
+  initiate_slew(); /* To do any fine trimming required */
+}
+
+/* ================================================== */
+/* This routine is used to abort a slew that is in progress, if any */
+
+static void
+abort_slew(void)
+{
+  if (fast_slewing) {
+    stop_fast_slew();
+    SCH_RemoveTimeout(slew_timeout_id);
+  }
+}
+
+/* ================================================== */
+/* This routine accrues an offset into the offset register, and starts
+   a slew if required.
+
+   The offset argument is measured in seconds.  Positive means the
+   clock needs to be slewed backwards (i.e. is currently fast of true
+   time) */
+
+static void
+accrue_offset(double offset)
+{
+  long toffset;
+  
+  /* Add the new offset to the register */
+  offset_register += offset;
+
+  /* Cancel any standard adjtime that is running */
+  toffset = 0;
+  if (TMX_ApplyOffset(&toffset) < 0) {
+    CROAK("adjtimex() failed in accrue_offset");
+  }
+
+  offset_register -= (double) toffset / 1.0e6;
+
+  if (!fast_slewing) {
+    initiate_slew();
+  } /* Otherwise, when the fast slew completes, any other stuff
+       in the offset register will be applied */
+
+}
+
+/* ================================================== */
+/* Positive means currently fast of true time, i.e. jump backwards */
+
+static void
+apply_step_offset(double offset)
+{
+  struct timeval old_time, new_time;
+  struct timezone tz;
+
+  if (fast_slewing) {
+    abort_slew();
+  }
+
+  if (gettimeofday(&old_time, &tz) < 0) {
+    CROAK("gettimeofday in apply_step_offset");
+  }
+
+  UTI_AddDoubleToTimeval(&old_time, -offset, &new_time);
+
+  if (settimeofday(&new_time, &tz) < 0) {
+    CROAK("settimeofday in apply_step_offset");
+  }
+
+  initiate_slew();
+
+}
+
+/* ================================================== */
+/* This call sets the Linux kernel frequency to a given value in parts
+   per million relative to the nominal running frequency.  Nominal is taken to
+   be tick=10000, freq=0 (for a HZ==100 system, other values otherwise).  The
+   convention is that this is called with a positive argument if the local
+   clock runs fast when uncompensated.  */
+
+static void
+set_frequency(double freq_ppm) {
+
+  long required_tick;
+  double required_freq; /* what we use */
+  double scaled_freq; /* what adjtimex & the kernel use */
+  int required_delta_tick;
+  int neg; /* True if estimate is that local clock runs slow,
+              i.e. positive frequency correction required */
+
+
+  /* If we in the middle of slewing the time by having the value of
+     tick altered, we have to stop doing that, because the timeout
+     expiry etc will change if we don't. */
+
+  if (fast_slewing) {
+    abort_slew();
+  }
+
+  if (freq_ppm < 0.0) {
+    neg = 1;
+    freq_ppm = -freq_ppm;
+  } else {
+    neg = 0;
+  }
+
+  required_delta_tick = round(freq_ppm / dhz);
+  required_freq = freq_ppm - dhz * (double) required_delta_tick;
+
+  if (neg) {
+    /* Uncompensated local clock runs slow */
+    required_tick = nominal_tick + required_delta_tick;
+    scaled_freq = freq_scale * required_freq;
+  } else {
+    /* Uncompensated local clock runs fast */
+    required_tick = nominal_tick - required_delta_tick;
+    scaled_freq = -freq_scale * required_freq;
+  }
+
+  if (TMX_SetFrequency(scaled_freq, required_tick) < 0) {
+    char buffer[1024];
+    sprintf(buffer, "adjtimex failed for set_frequency, freq_ppm=%10.4e scaled_freq=%10.4e required_tick=%ld",
+            freq_ppm, scaled_freq, required_tick);
+    CROAK(buffer);
+  }
+
+  current_tick = required_tick;
+  current_total_tick = ((double)current_tick + required_freq/dhz) / 1.0e6 ;
+
+  initiate_slew(); /* Restart any slews that need to be restarted */
+
+  return;
+
+}
+
+/* ================================================== */
+/* Read the ppm frequency from the kernel */
+
+static double
+read_frequency(void)
+{
+  double tick_term;
+  double unscaled_freq;
+  double freq_term;
+
+  if (TMX_GetFrequency(&unscaled_freq) < 0) {
+    CROAK("adjtimex failed in read_frequency");
+  }
+
+  /* Use current_tick here rather than txc.tick, otherwise we're
+     thrown off course when doing a fast slew (in which case, txc.tick
+     is nowhere near the nominal value) */
+  tick_term = dhz * (double)(nominal_tick - current_tick);
+  freq_term = unscaled_freq / freq_scale;
+  
+#if 0
+  LOG(LOGS_INFO, LOGF_SysLinux, "txc.tick=%ld txc.freq=%ld tick_term=%f freq_term=%f",
+      txc.tick, txc.freq, tick_term, freq_term);
+#endif
+
+  return tick_term - freq_term;
+
+}
+
+/* ================================================== */
+/* Given a raw time, determine the correction in seconds to generate
+   the 'cooked' time.  The correction has to be added to the
+   raw time */
+
+static void
+get_offset_correction(struct timeval *raw,
+                      double *corr)
+{
+
+  /* Correction is given by these things :
+     1. Any value in offset register
+     2. Amount of fast slew remaining
+     3. Any amount of adjtime correction remaining */
+
+
+  double adjtime_left;
+  double fast_slew_duration;
+  double fast_slew_achieved;
+  double fast_slew_remaining;
+  long offset;
+
+  if (have_readonly_adjtime) {
+    if (TMX_GetOffsetLeft(&offset) < 0) {
+      CROAK("adjtimex() failed in get_offset_correction");
+    }
+    
+    adjtime_left = (double)offset / 1.0e6;
+  } else {
+    offset = 0;
+    if (TMX_ApplyOffset(&offset) < 0) {
+      CROAK("adjtimex() failed in get_offset_correction");
+    }
+    
+    adjtime_left = (double)offset / 1.0e6;
+
+    /* txc.offset still set from return value of last call */
+    if (TMX_ApplyOffset(&offset) < 0) {
+      CROAK("adjtimex() failed in get_offset_correction");
+    }
+  }
+
+  if (fast_slewing) {
+    UTI_DiffTimevalsToDouble(&fast_slew_duration, raw, &slew_start_tv);
+    fast_slew_achieved = delta_total_tick * fast_slew_duration /
+      (current_total_tick + delta_total_tick);
+    fast_slew_remaining = fast_slew_wanted + fast_slew_achieved;
+  } else {
+    fast_slew_remaining = 0.0;
+  }  
+
+  *corr = - (offset_register + fast_slew_remaining) + adjtime_left;
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+immediate_step(void)
+{
+  struct timeval old_time, new_time;
+  struct timezone tz;
+  long offset;
+
+  if (fast_slewing) {
+    abort_slew();
+  }
+
+  offset = 0;
+  if (TMX_ApplyOffset(&offset) < 0) {
+    CROAK("adjtimex() failed in immediate_step");
+  }
+
+  offset_register -= (double) offset / 1.0e6;
+
+  if (gettimeofday(&old_time, &tz) < 0) {
+    CROAK("gettimeofday() failed in immediate_step");
+  }
+
+  UTI_AddDoubleToTimeval(&old_time, -offset_register, &new_time);
+
+  if (settimeofday(&new_time, &tz) < 0) {
+    CROAK("settimeofday() failed in immediate_step");
+  }
+
+  offset_register = 0.0;
+
+  return;
+}
+
+/* ================================================== */
+
+/* Estimate the value of HZ given the value of txc.tick that chronyd finds when
+ * it starts.  The only credible values are 100 (Linux/x86) or powers of 2.
+ * Also, the bounds checking inside the kernel's adjtimex system call enforces
+ * a +/- 10% movement of tick away from the nominal value 1e6/HZ. */
+
+static void
+guess_hz_and_shift_hz(int tick, int *hz, int *shift_hz)
+{
+  int i, tick_lo, tick_hi, ihz;
+  double tick_nominal;
+  /* Pick off the hz=100 case first */
+  if (tick >= 9000 && tick <= 11000) {
+    *hz = 100;
+    *shift_hz = 7;
+    return;
+  }
+
+  for (i=4; i<16; i++) { /* surely 16 .. 32768 is a wide enough range? */
+    ihz = 1 << i;
+    tick_nominal = 1.0e6 / (double) ihz;
+    tick_lo = (int)(0.5 + tick_nominal*2.0/3.0);
+    tick_hi = (int)(0.5 + tick_nominal*4.0/3.0);
+    
+    if (tick_lo < tick && tick <= tick_hi) {
+      *hz = ihz;
+      *shift_hz = i;
+      return;
+    }
+  }
+
+  /* oh dear.  doomed. */
+  *hz = 0;
+  *shift_hz = 0;
+  return;
+}
+
+/* ================================================== */
+/* Compute the scaling to use on any frequency we set, according to
+   the vintage of the Linux kernel being used. */
+
+static void
+get_version_specific_details(void)
+{
+  int major, minor, patch;
+  int shift_hz;
+  double dshift_hz;
+  double basic_freq_scale; /* what to use if HZ!=100 */
+  int config_hz, set_config_hz; /* values of HZ from conf file */
+  int set_config_freq_scale;
+  double config_freq_scale;
+  double calculated_freq_scale;
+  struct tmx_params tmx_params;
+  struct utsname uts;
+  
+  TMX_ReadCurrentParams(&tmx_params);
+  
+  guess_hz_and_shift_hz(tmx_params.tick, &hz, &shift_hz);
+
+  if (!shift_hz) {
+    LOG_FATAL(LOGF_SysLinux, "Can't determine hz (txc.tick=%ld txc.freq=%ld (%.8f) txc.offset=%ld)",
+              tmx_params.tick, tmx_params.freq, tmx_params.dfreq, tmx_params.offset);
+  } else {
+    LOG(LOGS_INFO, LOGF_SysLinux, "Initial txc.tick=%ld txc.freq=%ld (%.8f) txc.offset=%ld => hz=%d shift_hz=%d",
+        tmx_params.tick, tmx_params.freq, tmx_params.dfreq, tmx_params.offset, hz, shift_hz);
+  }
+
+  CNF_GetLinuxHz(&set_config_hz, &config_hz);
+  if (set_config_hz) hz = config_hz;
+  /* (If true, presumably freq_scale will be overridden anyway, making shift_hz
+     redundant too.) */
+
+  dhz = (double) hz;
+  dshift_hz = (double)(1UL << shift_hz);
+  basic_freq_scale = dshift_hz / dhz;
+  nominal_tick = (1000000L + (hz/2))/hz; /* Mirror declaration in kernel */
+  slew_delta_tick = nominal_tick / 12;
+  max_tick_bias = nominal_tick / 10;
+
+  /* The basic_freq_scale comes from:
+     * the kernel increments the usec counter HZ times per second (if the timer
+       interrupt period were perfect)
+     * the following code in the kernel
+
+       time_adj (+/-)= ltemp >>
+         (SHIFT_USEC + SHIFT_HZ - SHIFT_SCALE);
+
+       causes the adjtimex 'freq' value to be divided down by 1<<SHIFT_HZ.
+
+      The net effect is that we have to scale up the value we want by the
+      reciprocal of all this, i.e. multiply by (1<<SHIFT_HZ)/HZ.
+
+     If HZ==100, this code in the kernel comes into play too:
+#if HZ == 100
+    * Compensate for (HZ==100) != (1 << SHIFT_HZ).
+     * Add 25% and 3.125% to get 128.125; => only 0.125% error (p. 14)
+     *
+    if (time_adj < 0)
+        time_adj -= (-time_adj >> 2) + (-time_adj >> 5);
+    else
+        time_adj += (time_adj >> 2) + (time_adj >> 5);
+#endif
+
+    Special case that later.
+   */
+
+  if (uname(&uts) < 0) {
+    LOG_FATAL(LOGF_SysLinux, "Cannot uname(2) to get kernel version, sorry.");
+  }
+  if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) {
+    LOG_FATAL(LOGF_SysLinux, "Cannot read information from uname, sorry");
+  }
+
+  LOG(LOGS_INFO, LOGF_SysLinux, "Linux kernel major=%d minor=%d patch=%d", major, minor, patch);
+
+  version_major = major;
+  version_minor = minor;
+  version_patchlevel = patch;
+  
+  switch (major) {
+    case 1:
+      /* Does Linux v1.x even support HZ!=100? */
+      switch (minor) {
+        case 2:
+          if (patch == 13) {
+            freq_scale = (hz==100) ? (128.0 / 100.0) : basic_freq_scale ; /* I _think_! */
+           have_readonly_adjtime = 1;
+          } else {
+            LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry.");
+          }
+          break;
+        case 3:
+          /* I guess the change from the 1.2.x scaling to the 2.0.x
+             scaling must have happened during 1.3 development.  I
+             haven't a clue where though, until someone looks it
+             up. */
+          LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry.");
+          break;
+        default:
+          LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry.");
+          break;
+      }
+      break;
+    case 2:
+      switch (minor) {
+        case 0:
+          if (patch < 32) {
+            freq_scale = (hz==100) ? (128.0 / 125.0) : basic_freq_scale;
+           have_readonly_adjtime = 1;
+          } else if (patch >= 32) {
+            freq_scale = (hz==100) ? (128.0 / 128.125) : basic_freq_scale;
+           
+           /* The functionality in kernel/time.c in the kernel source
+               was modified with regard to what comes back in the
+               txc.offset field on return from adjtimex.  If txc.modes
+               was ADJ_OFFSET_SINGLESHOT on entry, the outstanding
+               adjustment is returned, however the running offset will
+               be modified to the passed value. */
+           have_readonly_adjtime = 0;
+          }
+          break;
+        case 1:
+          /* I know that earlier 2.1 kernels were like 2.0.31, hence
+             the settings below.  However, the 2.0.32 behaviour may
+             have been added late in the 2.1 series, however I have no
+             idea at which patch level.  Leave it like this until
+             someone supplies some info. */
+          freq_scale = (hz==100) ? (128.0 / 125.0) : basic_freq_scale;
+          have_readonly_adjtime = 0; /* For safety ! */
+          break;
+        case 2:
+        case 3:
+        case 4:
+        case 5:
+          /* These seem to be like 2.0.32 */
+          freq_scale = (hz==100) ? (128.0 / 128.125) : basic_freq_scale;
+          have_readonly_adjtime = 0;
+          break;
+        default:
+          LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry.");
+      }
+      break;
+    default:
+      LOG_FATAL(LOGF_SysLinux, "Kernel's major version not supported yet, sorry");
+      break;
+  }
+
+  /* Override freq_scale if it appears in conf file */
+  CNF_GetLinuxFreqScale(&set_config_freq_scale, &config_freq_scale);
+  calculated_freq_scale = freq_scale;
+  if (set_config_freq_scale) freq_scale = config_freq_scale;
+  
+  LOG(LOGS_INFO, LOGF_SysLinux, "calculated_freq_scale=%.8f freq_scale=%.8f",
+      calculated_freq_scale, freq_scale);
+
+}
+
+/* ================================================== */
+/* Set denorms to flush to zero instead of trapping. */
+
+#if defined(__SH5__)
+static void enable_flush_denorms(void)
+{
+  float fpscr;
+  unsigned long ifpscr;
+  asm volatile("fgetscr %0" : "=f" (fpscr));
+  asm volatile("fmov.sl %1, %0" : "=r" (ifpscr) : "f" (fpscr));
+  ifpscr |= 0x40000;
+  asm volatile("fmov.ls %1, %0" : "=f" (fpscr) : "r" (ifpscr));
+  asm volatile("fputscr %0" : : "f" (fpscr));
+  return;
+}
+#endif
+
+/* ================================================== */
+/* Initialisation code for this module */
+
+void
+SYS_Linux_Initialise(void)
+{
+  offset_register = 0.0;
+  fast_slewing = 0;
+
+#if defined(__SH5__)
+  enable_flush_denorms();
+#endif
+
+  get_version_specific_details();
+
+  current_tick = nominal_tick;
+  current_total_tick = 1.0 / dhz;
+
+  lcl_RegisterSystemDrivers(read_frequency, set_frequency,
+                            accrue_offset, apply_step_offset,
+                            get_offset_correction, immediate_step);
+}
+
+/* ================================================== */
+/* Finalisation code for this module */
+
+void
+SYS_Linux_Finalise(void)
+{
+  /* Must *NOT* leave a fast slew running - clock would drift way off
+     if the daemon is not restarted */
+  abort_slew();
+}
+
+/* ================================================== */
+
+void
+SYS_Linux_GetKernelVersion(int *major, int *minor, int *patchlevel)
+{
+  *major = version_major;
+  *minor = version_minor;
+  *patchlevel = version_patchlevel;
+}
+
+/* ================================================== */
+
+#endif /* LINUX */
+
+/* vim:ts=8
+ * */
+
diff --git a/sys_linux.h b/sys_linux.h
new file mode 100644 (file)
index 0000000..a17e51e
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+  $Header: /cvs/src/chrony/sys_linux.h,v 1.8 2002/02/28 23:27:15 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  The header file for the linux driver
+  */
+
+#ifndef GOT_SYS_LINUX_H
+#define GOT_SYS_LINUX_H
+
+extern void SYS_Linux_Initialise(void);
+
+extern void SYS_Linux_Finalise(void);
+
+extern void SYS_Linux_GetKernelVersion(int *major, int *minor, int *patchlevel);
+
+#endif  /* GOT_SYS_LINUX_H */
diff --git a/sys_netbsd.c b/sys_netbsd.c
new file mode 100644 (file)
index 0000000..3b275f3
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+  $Header: /cvs/src/chrony/sys_netbsd.c,v 1.2 2002/02/17 22:13:49 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2001
+ * Copyright (C) J. Hannken-Illjes  2001
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Driver file for the NetBSD operating system.
+  */
+
+#ifdef __NetBSD__
+
+#include <kvm.h>
+#include <nlist.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <signal.h>
+
+#include "sys_netbsd.h"
+#include "localp.h"
+#include "logging.h"
+#include "util.h"
+
+/* ================================================== */
+
+/* This register contains the number of seconds by which the local
+   clock was estimated to be fast of reference time at the epoch when
+   gettimeofday() returned T0 */
+
+static double offset_register;
+
+/* This register contains the epoch to which the offset is referenced */
+
+static struct timeval T0;
+
+/* This register contains the current estimate of the system
+   frequency, in absolute (NOT ppm) */
+
+static double current_freq;
+
+/* This register contains the number of seconds of adjustment that
+   were passed to adjtime last time it was called. */
+
+static double adjustment_requested;
+
+/* Kernel parameters to calculate adjtime error. */
+
+static int kern_tickadj;
+static long kern_bigadj;
+
+/* ================================================== */
+
+static void
+clock_initialise(void)
+{
+  struct timeval newadj, oldadj;
+  struct timezone tz;
+
+  offset_register = 0.0;
+  adjustment_requested = 0.0;
+  current_freq = 0.0;
+
+  if (gettimeofday(&T0, &tz) < 0) {
+    CROAK("gettimeofday() failed in clock_initialise()");
+  }
+
+  newadj.tv_sec = 0;
+  newadj.tv_usec = 0;
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in clock_initialise");
+  }
+
+}
+
+/* ================================================== */
+
+static void
+clock_finalise(void)
+{
+  /* Nothing to do yet */
+
+}
+
+/* ================================================== */
+
+static void
+start_adjust(void)
+{
+  struct timeval newadj, oldadj;
+  struct timeval T1;
+  struct timezone tz;
+  double elapsed, accrued_error;
+  double adjust_required;
+  struct timeval exact_newadj;
+  long delta, tickdelta;
+  double rounding_error;
+  double old_adjust_remaining;
+
+  /* Determine the amount of error built up since the last adjustment */
+  if (gettimeofday(&T1, &tz) < 0) {
+    CROAK("gettimeofday() failed in start_adjust");
+  }
+
+  UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0);
+  accrued_error = elapsed * current_freq;
+  
+  adjust_required = - (accrued_error + offset_register);
+
+  UTI_DoubleToTimeval(adjust_required, &exact_newadj);
+
+  /* At this point, we need to round the required adjustment the
+     same way the kernel does. */
+
+  delta = exact_newadj.tv_sec * 1000000 + exact_newadj.tv_usec;
+  if (delta > kern_bigadj || delta < -kern_bigadj)
+    tickdelta = 10 * kern_tickadj;
+  else
+    tickdelta = kern_tickadj;
+  if (delta % tickdelta)
+       delta = delta / tickdelta * tickdelta;
+  newadj.tv_sec = 0;
+  newadj.tv_usec = delta;
+  UTI_NormaliseTimeval(&newadj);
+
+  /* Add rounding error back onto offset register. */
+  UTI_DiffTimevalsToDouble(&rounding_error, &newadj, &exact_newadj);
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in start_adjust");
+  }
+
+  UTI_TimevalToDouble(&oldadj, &old_adjust_remaining);
+
+  offset_register = rounding_error - old_adjust_remaining;
+
+  T0 = T1;
+  UTI_TimevalToDouble(&newadj, &adjustment_requested);
+
+}
+
+/* ================================================== */
+
+static void
+stop_adjust(void)
+{
+  struct timeval T1;
+  struct timezone tz;
+  struct timeval zeroadj, remadj;
+  double adjustment_remaining, adjustment_achieved;
+  double elapsed, elapsed_plus_adjust;
+
+  zeroadj.tv_sec = 0;
+  zeroadj.tv_usec = 0;
+
+  if (adjtime(&zeroadj, &remadj) < 0) {
+    CROAK("adjtime() failed in stop_adjust");
+  }
+
+  if (gettimeofday(&T1, &tz) < 0) {
+    CROAK("gettimeofday() failed in stop_adjust");
+  }
+  
+  UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0);
+  UTI_TimevalToDouble(&remadj, &adjustment_remaining);
+
+  adjustment_achieved = adjustment_requested - adjustment_remaining;
+  elapsed_plus_adjust = elapsed - adjustment_achieved;
+
+  offset_register += current_freq * elapsed_plus_adjust - adjustment_remaining;
+
+  adjustment_requested = 0.0;
+  T0 = T1;
+
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+   slew backwards */
+
+static void
+accrue_offset(double offset)
+{
+  stop_adjust();
+  offset_register += offset;
+  start_adjust();
+
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+   step backwards */
+
+static void
+apply_step_offset(double offset)
+{
+  struct timeval old_time, new_time, T1;
+  struct timezone tz;
+  
+  stop_adjust();
+
+  if (gettimeofday(&old_time, &tz) < 0) {
+    CROAK("gettimeofday in apply_step_offset");
+  }
+
+  UTI_AddDoubleToTimeval(&old_time, -offset, &new_time);
+
+  if (settimeofday(&new_time, &tz) < 0) {
+    CROAK("settimeofday in apply_step_offset");
+  }
+
+  UTI_AddDoubleToTimeval(&T0, offset, &T1);
+  T0 = T1;
+
+  start_adjust();
+
+}
+
+/* ================================================== */
+
+static void
+set_frequency(double new_freq_ppm)
+{
+  stop_adjust();
+  current_freq = new_freq_ppm * 1.0e-6;
+  start_adjust();
+}
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+  return current_freq * 1.0e6;
+}
+
+/* ================================================== */
+
+static void
+get_offset_correction(struct timeval *raw,
+                      double *corr)
+{
+  stop_adjust();
+  *corr = -offset_register;
+  start_adjust();
+}
+
+/* ================================================== */
+
+void
+SYS_NetBSD_Initialise(void)
+{
+  static struct nlist nl[] = {
+    {"_tickadj"},
+    {"_bigadj"},
+    {NULL}
+  };
+
+  kvm_t *kt;
+  FILE *fp;
+
+  kt = kvm_open(NULL, NULL, NULL, O_RDWR, NULL);
+  if (!kt) {
+    CROAK("Cannot open kvm\n");
+  }
+
+  if (kvm_nlist(kt, nl) < 0) {
+    CROAK("Cannot read kernel symbols\n");
+  }
+
+  if (kvm_read(kt, nl[0].n_value, (char *)(&kern_tickadj), sizeof(int)) < 0) {
+    CROAK("Cannot read from _tickadj\n");
+  }
+
+  if (kvm_read(kt, nl[1].n_value, (char *)(&kern_bigadj), sizeof(long)) < 0) {
+    CROAK("Cannot read from _bigadj\n");
+  }
+
+  kvm_close(kt);
+
+  clock_initialise();
+
+  lcl_RegisterSystemDrivers(read_frequency, set_frequency, 
+                            accrue_offset, apply_step_offset,
+                            get_offset_correction, NULL /* immediate_step */);
+
+}
+
+/* ================================================== */
+
+void
+SYS_NetBSD_Finalise(void)
+{
+  clock_finalise();
+}
+
+/* ================================================== */
+
+
+#endif /* __NetBSD__ */
diff --git a/sys_netbsd.h b/sys_netbsd.h
new file mode 100644 (file)
index 0000000..22ebdb0
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+  $Header: /cvs/src/chrony/sys_netbsd.h,v 1.2 2002/02/17 22:13:49 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2001
+ * Copyright (C) J. Hannken-Illjes  2001
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for NetBSD driver
+  */
+
+#ifndef GOT_SYS_NETBSD_H
+#define GOT_SYS_NETBSD_H
+
+void SYS_NetBSD_Initialise(void);
+
+void SYS_NetBSD_Finalise(void);
+
+#endif
diff --git a/sys_solaris.c b/sys_solaris.c
new file mode 100644 (file)
index 0000000..8fdeef3
--- /dev/null
@@ -0,0 +1,483 @@
+/*
+  $Header: /cvs/src/chrony/sys_solaris.c,v 1.18 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Driver file for Solaris operating system
+  */
+
+#ifdef SOLARIS
+
+#include <kvm.h>
+#include <fcntl.h>
+#include <nlist.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+
+#include <stdio.h>
+
+#include "sys_solaris.h"
+#include "localp.h"
+#include "sched.h"
+#include "logging.h"
+#include "util.h"
+
+/* ================================================== */
+
+/* This register contains the number of seconds by which the local
+   clock was estimated to be fast of reference time at the epoch when
+   gettimeofday() returned T0 */
+
+static double offset_register;
+
+/* This register contains the epoch to which the offset is referenced */
+
+static struct timeval T0;
+
+/* This register contains the current estimate of the system
+   frequency, in absolute (NOT ppm) */
+
+static double current_freq;
+
+/* This register contains the number of seconds of adjustment that
+   were passed to adjtime last time it was called. */
+
+static double adjustment_requested;
+
+/* ================================================== */
+/* On Solaris 2.5 & 2.5.1, passing an argument of zero as the new
+   delta to adjtime does not zero out the adjustment - the remaining
+   adjustment is returned as the old delta arg, but the adjustment keeps
+   running.  To get round this, we set adjustments of +/-1us when we
+   really want zero.  Alternate adjustments are used to avoid a drift
+   from building up. */
+
+static struct timeval zeroes[2] = {
+  {0, 1},
+  {-1, 999999}
+};
+
+static int index=0;
+
+/* If 1, need to run dosynctodr().  If 0, don't */
+static int need_dosynctodr = -1;
+
+
+#define GET_ZERO (zeroes[index^=1])
+
+/* ================================================== */
+
+static void
+clock_initialise(void)
+{
+  struct timeval newadj, oldadj;
+  struct timezone tz;
+
+  offset_register = 0.0;
+  adjustment_requested = 0.0;
+  current_freq = 0.0;
+
+  if (gettimeofday(&T0, &tz) < 0) {
+    CROAK("gettimeofday() failed in clock_initialise()");
+  }
+
+  newadj = GET_ZERO;
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in clock_initialise");
+  }
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in clock_initialise");
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+clock_finalise(void)
+{
+  /* Nothing to do yet */
+
+  return;
+
+}
+
+/* ================================================== */
+
+static void
+start_adjust(void)
+{
+  struct timeval newadj, oldadj;
+  struct timeval T1;
+  struct timezone tz;
+  double elapsed, accrued_error;
+  double adjust_required;
+  struct timeval exact_newadj;
+  double rounding_error;
+  double old_adjust_remaining;
+
+  /* Determine the amount of error built up since the last adjustment */
+  if (gettimeofday(&T1, &tz) < 0) {
+    CROAK("gettimeofday() failed in start_adjust");
+  }
+
+  UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0);
+  accrued_error = elapsed * current_freq;
+  
+  adjust_required = - (accrued_error + offset_register);
+
+  UTI_DoubleToTimeval(adjust_required, &exact_newadj);
+
+  /* At this point, we will need to call the adjustment rounding
+     algorithm in the system-specific layer.  For now, just assume the
+     adjustment can be applied exactly. */
+
+  newadj = exact_newadj;
+  
+  /* Want to *add* rounding error back onto offset register */
+  UTI_DiffTimevalsToDouble(&rounding_error, &exact_newadj, &newadj);
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in start_adjust");
+  }
+
+  UTI_TimevalToDouble(&oldadj, &old_adjust_remaining);
+
+  offset_register = rounding_error - old_adjust_remaining;
+
+  T0 = T1;
+  UTI_TimevalToDouble(&newadj, &adjustment_requested);
+  
+}
+
+/* ================================================== */
+
+static void
+stop_adjust(void)
+{
+  struct timeval T1;
+  struct timezone tz;
+  struct timeval zeroadj, remadj;
+  double adjustment_remaining, adjustment_achieved;
+  double elapsed, elapsed_plus_adjust;
+
+
+  zeroadj = GET_ZERO;
+
+  if (adjtime(&zeroadj, &remadj) < 0) {
+    CROAK("adjtime() failed in stop_adjust");
+  }
+
+  if (gettimeofday(&T1, &tz) < 0) {
+    CROAK("gettimeofday() failed in stop_adjust");
+  }
+  
+  UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0);
+  UTI_TimevalToDouble(&remadj, &adjustment_remaining);
+
+  adjustment_achieved = adjustment_requested - adjustment_remaining;
+  elapsed_plus_adjust = elapsed - adjustment_achieved;
+
+  offset_register += current_freq * elapsed_plus_adjust - adjustment_remaining;
+
+  adjustment_requested = 0.0;
+  T0 = T1;
+
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+   slew backwards */
+
+static void
+accrue_offset(double offset)
+{
+  stop_adjust();
+  offset_register += offset;
+  start_adjust();
+  return;
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+   step backwards */
+
+static void
+apply_step_offset(double offset)
+{
+  struct timeval old_time, new_time, rounded_new_time, T1;
+  double rounding_error;
+  struct timezone tz;
+  
+  stop_adjust();
+  if (gettimeofday(&old_time, &tz) < 0) {
+    CROAK("gettimeofday in apply_step_offset");
+  }
+
+  UTI_AddDoubleToTimeval(&old_time, -offset, &new_time);
+
+  /* The settimeofday function (on Solaris 2.5/Sparc20 at least) does
+     not work quite as we would want.  The time we want to set is
+     rounded to the nearest second and that time is used.  Also, the
+     clock appears to start from that second boundary plus about 4ms.
+     For now we'll tolerate this small error. */
+
+  rounded_new_time.tv_usec = 0;
+  if (new_time.tv_usec >= 500000) {
+    rounded_new_time.tv_sec = new_time.tv_sec + 1;
+  } else {
+    rounded_new_time.tv_sec = new_time.tv_sec;
+  }
+
+  UTI_DiffTimevalsToDouble(&rounding_error, &rounded_new_time, &new_time);
+
+  if (settimeofday(&new_time, &tz) < 0) {
+    CROAK("settimeofday in apply_step_offset");
+  }
+
+  UTI_AddDoubleToTimeval(&T0, offset, &T1);
+  T0 = T1;
+
+  offset_register += rounding_error;
+
+  start_adjust();
+}
+
+/* ================================================== */
+
+static void
+set_frequency(double new_freq_ppm)
+{
+  stop_adjust();
+  current_freq = new_freq_ppm * 1.0e-6;
+  start_adjust();
+}
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+  return current_freq * 1.0e6;
+}
+
+/* ================================================== */
+
+static void
+get_offset_correction(struct timeval *raw,
+                      double *corr)
+{
+  stop_adjust();
+  *corr = -offset_register;
+  start_adjust();
+  return;
+}
+
+/* ================================================== */
+
+static void
+immediate_step(void)
+{
+  return;
+}
+
+/* ================================================== */
+
+/* Interval in seconds between adjustments to cancel systematic drift */
+#define DRIFT_REMOVAL_INTERVAL (4.0)
+
+static int drift_removal_running = 0;
+static SCH_TimeoutID drift_removal_id;
+
+/* ================================================== */
+/* This is the timer callback routine which is called periodically to
+   invoke a time adjustment to take out the machine's drift.
+   Otherwise, times reported through this software (e.g. by running
+   ntpdate from another machine) show the machine being correct (since
+   they correct for drift build-up), but any program on this machine
+   that reads the system time will be given an erroneous value, the
+   degree of error depending on how long it is since
+   get_offset_correction was last called. */
+
+static void
+drift_removal_timeout(SCH_ArbitraryArgument not_used)
+{
+  stop_adjust();
+  start_adjust();
+  drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL);
+}
+
+/* ================================================== */
+
+static void
+check_need_dosynctodr(void)
+{
+  struct utsname name;
+  int result;
+  int major, minor, veryminor, n_fields;
+
+  result = uname(&name);
+  if (result < 0) {
+    LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot use uname to detect Solaris version");
+    need_dosynctodr = 0; /* Assume recent Solaris where it isn't needed */
+    return;
+  }
+
+  n_fields = sscanf(name.release, "%d.%d.%d\n", &major, &minor, &veryminor);
+
+  if (n_fields < 2) {
+    LOG(LOGS_ERR, LOGF_SysSolaris, "Solaris version doesn't appear to be of the form X.Y[.Z]");
+    need_dosynctodr = 0; /* Assume recent Solaris where it isn't needed */
+    return;
+  }
+
+  if (major != 5) {
+    LOG(LOGS_ERR, LOGF_SysSolaris, "Solaris major version doesn't appear to be 5");
+    need_dosynctodr = 0; /* Assume recent Solaris where it isn't needed */
+    return;
+  }
+
+  /* The 'rule of thumb' is that from Solaris 2.6 onwards, dosynctodr() doesn't
+   * need to be called, and in fact it is counter-productive to do so.  For
+   * earlier versions, it is required. */ 
+
+  if (minor < 6) {
+    need_dosynctodr = 1;
+  } else {
+    need_dosynctodr = 0;
+  }
+  
+}
+
+/* ================================================== */
+
+static void
+set_dosynctodr(unsigned long on_off)
+{
+  static struct nlist nl[] = {
+    {"dosynctodr"},
+    {NULL}
+  };
+
+  kvm_t *kt;
+  unsigned long read_back;
+
+  if (on_off!=1 && on_off!=0) {
+    CROAK("on_off should be 0 or 1");
+  }
+
+  kt = kvm_open(NULL, NULL, NULL, O_RDWR, NULL);
+  if (!kt) {
+    LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot open kvm to change dosynctodr");
+    return;
+  }
+
+  if (kvm_nlist(kt, nl) < 0) {
+    LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot read dosynctodr in nlist");
+    kvm_close(kt);
+    return;
+  }
+
+  if (kvm_write(kt, nl[0].n_value, (char *)(&on_off), sizeof(unsigned long)) < 0) {
+    LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot write to dosynctodr");
+    kvm_close(kt);
+    return;
+  }
+
+  if (kvm_read(kt, nl[0].n_value, (char *)(&read_back), sizeof(unsigned long)) < 0) {
+    LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot read from dosynctodr");
+    kvm_close(kt);
+    return;
+  }
+
+  kvm_close(kt);
+
+  if (read_back != on_off) {
+    CROAK("read_back should equal on_off");
+  }
+
+#if 0
+  LOG(LOGS_INFO, LOGF_SysSolaris, "Set value of dosynctodr to %d", on_off);
+#endif
+
+}
+
+/* ================================================== */
+
+void
+SYS_Solaris_Initialise(void)
+{
+
+  check_need_dosynctodr();
+
+  /* Need to do KVM stuff to turn off dosynctodr. */
+
+  clock_initialise();
+
+  lcl_RegisterSystemDrivers(read_frequency, set_frequency, 
+                            accrue_offset, apply_step_offset,
+                            get_offset_correction, NULL /* immediate_step */);
+
+  /* Turn off the kernel switch that keeps the system clock in step
+     with the non-volatile clock */
+  if (need_dosynctodr) {
+    set_dosynctodr(0);
+  }
+
+  drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL);
+  drift_removal_running = 1;
+}
+
+/* ================================================== */
+
+void
+SYS_Solaris_Finalise(void)
+{
+
+  if (drift_removal_running) {
+    SCH_RemoveTimeout(drift_removal_id);
+  }
+
+  clock_finalise();
+
+  /* When exiting, we want to return the machine to its 'autonomous'
+     tracking mode */
+  if (need_dosynctodr) {
+    set_dosynctodr(1);
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+#endif /* SOLARIS */
+
diff --git a/sys_solaris.h b/sys_solaris.h
new file mode 100644 (file)
index 0000000..2be4c86
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+  $Header: /cvs/src/chrony/sys_solaris.h,v 1.7 2002/02/28 23:27:15 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for Solaris driver
+  */
+
+#ifndef GOT_SYS_SOLARIS_H
+#define GOT_SYS_SOLARIS_H
+
+void SYS_Solaris_Initialise(void);
+
+void SYS_Solaris_Finalise(void);
+
+#endif
diff --git a/sys_sunos.c b/sys_sunos.c
new file mode 100644 (file)
index 0000000..80c96dc
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+  $Header: /cvs/src/chrony/sys_sunos.c,v 1.18 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Driver file for the SunOS 4.1.x operating system.
+  */
+
+#ifdef SUNOS
+
+#include <kvm.h>
+#include <fcntl.h>
+#include <nlist.h>
+#include <assert.h>
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <signal.h>
+
+#include "sys_sunos.h"
+#include "localp.h"
+#include "logging.h"
+#include "util.h"
+#include "sched.h"
+
+/* ================================================== */
+
+/* This register contains the number of seconds by which the local
+   clock was estimated to be fast of reference time at the epoch when
+   gettimeofday() returned T0 */
+
+static double offset_register;
+
+/* This register contains the epoch to which the offset is referenced */
+
+static struct timeval T0;
+
+/* This register contains the current estimate of the system
+   frequency, in absolute (NOT ppm) */
+
+static double current_freq;
+
+/* This register contains the number of seconds of adjustment that
+   were passed to adjtime last time it was called. */
+
+static double adjustment_requested;
+
+/* Eventually, this needs to be a user-defined parameter - e.g. user
+   might want 5 to get much finer resolution like xntpd.  We stick
+   with a reasonable number so that slewing can work.
+
+   This value has to be a factor of 1 million, otherwise the noddy
+   method we use for rounding an adjustment to the nearest multiple of
+   this value won't work!!
+
+   */
+static unsigned long our_tickadj = 100;
+
+/* ================================================== */
+
+static void
+clock_initialise(void)
+{
+  struct timeval newadj, oldadj;
+  struct timezone tz;
+
+  offset_register = 0.0;
+  adjustment_requested = 0.0;
+  current_freq = 0.0;
+
+  if (gettimeofday(&T0, &tz) < 0) {
+    CROAK("gettimeofday() failed in clock_initialise()");
+  }
+
+  newadj.tv_sec = 0;
+  newadj.tv_usec = 0;
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in clock_initialise");
+  }
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in clock_initialise");
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+static void
+clock_finalise(void)
+{
+  /* Nothing to do yet */
+
+  return;
+
+}
+
+/* ================================================== */
+
+static void
+start_adjust(void)
+{
+  struct timeval newadj, oldadj;
+  struct timeval T1;
+  struct timezone tz;
+  double elapsed, accrued_error;
+  double adjust_required;
+  struct timeval exact_newadj;
+  double rounding_error;
+  double old_adjust_remaining;
+  long remainder, multiplier;
+
+  /* Determine the amount of error built up since the last adjustment */
+  if (gettimeofday(&T1, &tz) < 0) {
+    CROAK("gettimeofday() failed in start_adjust");
+  }
+
+  UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0);
+  accrued_error = elapsed * current_freq;
+  
+  adjust_required = - (accrued_error + offset_register);
+
+  UTI_DoubleToTimeval(adjust_required, &exact_newadj);
+
+  /* At this point, we need to round the required adjustment to the
+     closest multiple of _tickadj --- because SunOS can't process
+     other adjustments exactly and will silently discard the residual.
+     Obviously such behaviour can't be tolerated for us. */
+
+  newadj = exact_newadj;
+  remainder = newadj.tv_usec % our_tickadj;
+  multiplier = newadj.tv_usec / our_tickadj;
+  if (remainder >= (our_tickadj >> 1)) {
+    newadj.tv_usec = (multiplier + 1) * our_tickadj;
+  } else {
+    newadj.tv_usec = multiplier * our_tickadj;
+  }
+
+  UTI_NormaliseTimeval(&newadj);
+  
+  /* Want to *add* rounding error back onto offset register.  Note
+     that the exact adjustment was the offset register *negated* */
+  UTI_DiffTimevalsToDouble(&rounding_error, &newadj, &exact_newadj);
+
+  if (adjtime(&newadj, &oldadj) < 0) {
+    CROAK("adjtime() failed in start_adjust");
+  }
+
+  UTI_TimevalToDouble(&oldadj, &old_adjust_remaining);
+
+  offset_register = rounding_error - old_adjust_remaining;
+
+  T0 = T1;
+  UTI_TimevalToDouble(&newadj, &adjustment_requested);
+
+}
+
+/* ================================================== */
+
+static void
+stop_adjust(void)
+{
+  struct timeval T1;
+  struct timezone tz;
+  struct timeval zeroadj, remadj;
+  double adjustment_remaining, adjustment_achieved;
+  double gap;
+  double elapsed, elapsed_plus_adjust;
+
+  zeroadj.tv_sec = 0;
+  zeroadj.tv_usec = 0;
+
+  if (adjtime(&zeroadj, &remadj) < 0) {
+    CROAK("adjtime() failed in stop_adjust");
+  }
+
+  if (gettimeofday(&T1, &tz) < 0) {
+    CROAK("gettimeofday() failed in stop_adjust");
+  }
+  
+  UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0);
+  UTI_TimevalToDouble(&remadj, &adjustment_remaining);
+
+  adjustment_achieved = adjustment_requested - adjustment_remaining;
+  elapsed_plus_adjust = elapsed - adjustment_achieved;
+
+  offset_register += current_freq * elapsed_plus_adjust - adjustment_remaining;
+
+  adjustment_requested = 0.0;
+  T0 = T1;
+
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+   slew backwards */
+
+static void
+accrue_offset(double offset)
+{
+  stop_adjust();
+  offset_register += offset;
+  start_adjust();
+  return;
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+   step backwards */
+
+static void
+apply_step_offset(double offset)
+{
+  struct timeval old_time, new_time, T1;
+  struct timezone tz;
+  
+  stop_adjust();
+  if (gettimeofday(&old_time, &tz) < 0) {
+    CROAK("gettimeofday in apply_step_offset");
+  }
+
+  UTI_AddDoubleToTimeval(&old_time, -offset, &new_time);
+
+  if (settimeofday(&new_time, &tz) < 0) {
+    CROAK("settimeofday in apply_step_offset");
+  }
+
+  UTI_AddDoubleToTimeval(&T0, offset, &T1);
+  T0 = T1;
+
+  start_adjust();
+
+}
+
+/* ================================================== */
+
+static void
+set_frequency(double new_freq_ppm)
+{
+  stop_adjust();
+  current_freq = new_freq_ppm * 1.0e-6;
+  start_adjust();
+}
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+  return current_freq * 1.0e6;
+}
+
+/* ================================================== */
+
+static void
+get_offset_correction(struct timeval *raw,
+                      double *corr)
+{
+  stop_adjust();
+  *corr = -offset_register;
+  start_adjust();
+  return;
+}
+
+/* ================================================== */
+
+static void
+immediate_step(void)
+{
+  return;
+}
+
+/* ================================================== */
+
+/* Interval in seconds between adjustments to cancel systematic drift */
+#define DRIFT_REMOVAL_INTERVAL (4.0)
+
+static int drift_removal_running = 0;
+static SCH_TimeoutID drift_removal_id;
+
+/* ================================================== */
+/* This is the timer callback routine which is called periodically to
+   invoke a time adjustment to take out the machine's drift.
+   Otherwise, times reported through this software (e.g. by running
+   ntpdate from another machine) show the machine being correct (since
+   they correct for drift build-up), but any program on this machine
+   that reads the system time will be given an erroneous value, the
+   degree of error depending on how long it is since
+   get_offset_correction was last called. */
+
+static void
+drift_removal_timeout(SCH_ArbitraryArgument not_used)
+{
+  stop_adjust();
+  start_adjust();
+  drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL);
+}
+
+/* ================================================== */
+
+static void
+setup_kernel(unsigned long on_off)
+{
+  static struct nlist nl[] = {
+    {"_dosynctodr"},
+    {"_tick"},
+    {"_tickadj"},
+    {NULL}
+  };
+
+  kvm_t *kt;
+  unsigned long read_back;
+  unsigned long our_tick = 10000;
+  unsigned long default_tickadj = 625;
+
+  if (on_off!=1 && on_off!=0) {
+    CROAK("on_off should be 0 or 1");
+  }
+
+  kt = kvm_open(NULL, NULL, NULL, O_RDWR, NULL);
+  if (!kt) {
+    LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot open kvm");
+    return;
+  }
+
+  if (kvm_nlist(kt, nl) < 0) {
+    LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot read kernel symbols");
+    kvm_close(kt);
+    return;
+  }
+
+  if (kvm_write(kt, nl[0].n_value, (char *)(&on_off), sizeof(unsigned long)) < 0) {
+    LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot write to _dosynctodr");
+    kvm_close(kt);
+    return;
+  }
+
+  if (kvm_write(kt, nl[1].n_value, (char *)(&our_tick), sizeof(unsigned long)) < 0) {
+    LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot write to _tick");
+    kvm_close(kt);
+    return;
+  }
+
+  if (kvm_write(kt, nl[2].n_value,
+                (char *)(&(on_off ? default_tickadj : our_tickadj)),
+                sizeof(unsigned long)) < 0) {
+    LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot write to _tickadj");
+    kvm_close(kt);
+    return;
+  }
+
+  kvm_close(kt);
+
+#if 0
+  LOG(LOGS_INFO, LOGF_SysSunOS, "Set value of _dosynctodr to %d", on_off);
+#endif
+
+}
+
+/* ================================================== */
+
+void
+SYS_SunOS_Initialise(void)
+{
+
+  /* Need to do KVM stuff to turn off dosynctodr. */
+
+  clock_initialise();
+
+  lcl_RegisterSystemDrivers(read_frequency, set_frequency, 
+                            accrue_offset, apply_step_offset,
+                            get_offset_correction, NULL /* immediate_step */);
+
+  /* Turn off the kernel switch that keeps the system clock in step
+     with the non-volatile clock */
+  setup_kernel(0);
+
+  drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL);
+  drift_removal_running = 1;
+
+}
+
+/* ================================================== */
+
+void
+SYS_SunOS_Finalise(void)
+{
+
+  if (drift_removal_running) {
+    SCH_RemoveTimeout(drift_removal_id);
+  }
+
+  /* Turn dosynctodr back on?? */
+
+  clock_finalise();
+
+  /* When exiting, we want to return the machine to its 'autonomous'
+     tracking mode */
+  setup_kernel(1);
+
+  return;
+}
+
+/* ================================================== */
+
+
+#endif /* SUNOS */
diff --git a/sys_sunos.h b/sys_sunos.h
new file mode 100644 (file)
index 0000000..44a8062
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+  $Header: /cvs/src/chrony/sys_sunos.h,v 1.7 2002/02/28 23:27:15 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for Solaris driver
+  */
+
+#ifndef GOT_SYS_SUNOS_H
+#define GOT_SYS_SUNOS_H
+
+void SYS_SunOS_Initialise(void);
+
+void SYS_SunOS_Finalise(void);
+
+#endif
diff --git a/sysincl.h b/sysincl.h
new file mode 100644 (file)
index 0000000..afe340d
--- /dev/null
+++ b/sysincl.h
@@ -0,0 +1,129 @@
+/*
+  $Header: /cvs/src/chrony/sysincl.h,v 1.10 2003/04/10 21:28:11 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This file includes all system header files that the software
+  requires.  This allows us to isolate system dependencies to this file
+  alone.
+  */
+
+#ifndef GOT_SYSINCL_H
+#define GOT_SYSINCL_H
+
+#if defined (SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(__NetBSD__)
+
+#if !defined(__NetBSD__)
+#include <alloca.h>
+#endif
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <float.h>
+#include <malloc.h>
+#include <math.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <time.h>
+
+#if HAS_STDINT_H
+#include <stdint.h>
+#elif defined(HAS_INTTYPES_H)
+#include <inttypes.h>
+#else
+/* Tough */
+#endif
+
+/* One or other of these to make getsid() visible */
+#define __EXTENSIONS__ 1
+#define __USE_XOPEN_EXTENDED 1
+
+#include <unistd.h>
+
+#endif
+
+#if defined (SOLARIS) || defined(SUNOS)
+/* Only needed on these platforms, and doesn't exist on some Linux
+   versions. */
+#include <nlist.h>
+#endif
+
+#if defined (HAS_NO_BZERO)
+#define bzero(ptr,n) memset(ptr,0,n)
+#endif /* HAS_NO_BZERO */
+
+#if defined (WINNT)
+
+/* Designed to work with the GCC from the GNAT-3.10 for Win32
+   distribution */
+
+#define Win32_Winsock
+#include <assert.h>
+#include <ctype.h>
+
+#if 1
+/* Cheat and inline the necessary bits from <errno.h>.  We don't
+   include it directly because it redefines some EXXX constants that
+   conflict with <windows32/sockets.h> (included by <windows.h>) */
+
+int*   _errno();
+int*   __doserrno();
+
+#define        errno           (*_errno())
+#define        _doserrno       (*__doserrno())
+
+#define ENOENT 2
+#else
+
+#include <errno.h>
+#endif
+
+
+#include <float.h>
+#include <math.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <windows.h>
+#endif
+
+#endif /* GOT_SYSINCL_H */
diff --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..ce743c3
--- /dev/null
+++ b/util.c
@@ -0,0 +1,364 @@
+/*
+  $Header: /cvs/src/chrony/util.c,v 1.19 2003/03/24 23:35:43 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Various utility functions
+  */
+
+#include "sysincl.h"
+
+#include "util.h"
+#include "logging.h"
+
+/* ================================================== */
+
+INLINE_STATIC void
+UTI_TimevalToDouble(struct timeval *a, double *b)
+{
+  *b = (double)(a->tv_sec) + 1.0e-6 * (double)(a->tv_usec);
+
+}
+
+/* ================================================== */
+
+INLINE_STATIC void
+UTI_DoubleToTimeval(double a, struct timeval *b)
+{
+  long int_part, frac_part;
+  int_part = (long)(a);
+  frac_part = (long)(0.5 + 1.0e6 * (a - (double)(int_part)));
+  b->tv_sec = int_part;
+  b->tv_usec = frac_part;
+  UTI_NormaliseTimeval(b);
+}
+
+/* ================================================== */
+
+INLINE_STATIC int
+UTI_CompareTimevals(struct timeval *a, struct timeval *b)
+{
+  if (a->tv_sec < b->tv_sec) {
+    return -1;
+  } else if (a->tv_sec > b->tv_sec) {
+    return +1;
+  } else {
+    if (a->tv_sec != b->tv_sec) {
+      CROAK("a->tv_sec != b->tv_sec");
+    }
+    if (a->tv_usec < b->tv_usec) {
+      return -1;
+    } else if (a->tv_usec > b->tv_usec) {
+      return +1;
+    } else {
+      if (a->tv_usec != b->tv_usec) {
+        CROAK("a->tv_usec != b->tv_usec");
+      }
+      return 0;
+    }
+  }
+  CROAK("Impossible"); /* Shouldn't be able to fall through. */
+}
+
+/* ================================================== */
+
+INLINE_STATIC void
+UTI_NormaliseTimeval(struct timeval *x)
+{
+  while (x->tv_usec >= 1000000) {
+    ++x->tv_sec;
+    x->tv_usec -= 1000000;
+  }
+
+  while (x->tv_usec < 0) {
+    --x->tv_sec;
+    x->tv_usec += 1000000;
+  }
+
+}
+
+/* ================================================== */
+
+INLINE_STATIC void
+UTI_DiffTimevals(struct timeval *result,
+                 struct timeval *a,
+                 struct timeval *b)
+{
+  result->tv_sec  = a->tv_sec  - b->tv_sec;
+  result->tv_usec = a->tv_usec - b->tv_usec;
+
+  /* Correct microseconds field to bring it into the range
+     [0,1000000) */
+
+  while (result->tv_usec < 0) {
+    result->tv_usec += 1000000;
+    --result->tv_sec;
+  }
+
+  while (result->tv_usec > 999999) {
+    result->tv_usec -= 1000000;
+    ++result->tv_sec;
+  }
+
+  return;
+}
+
+/* ================================================== */
+
+/* Calculate result = a - b and return as a double */
+INLINE_STATIC void
+UTI_DiffTimevalsToDouble(double *result, 
+                         struct timeval *a,
+                         struct timeval *b)
+{
+  *result = (double)(a->tv_sec - b->tv_sec) +
+    (double)(a->tv_usec - b->tv_usec) * 1.0e-6;
+}
+
+/* ================================================== */
+
+INLINE_STATIC void
+UTI_AddDoubleToTimeval(struct timeval *start,
+                       double increment,
+                       struct timeval *end)
+{
+  long int_part, frac_part;
+
+  /* Don't want to do this by using (long)(1000000 * increment), since
+     that will only cope with increments up to +/- 2148 seconds, which
+     is too marginal here. */
+
+  int_part = (long) increment;
+  frac_part = (long) (0.5 + 1.0e6 * (increment - (double)int_part));
+
+  end->tv_sec  = int_part  + start->tv_sec;
+  end->tv_usec = frac_part + start->tv_usec;
+
+  UTI_NormaliseTimeval(end);
+}
+
+/* ================================================== */
+
+/* Calculate the average and difference (as a double) of two timevals */
+INLINE_STATIC void
+UTI_AverageDiffTimevals (struct timeval *earlier,
+                         struct timeval *later,
+                         struct timeval *average,
+                         double *diff)
+{
+  struct timeval tvdiff;
+  struct timeval tvhalf;
+
+  UTI_DiffTimevals(&tvdiff, later, earlier);
+  *diff = (double)tvdiff.tv_sec + 1.0e-6 * (double)tvdiff.tv_usec;
+
+  if (*diff < 0.0) {
+    /* Either there's a bug elsewhere causing 'earlier' and 'later' to
+       be backwards, or something wierd has happened.  Maybe when we
+       change the frequency on Linux? */
+
+    /* This seems to be fairly benign, so don't bother logging it */
+
+#if 0
+    LOG(LOGS_INFO, LOGF_Util, "Earlier=[%s] Later=[%s]",
+        UTI_TimevalToString(earlier), UTI_TimevalToString(later));
+#endif
+
+    /* Assume the required behaviour is to treat it as zero */
+    *diff = 0.0;
+  }
+
+  tvhalf.tv_sec = tvdiff.tv_sec / 2;
+  tvhalf.tv_usec = tvdiff.tv_usec / 2 + (tvdiff.tv_sec % 2);
+  
+  average->tv_sec  = earlier->tv_sec  + tvhalf.tv_sec;
+  average->tv_usec = earlier->tv_usec + tvhalf.tv_usec;
+  
+  /* Bring into range */
+  UTI_NormaliseTimeval(average);
+
+  while (average->tv_usec >= 1000000) {
+    ++average->tv_sec;
+    average->tv_usec -= 1000000;
+  }
+
+  while (average->tv_usec < 0) {
+    --average->tv_sec;
+    average->tv_usec += 1000000;
+  }
+
+}
+
+/* ================================================== */
+
+#define POOL_ENTRIES 16
+#define BUFFER_LENGTH 64
+static char buffer_pool[POOL_ENTRIES][BUFFER_LENGTH];
+static int  pool_ptr = 0;
+
+#define NEXT_BUFFER (buffer_pool[pool_ptr = ((pool_ptr + 1) % POOL_ENTRIES)])
+
+/* ================================================== */
+/* Convert a timeval into a temporary string, largely for diagnostic
+   display */
+
+char *
+UTI_TimevalToString(struct timeval *tv)
+{
+  char buffer[64], *result;
+  struct tm stm;
+  stm = *gmtime((time_t *) &(tv->tv_sec));
+  strftime(buffer, sizeof(buffer), "%a %x %X", &stm);
+  result = NEXT_BUFFER;
+  sprintf(result, "%s.%06ld", buffer, (unsigned long)(tv->tv_usec));
+  return result;
+}
+
+/* ================================================== */
+#define JAN_1970 0x83aa7e80UL
+
+inline static void
+int64_to_timeval(NTP_int64 *src,
+                 struct timeval *dest)
+{
+  dest->tv_sec = ntohl(src->hi) - JAN_1970;
+  
+  /* Until I invent a slick way to do this, just do it the obvious way */
+  dest->tv_usec = (int)(0.5 + (double)(ntohl(src->lo)) / 4294.967296);
+}
+
+/* ================================================== */
+/* Convert an NTP timestamp into a temporary string, largely
+   for diagnostic display */
+
+char *
+UTI_TimestampToString(NTP_int64 *ts)
+{
+  struct timeval tv;
+  int64_to_timeval(ts, &tv);
+  return UTI_TimevalToString(&tv);
+}
+
+/* ================================================== */
+
+char *
+UTI_IPToDottedQuad(unsigned long ip)
+{
+  unsigned long a, b, c, d;
+  char *result;
+  a = (ip>>24) & 0xff;
+  b = (ip>>16) & 0xff;
+  c = (ip>> 8) & 0xff;
+  d = (ip>> 0) & 0xff;
+  result = NEXT_BUFFER;
+  sprintf(result, "%ld.%ld.%ld.%ld", a, b, c, d);
+  return result;
+}
+
+/* ================================================== */
+
+char *
+UTI_TimeToLogForm(time_t t)
+{
+  struct tm stm;
+  char *result;
+
+  result = NEXT_BUFFER;
+
+  stm = *gmtime(&t);
+  strftime(result, BUFFER_LENGTH, "%Y-%m-%d %H:%M:%S", &stm);
+
+  return result;
+}
+
+/* ================================================== */
+
+void
+UTI_AdjustTimeval(struct timeval *old_tv, struct timeval *when, struct timeval *new_tv, double dfreq, double doffset)
+{
+  double elapsed, delta_time;
+
+  UTI_DiffTimevalsToDouble(&elapsed, when, old_tv);
+  delta_time = elapsed * dfreq - doffset;
+  UTI_AddDoubleToTimeval(old_tv, delta_time, new_tv);
+}
+
+/* ================================================== */
+
+/* Seconds part of RFC1305 timestamp correponding to the origin of the
+   struct timeval format. */
+#define JAN_1970 0x83aa7e80UL
+
+void
+UTI_TimevalToInt64(struct timeval *src,
+                   NTP_int64 *dest)
+{
+  unsigned long usec = src->tv_usec;
+  unsigned long sec = src->tv_sec;
+
+  /* Recognize zero as a special case - it always signifies
+     an 'unknown' value */
+  if (!usec && !sec) {
+    dest->hi = dest->lo = 0;
+  } else {
+    dest->hi = htonl(src->tv_sec + JAN_1970);
+
+    /* This formula gives an error of about 0.1us worst case */
+    dest->lo = htonl(4295 * usec - (usec>>5) - (usec>>9));
+  }
+}
+
+/* ================================================== */
+
+void
+UTI_Int64ToTimeval(NTP_int64 *src,
+                   struct timeval *dest)
+{
+  /* As yet, there is no need to check for zero - all processing that
+     has to detect that case is in the NTP layer */
+
+  dest->tv_sec = ntohl(src->hi) - JAN_1970;
+  
+  /* Until I invent a slick way to do this, just do it the obvious way */
+  dest->tv_usec = (int)(0.5 + (double)(ntohl(src->lo)) / 4294.967296);
+}
+
+/* ================================================== */
+/* Force a core dump and exit without doing abort() or assert(0).
+   These do funny things with the call stack in the core file that is
+   generated, which makes diagnosis difficult. */
+
+int
+croak(const char *file, int line, const char *msg)
+{
+  int a;
+  LOG(LOGS_ERR, LOGF_Util, "Unexpected condition [%s] at %s:%d, core dumped",
+      msg, file, line);
+  a = * (int *) 0;
+  return a; /* Can't happen - this stops the optimiser optimising the
+               line above */
+}
+
+/* ================================================== */
diff --git a/util.h b/util.h
new file mode 100644 (file)
index 0000000..03bd660
--- /dev/null
+++ b/util.h
@@ -0,0 +1,104 @@
+/*
+  $Header: /cvs/src/chrony/util.h,v 1.14 2003/04/01 20:07:20 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Various utility functions
+  */
+
+#ifndef GOT_UTIL_H
+#define GOT_UTIL_H
+
+#include "sysincl.h"
+
+#include "ntp.h"
+
+/* Convert a timeval into a floating point number of seconds */
+extern void UTI_TimevalToDouble(struct timeval *a, double *b);
+
+/* Convert a number of seconds expressed in floating point into a
+   timeval */
+extern void UTI_DoubleToTimeval(double a, struct timeval *b);
+
+/* Returns -1 if a comes earlier than b, 0 if a is the same time as b,
+   and +1 if a comes after b */
+extern int UTI_CompareTimevals(struct timeval *a, struct timeval *b);
+
+/* Normalise a struct timeval, by adding or subtracting seconds to bring
+   its microseconds field into range */
+extern void UTI_NormaliseTimeval(struct timeval *x);
+
+/* Calculate result = a - b */
+extern void UTI_DiffTimevals(struct timeval *result, struct timeval *a, struct timeval *b);
+
+/* Calculate result = a - b and return as a double */
+extern void UTI_DiffTimevalsToDouble(double *result, struct timeval *a, struct timeval *b);
+
+/* Add a double increment to a timeval to get a new one. 'start' is
+   the starting time, 'end' is the result that we return.  This is
+   safe to use if start and end are the same */
+extern void UTI_AddDoubleToTimeval(struct timeval *start, double increment, struct timeval *end);
+
+/* Calculate the average and difference (as a double) of two timevals */
+extern void UTI_AverageDiffTimevals(struct timeval *earlier, struct timeval *later, struct timeval *average, double *diff);
+
+/* Convert a timeval into a temporary string, largely for diagnostic
+   display */
+extern char *UTI_TimevalToString(struct timeval *tv);
+
+/* Convert an NTP timestamp into a temporary string, largely for
+   diagnostic display */
+extern char *UTI_TimestampToString(NTP_int64 *ts);
+
+/* Convert an IP address to dotted quad notation, for diagnostics */
+extern char *UTI_IPToDottedQuad(unsigned long ip);
+
+extern char *UTI_TimeToLogForm(time_t t);
+
+/* Adjust time following a frequency/offset change */
+extern void UTI_AdjustTimeval(struct timeval *old_tv, struct timeval *when, struct timeval *new_tv, double dfreq, double doffset);
+
+
+extern void UTI_TimevalToInt64(struct timeval *src, NTP_int64 *dest);
+
+extern void UTI_Int64ToTimeval(NTP_int64 *src, struct timeval *dest);
+
+/* Like assert(0) */
+
+#if defined(LINUX) && defined(__alpha__)
+#define CROAK(message) assert(0) /* Added JGH Feb 24 2001  FIXME */
+#else
+extern int croak(const char *file, int line, const char *msg);
+#define CROAK(message) croak(__FILE__, __LINE__, message);
+#endif
+
+#if defined (INLINE_UTILITIES)
+#define INLINE_STATIC inline static
+#include "util.c"
+#else
+#define INLINE_STATIC
+#endif /* defined (INLINE_UTILITIES) */
+
+#endif /* GOT_UTIL_H */
diff --git a/version.txt b/version.txt
new file mode 100644 (file)
index 0000000..d5a0b00
--- /dev/null
@@ -0,0 +1,4 @@
+#ifndef VERSION_H
+#define VERSION_H
+#define PROGRAM_VERSION_STRING "$Name:  $"
+#endif /* VERSION_H */
diff --git a/wrap_adjtimex.c b/wrap_adjtimex.c
new file mode 100644 (file)
index 0000000..8d97f50
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+  $Header: /cvs/src/chrony/wrap_adjtimex.c,v 1.9 2002/11/19 21:33:42 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  This is a wrapper around the Linux adjtimex system call.  It isolates the
+  inclusion of <linux/adjtimex.h> from the need to include other header files,
+  many of which conflict with those in <linux/...> on some recent distributions
+  (as of Jul 2000) using kernels around 2.2.16 onwards.
+
+  */
+
+#ifdef LINUX
+
+#define _LOOSE_KERNEL_NAMES
+
+#include <linux/time.h>
+#include <linux/timex.h>
+#include <asm/param.h>
+
+#include "wrap_adjtimex.h"
+
+/* This doesn't seem to be in any include files !! */
+
+extern int adjtimex(struct timex *);
+
+int
+TMX_SetTick(long tick)
+{
+  struct timex txc;
+  txc.modes = ADJ_TICK;
+  txc.tick = tick;
+  
+  return adjtimex(&txc);
+}
+
+int
+TMX_ApplyOffset(long *offset)
+{
+  struct timex txc;
+  int result;
+
+  txc.modes = ADJ_OFFSET_SINGLESHOT;
+  txc.offset = *offset;
+  result = adjtimex(&txc);
+  *offset = txc.offset;
+  return result;
+}
+
+int
+TMX_SetFrequency(double freq, long tick)
+{
+  struct timex txc;
+  
+  txc.modes = ADJ_TICK | ADJ_FREQUENCY | ADJ_STATUS;
+
+  txc.freq = (long)(freq * (double)(1 << SHIFT_USEC));
+  txc.tick = tick;
+  txc.status = STA_UNSYNC; /* Prevent any of the FLL/PLL stuff coming
+                              up */
+
+  return adjtimex(&txc);
+}
+
+int
+TMX_GetFrequency(double *freq)
+{
+  struct timex txc;
+  int result;
+  txc.modes = 0; /* pure read */
+  result = adjtimex(&txc);
+  *freq = txc.freq / (double)(1 << SHIFT_USEC);
+  return result;
+}
+
+int
+TMX_GetOffsetLeft(long *offset)
+{
+  struct timex txc;
+  int result;
+  txc.modes = 0; /* pure read */
+  result = adjtimex(&txc);
+  *offset = txc.offset;
+  return result;
+}
+
+int
+TMX_ReadCurrentParams(struct tmx_params *params)
+{
+  struct timex txc;
+  int result;
+  
+  txc.modes = 0; /* pure read */
+  result = adjtimex(&txc);
+
+  params->tick     = txc.tick;
+  params->offset   = txc.offset;
+  params->freq     = txc.freq;
+  params->dfreq    = txc.freq / (double)(1 << SHIFT_USEC);
+  params->maxerror = txc.maxerror;
+  params->esterror = txc.esterror;
+  
+  params->sta_pll       = (txc.status & STA_PLL);
+  params->sta_ppsfreq   = (txc.status & STA_PPSFREQ);
+  params->sta_ppstime   = (txc.status & STA_PPSTIME);
+  params->sta_fll       = (txc.status & STA_FLL);
+  params->sta_ins       = (txc.status & STA_INS);
+  params->sta_del       = (txc.status & STA_DEL);
+  params->sta_unsync    = (txc.status & STA_UNSYNC);
+  params->sta_freqhold  = (txc.status & STA_FREQHOLD);
+  params->sta_ppssignal = (txc.status & STA_PPSSIGNAL);
+  params->sta_ppsjitter = (txc.status & STA_PPSJITTER);
+  params->sta_ppswander = (txc.status & STA_PPSWANDER);
+  params->sta_ppserror  = (txc.status & STA_PPSERROR);
+  params->sta_clockerr  = (txc.status & STA_CLOCKERR);
+
+  params->constant  = txc.constant;
+  params->precision = txc.precision;
+  params->tolerance = txc.tolerance;
+  params->ppsfreq   = txc.ppsfreq;
+  params->jitter    = txc.jitter;
+  params->shift     = txc.shift;
+  params->stabil    = txc.stabil;
+  params->jitcnt    = txc.jitcnt;
+  params->calcnt    = txc.calcnt;
+  params->errcnt    = txc.errcnt;
+  params->stbcnt    = txc.stbcnt;
+
+  return result;
+}
+
+#endif
+
diff --git a/wrap_adjtimex.h b/wrap_adjtimex.h
new file mode 100644 (file)
index 0000000..7e44c6a
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+  $Header: /cvs/src/chrony/wrap_adjtimex.h,v 1.6 2002/11/19 21:33:42 richard Exp $
+
+  =======================================================================
+
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow  1997-2002
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  The header file for the adjtimex wrapper
+  */
+
+#ifndef GOT_WRAP_ADJTIMEX_H
+#define GOT_WRAP_ADJTIMEX_H
+
+/* Cut-down version of struct timex */
+struct tmx_params {
+  long tick;
+  long offset;
+  long freq;
+  double dfreq;
+  long maxerror;
+  long esterror;
+  
+  unsigned sta_pll:1;
+  unsigned sta_ppsfreq:1;  
+  unsigned sta_ppstime:1;
+  unsigned sta_fll:1;
+  unsigned sta_ins:1;
+  unsigned sta_del:1;
+  unsigned sta_unsync:1;
+  unsigned sta_freqhold:1;
+  unsigned sta_ppssignal:1;
+  unsigned sta_ppsjitter:1;
+  unsigned sta_ppswander:1;
+  unsigned sta_ppserror:1;
+  unsigned sta_clockerr:1;
+  
+  int  status;
+  long constant;
+  long precision;
+  long tolerance;
+  long ppsfreq;
+  long jitter;
+  int  shift;
+  long stabil;
+  long jitcnt;
+  long calcnt;
+  long errcnt;
+  long stbcnt;
+};
+
+int TMX_SetTick(long tick);
+int TMX_ApplyOffset(long *offset);
+int TMX_SetFrequency(double freq, long tick);
+int TMX_GetFrequency(double *freq);
+int TMX_GetOffsetLeft(long *offset);
+int TMX_ReadCurrentParams(struct tmx_params *params);
+
+#endif  /* GOT_WRAP_ADJTIMEX_H */
+