From 352396bdc28d21295470c0d39ec0b9ef45050e64 Mon Sep 17 00:00:00 2001
From: jrandom <jrandom>
Date: Fri, 13 Aug 2004 21:15:22 +0000
Subject: [PATCH] > Date: Fri, 13 Aug 2004 15:58:30 +1200 (NZST) > Message-ID:
 <1776.202.37.75.101.1092369510.squirrel@202.37.75.101> > From:
 adam@adambuckley.net > To: jrandom@i2p.net > > [...] > > I hereby authorize
 my NtpClient.java and NtpMessage.java code to be > redistributed under the
 BSD license for the purpose of integration with > the I2P project, providing
 that I am credited as the original author of > the code. > > [...] w00t! 
 adam++ code migrated into core/java/src/net/i2p/time, integrated with Clock,
 dropping that whole ugly pass-the-time-through-URL, and hence dropped support
 for :7655/setTime. New router.config properties to control the timestamper:  
 time.sntpServerList=pool.ntp.org,pool.ntp.org,pool.ntp.org  
 time.queryFrequencyMs=300000   time.disabled=false So, to disable, add
 time.disabled=true to your router.config.  It is enabled by default. Default
 router.config and startup scripts updated accordingly (since timestamper.jar
 is now gone)

---
 .../net/i2p/router/web/ConfigNetHandler.java  |   5 +-
 .../net/i2p/router/web/ConfigNetHelper.java   |   5 +-
 apps/routerconsole/jsp/config.jsp             |   6 +-
 apps/time/java/build.xml                      |  41 ----
 .../java/src/net/i2p/time/Timestamper.java    | 122 -----------
 build.xml                                     |   3 -
 .../java/src/net/i2p/time/NtpClient.java      |  44 ++--
 .../java/src/net/i2p/time/NtpMessage.java     |  44 ++--
 core/java/src/net/i2p/time/Timestamper.java   | 190 ++++++++++++++++++
 core/java/src/net/i2p/util/Clock.java         |   8 +-
 installer/java/build.xml                      |   5 -
 installer/java/src/Install.java               |   4 -
 installer/java/src/install.config             |   2 -
 installer/java/src/router.config.template     |  61 ++----
 installer/java/src/startRouter.bat.template   |   2 +-
 installer/java/src/startRouter.sh.template    |   2 +-
 .../src/net/i2p/router/admin/AdminRunner.java |  56 ------
 17 files changed, 287 insertions(+), 313 deletions(-)
 delete mode 100644 apps/time/java/build.xml
 delete mode 100644 apps/time/java/src/net/i2p/time/Timestamper.java
 rename {apps/time => core}/java/src/net/i2p/time/NtpClient.java (76%)
 rename {apps/time => core}/java/src/net/i2p/time/NtpMessage.java (90%)
 create mode 100644 core/java/src/net/i2p/time/Timestamper.java

diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
index ca096ea009..3d7f4867c2 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
@@ -17,6 +17,7 @@ import java.util.List;
 import java.util.Set;
 
 import net.i2p.util.Log;
+import net.i2p.time.Timestamper;
 
 import net.i2p.router.RouterContext;
 import net.i2p.router.ClientTunnelSettings;
@@ -221,9 +222,9 @@ public class ConfigNetHandler extends FormHandler {
         updateRates();
         
         if (_timeSyncEnabled) {
-            System.setProperty("timestamper.enabled", "true");
+            _context.router().setConfigSetting(Timestamper.PROP_DISABLED, "false");
         } else {
-            System.setProperty("timestamper.enabled", "false");
+            _context.router().setConfigSetting(Timestamper.PROP_DISABLED, "false");
         }
         
         boolean saved = _context.router().saveConfig();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
index 6a788c3240..af0e441115 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.Iterator;
 import java.util.TreeMap;
 
+import net.i2p.time.Timestamper;
 import net.i2p.util.Log;
 
 import net.i2p.router.RouterContext;
@@ -50,8 +51,8 @@ public class ConfigNetHelper {
     }
     
     public String getEnableTimeSyncChecked() {
-        String enabled = System.getProperty("timestamper.enabled");
-        if ( (enabled == null) || (!"true".equals(enabled)) )
+        String enabled = _context.getProperty(Timestamper.PROP_DISABLED, "true");
+        if ( (enabled == null) || (!"true".equalsIgnoreCase(enabled)) )
             return "";
         else
             return " checked ";
diff --git a/apps/routerconsole/jsp/config.jsp b/apps/routerconsole/jsp/config.jsp
index 83716ebb97..7558e9ddcc 100644
--- a/apps/routerconsole/jsp/config.jsp
+++ b/apps/routerconsole/jsp/config.jsp
@@ -34,10 +34,8 @@
  to <a href="http://www.whatismyip.com/">www.whatismyip.com</a>.</i>
  <hr />
  <b>Enable internal time synchronization?</b> <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
- <i>If disabled, your machine <b>must</b> be NTP synchronized.  This option only 
-    takes effect for the current run - if your machine is always synchronized within
-    (a few seconds), you can update your configuration so that it doesn't start the
-    "Timestamper" app (which would make this option irrelevent)</i>
+ <i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
+    be within a few seconds of "correct".</i>
  <hr />
  <b>Bandwidth limiter</b><br />
  <b>Inbound rate</b>: 
diff --git a/apps/time/java/build.xml b/apps/time/java/build.xml
deleted file mode 100644
index 768d6e2164..0000000000
--- a/apps/time/java/build.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project basedir="." default="all" name="time">
-    <target name="all" depends="clean, build" />
-    <target name="build" depends="builddep, jar" />
-    <target name="builddep">
-        <ant dir="../../../core/java/" target="build" />
-    </target>
-    <target name="compile">
-        <mkdir dir="./build" />
-        <mkdir dir="./build/obj" />
-        <javac srcdir="./src" debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj" includes="**/*.java" classpath="../../../core/java/build/i2p.jar" />
-    </target>
-    <target name="jar" depends="compile">
-        <jar destfile="./build/timestamper.jar" basedir="./build/obj" includes="**/*.class">
-            <manifest>
-                <attribute name="Main-Class" value="net.i2p.time.Timestamper" />
-                <attribute name="Class-Path" value="i2p.jar timestamper.jar" />
-            </manifest>
-        </jar>
-    </target>
-    <target name="javadoc">
-        <mkdir dir="./build" />
-        <mkdir dir="./build/javadoc" />
-        <javadoc 
-            sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc" 
-            packagenames="*" 
-            use="true" 
-            access="package"
-            splitindex="true" 
-            windowtitle="I2P timestamper" />
-    </target>
-    <target name="clean">
-        <delete dir="./build" />
-    </target>
-    <target name="cleandep" depends="clean">
-        <ant dir="../../../core/java/" target="cleandep" />
-    </target>
-    <target name="distclean" depends="clean">
-        <ant dir="../../../core/java/" target="distclean" />
-    </target>
-</project>
diff --git a/apps/time/java/src/net/i2p/time/Timestamper.java b/apps/time/java/src/net/i2p/time/Timestamper.java
deleted file mode 100644
index 7ecca982fd..0000000000
--- a/apps/time/java/src/net/i2p/time/Timestamper.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package net.i2p.time;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-import net.i2p.util.I2PThread;
-import net.i2p.util.Log;
-
-/**
- * Periodically query a series of NTP servers and post the offset 
- * to a given URL.  It tries the NTP servers in order, contacting them
- * using UDP port 123, and sends the current date to the URL specified
- * (specifically, URL+"&now=" + yyyyMMdd_HH:mm:ss.SSS in the UK locale).
- * It does this every 5 minutes, forever.
- *
- * Usage: <pre>
- *  Timestamper URL ntpServer1[ ntpServer2]*
- * </pre>
- */
-public class Timestamper implements Runnable {
-    private static Log _log = new Log(Timestamper.class);
-    private static String _targetURL;
-    private static String _serverList[];
-    
-    private int DELAY_MS = 5*60*1000;
-    
-    public Timestamper() {}
-    
-    public void startTimestamper() {
-        if (_log.shouldLog(Log.INFO))
-            _log.info("Starting timestamper pointing at " + _targetURL);
-        synchronized (Timestamper.class) {
-            String enabled = System.getProperty("timestamper.started");
-            if (enabled != null) {
-                _log.warn("Timestamper already running");
-                return;
-            } else {
-                System.setProperty("timestamper.started", "true");
-                System.setProperty("timestamper.enabled", "true");
-            }
-        }
-        I2PThread t = new I2PThread(this, "Timestamper");
-        t.setPriority(I2PThread.MIN_PRIORITY);
-        t.start();
-    }
-    
-    public void run() {
-        if (_log.shouldLog(Log.INFO))
-            _log.info("Starting up timestamper");
-        try {
-            while (true) {
-                String enabled = System.getProperty("timestamper.enabled");
-                if ( (enabled == null) || (!"true".equals(enabled)) ) {
-                    if (_log.shouldLog(Log.DEBUG))
-                        _log.debug("Not stamping the time");
-                } else {
-                    if (_log.shouldLog(Log.DEBUG))
-                        _log.debug("Querying servers " + _serverList);
-                    try {
-                        long now = NtpClient.currentTime(_serverList);
-                        if (_log.shouldLog(Log.DEBUG))
-                            _log.debug("Stamp time");
-                        stampTime(now);
-                    } catch (IllegalArgumentException iae) {
-                        _log.log(Log.CRIT, "Unable to reach any of the NTP servers - network disconnected?");
-                    }
-                }
-                try { Thread.sleep(DELAY_MS); } catch (InterruptedException ie) {}
-            }
-        } catch (Throwable t) {
-            _log.log(Log.CRIT, "Timestamper died!", t);
-        }
-    }
-    
-    /**
-     * Send an HTTP request to a given URL specifying the current time 
-     */
-    private void stampTime(long now) {
-        try {
-            String toRequest = _targetURL + "&now=" + getNow(now);
-            if (_log.shouldLog(Log.DEBUG))
-                _log.debug("Stamping [" + toRequest + "]");
-            URL url = new URL(toRequest);
-            Object o = url.getContent();
-            // ignore the content
-        } catch (MalformedURLException mue) {
-            _log.error("Invalid URL", mue);
-        } catch (IOException ioe) {
-            _log.error("Error stamping the time", ioe);
-        }
-    }
-    
-    private SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd_HH:mm:ss.SSS", Locale.UK);
-    private String getNow(long now) {
-        synchronized (_fmt) {
-            return _fmt.format(new Date(now));
-        }
-    }
-    
-    public static void main(String args[]) {
-        if ( (args == null) || (args.length < 2) ) {
-            usage();
-            return;
-            //args = new String[] { "http://dev.i2p.net:80/somePath?pass=password", "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
-        } 
-        String servers[] = new String[args.length-1];
-        System.arraycopy(args, 1, servers, 0, servers.length);
-        _targetURL = args[0];
-        _serverList = servers;
-        Timestamper ts = new Timestamper();
-        ts.startTimestamper();
-    }
-    
-    private static void usage() {
-        System.err.println("Usage: Timestamper URL ntpServer[ ntpServer]*");
-        _log.error("Usage: Timestamper URL ntpServer[ ntpServer]*");
-    }
-}
\ No newline at end of file
diff --git a/build.xml b/build.xml
index 60872ca7cb..62ab21181c 100644
--- a/build.xml
+++ b/build.xml
@@ -27,7 +27,6 @@
         <ant dir="apps/sam/java/" target="jar" />
         <ant dir="apps/heartbeat/java/" target="jar" />
         <ant dir="apps/netmonitor/java/" target="jar" />
-        <ant dir="apps/time/java/" target="jar" />
         <ant dir="apps/routerconsole/java/" target="jar" />
         <ant dir="installer/java/" target="jar" />
     </target>
@@ -43,7 +42,6 @@
         <copy file="apps/sam/java/build/sam.jar" todir="build/" />
         <copy file="apps/heartbeat/java/build/heartbeat.jar" todir="build/" />
         <copy file="apps/netmonitor/java/build/netmonitor.jar" todir="build/" />
-        <copy file="apps/time/java/build/timestamper.jar" todir="build/" />
         <copy file="installer/java/build/install.jar" todir="build/" />
         <copy file="installer/java/build/guiinstall.jar" todir="build/" />
         <copy file="installer/java/build/fetchseeds.jar" todir="build/" />
@@ -74,7 +72,6 @@
         <ant dir="apps/heartbeat/java/" target="distclean" />
         <ant dir="apps/netmonitor/java/" target="distclean" />
         <ant dir="apps/routerconsole/java/" target="distclean" />
-        <ant dir="apps/time/java/" target="distclean" />
         <ant dir="installer/java/" target="distclean" />
         <delete>
             <fileset dir="." includes="**/*.class" />
diff --git a/apps/time/java/src/net/i2p/time/NtpClient.java b/core/java/src/net/i2p/time/NtpClient.java
similarity index 76%
rename from apps/time/java/src/net/i2p/time/NtpClient.java
rename to core/java/src/net/i2p/time/NtpClient.java
index aa50ead4d2..5360f8d301 100644
--- a/apps/time/java/src/net/i2p/time/NtpClient.java
+++ b/core/java/src/net/i2p/time/NtpClient.java
@@ -1,5 +1,33 @@
 package net.i2p.time;
-
+/*
+ * Copyright (c) 2004, Adam Buckley
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * - Redistributions of source code must retain the above copyright notice, 
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice, 
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of Adam Buckley nor the names of its contributors may be 
+ *   used to endorse or promote products derived from this software without 
+ *   specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.DatagramPacket;
@@ -17,20 +45,6 @@ import java.net.InetAddress;
  * Note that on windows platforms, the curent time-of-day timestamp is limited
  * to an resolution of 10ms and adversely affects the accuracy of the results.
  *
- *
- * This code is copyright (c) Adam Buckley 2004
- *
- * 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.  A HTML version of the GNU General Public License can be
- * seen at http://www.gnu.org/licenses/gpl.html
- *
- * 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.
- *
  * @author Adam Buckley
  * (minor refactoring by jrandom)
  */
diff --git a/apps/time/java/src/net/i2p/time/NtpMessage.java b/core/java/src/net/i2p/time/NtpMessage.java
similarity index 90%
rename from apps/time/java/src/net/i2p/time/NtpMessage.java
rename to core/java/src/net/i2p/time/NtpMessage.java
index c7076a9565..f7626b55b0 100644
--- a/apps/time/java/src/net/i2p/time/NtpMessage.java
+++ b/core/java/src/net/i2p/time/NtpMessage.java
@@ -1,4 +1,33 @@
 package net.i2p.time;
+/*
+ * Copyright (c) 2004, Adam Buckley
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * - Redistributions of source code must retain the above copyright notice, 
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice, 
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of Adam Buckley nor the names of its contributors may be 
+ *   used to endorse or promote products derived from this software without 
+ *   specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
 
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
@@ -34,21 +63,6 @@ import java.util.Date;
  * socket.receive(packet);
  * System.out.println(msg.toString());
  *
- *
- * This code is copyright (c) Adam Buckley 2004
- *
- * 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.  A HTML version of the GNU General Public License can be
- * seen at http://www.gnu.org/licenses/gpl.html
- *
- * 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.
- *
- *
  * Comments for member variables are taken from RFC2030 by David Mills,
  * University of Delaware.
  *
diff --git a/core/java/src/net/i2p/time/Timestamper.java b/core/java/src/net/i2p/time/Timestamper.java
new file mode 100644
index 0000000000..1c0b7b0607
--- /dev/null
+++ b/core/java/src/net/i2p/time/Timestamper.java
@@ -0,0 +1,190 @@
+package net.i2p.time;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import net.i2p.I2PAppContext;
+import net.i2p.util.I2PThread;
+import net.i2p.util.Log;
+
+/**
+ * Periodically query a series of NTP servers and update any associated
+ * listeners.  It tries the NTP servers in order, contacting them using 
+ * SNTP (UDP port 123).  By default, it does this every 5 minutes, 
+ * forever.
+ */
+public class Timestamper implements Runnable {
+    private I2PAppContext _context;
+    private Log _log;
+    private List _servers;
+    private List _listeners;
+    private int _queryFrequency;
+    private boolean _disabled;
+    
+    private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000;
+    private static final String DEFAULT_SERVER_LIST = "pool.ntp.org, pool.ntp.org";
+    private static final boolean DEFAULT_DISABLED = false;
+    
+    public static final String PROP_QUERY_FREQUENCY = "time.queryFrequencyMs";
+    public static final String PROP_SERVER_LIST = "time.sntpServerList";
+    public static final String PROP_DISABLED = "time.disabled";
+    
+    public Timestamper(I2PAppContext ctx) {
+        this(ctx, null);
+    }
+    
+    public Timestamper(I2PAppContext ctx, UpdateListener lsnr) {
+        _context = ctx;
+        _servers = new ArrayList(1);
+        _listeners = new ArrayList(1);
+        if (lsnr != null)
+            _listeners.add(lsnr);
+        updateConfig();
+        startTimestamper();
+    }
+    
+    public int getServerCount() { 
+        synchronized (_servers) {
+            return _servers.size(); 
+        }
+    }
+    public String getServer(int index) { 
+        synchronized (_servers) {
+            return (String)_servers.get(index); 
+        }
+    }
+    
+    public int getQueryFrequencyMs() { return _queryFrequency; }
+    
+    public boolean getIsDisabled() { return _disabled; }
+    
+    public void addListener(UpdateListener lsnr) {
+        synchronized (_listeners) {
+            _listeners.add(lsnr);
+        }
+    }
+    public void removeListener(UpdateListener lsnr) {
+        synchronized (_listeners) {
+            _listeners.remove(lsnr);
+        }
+    }
+    public int getListenerCount() {
+        synchronized (_listeners) {
+            return _listeners.size();
+        }
+    }
+    public UpdateListener getListener(int index) {
+        synchronized (_listeners) {
+            return (UpdateListener)_listeners.get(index);
+        }
+    }
+    
+    private void startTimestamper() {
+        I2PThread t = new I2PThread(this, "Timestamper");
+        t.setPriority(I2PThread.MIN_PRIORITY);
+        t.start();
+    }
+    
+    public void run() {
+        try { Thread.sleep(1000); } catch (InterruptedException ie) {}
+        _log = _context.logManager().getLog(Timestamper.class);
+        if (_log.shouldLog(Log.INFO))
+            _log.info("Starting timestamper");
+
+        if (_log.shouldLog(Log.INFO))
+            _log.info("Starting up timestamper");
+        try {
+            while (true) {
+                if (!_disabled) {
+                    String serverList[] = null;
+                    synchronized (_servers) {
+                        serverList = new String[_servers.size()];
+                        for (int i = 0; i < serverList.length; i++)
+                            serverList[i] = (String)_servers.get(i);
+                    }
+                    if (_log.shouldLog(Log.DEBUG))
+                        _log.debug("Querying servers " + _servers);
+                    try {
+                        long now = NtpClient.currentTime(serverList);
+                        if (_log.shouldLog(Log.DEBUG))
+                            _log.debug("Stamp time");
+                        stampTime(now);
+                    } catch (IllegalArgumentException iae) {
+                        _log.log(Log.CRIT, "Unable to reach any of the NTP servers - network disconnected?");
+                    }
+                }
+                updateConfig();
+                try { Thread.sleep(_queryFrequency); } catch (InterruptedException ie) {}
+            }
+        } catch (Throwable t) {
+            _log.log(Log.CRIT, "Timestamper died!", t);
+        }
+    }
+    
+    /**
+     * Send an HTTP request to a given URL specifying the current time 
+     */
+    private void stampTime(long now) {
+        synchronized (_listeners) {
+            for (int i = 0; i < _listeners.size(); i++) {
+                UpdateListener lsnr = (UpdateListener)_listeners.get(i);
+                lsnr.setNow(now);
+            }
+        }
+    }
+ 
+    /**
+     * Reload all the config elements from the appContext
+     *
+     */
+    private void updateConfig() {
+        String serverList = _context.getProperty(PROP_SERVER_LIST);
+        if ( (serverList == null) || (serverList.trim().length() <= 0) )
+            serverList = DEFAULT_SERVER_LIST;
+        synchronized (_servers) {
+            StringTokenizer tok = new StringTokenizer(serverList, ",");
+            while (tok.hasMoreTokens()) {
+                String val = (String)tok.nextToken();
+                val = val.trim();
+                if (val.length() > 0)
+                    _servers.add(val);
+            }
+        }
+        
+        String freq = _context.getProperty(PROP_QUERY_FREQUENCY);
+        if ( (freq == null) || (freq.trim().length() <= 0) )
+            freq = DEFAULT_QUERY_FREQUENCY + "";
+        try {
+            int ms = Integer.parseInt(freq);
+            if (ms > 60*1000) {
+                _queryFrequency = ms;
+            } else {
+                if ( (_log != null) && (_log.shouldLog(Log.ERROR)) )
+                    _log.error("Query frequency once every " + ms + "ms is too fast!");
+                _queryFrequency = DEFAULT_QUERY_FREQUENCY;
+            }
+        } catch (NumberFormatException nfe) {
+            if ( (_log != null) && (_log.shouldLog(Log.WARN)) )
+                _log.warn("Invalid query frequency [" + freq + "], falling back on " + DEFAULT_QUERY_FREQUENCY);
+            _queryFrequency = DEFAULT_QUERY_FREQUENCY;
+        }
+        
+        String disabled = _context.getProperty(PROP_DISABLED);
+        if (disabled == null)
+            disabled = DEFAULT_DISABLED + "";
+        _disabled = Boolean.getBoolean(disabled);
+    }
+    
+    /**
+     * Interface to receive update notifications for when we query the time
+     *
+     */
+    public interface UpdateListener {
+        /**
+         * The time has been queried and we have a current value for 'now'
+         *
+         */
+        public void setNow(long now);
+    }
+}
\ No newline at end of file
diff --git a/core/java/src/net/i2p/util/Clock.java b/core/java/src/net/i2p/util/Clock.java
index a81497ba7e..304f06a26e 100644
--- a/core/java/src/net/i2p/util/Clock.java
+++ b/core/java/src/net/i2p/util/Clock.java
@@ -5,6 +5,7 @@ import java.util.Iterator;
 import java.util.Set;
 
 import net.i2p.I2PAppContext;
+import net.i2p.time.Timestamper;
 
 /**
  * Alternate location for determining the time which takes into account an offset.
@@ -13,18 +14,23 @@ import net.i2p.I2PAppContext;
  * (such as an NTP synchronized clock).
  *
  */
-public class Clock {
+public class Clock implements Timestamper.UpdateListener {
     private I2PAppContext _context;
+    private Timestamper _timestamper;
+    
     public Clock(I2PAppContext context) {
         _context = context;
         _offset = 0;
         _alreadyChanged = false;
         _listeners = new HashSet(64);
+        _timestamper = new Timestamper(context, this);
     }
     public static Clock getInstance() {
         return I2PAppContext.getGlobalContext().clock();
     }
     
+    public Timestamper getTimestamper() { return _timestamper; }
+    
     /** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
     private Log getLog() { return _context.logManager().getLog(Clock.class); }
     
diff --git a/installer/java/build.xml b/installer/java/build.xml
index 6a4ba24af8..34e0670bba 100644
--- a/installer/java/build.xml
+++ b/installer/java/build.xml
@@ -10,7 +10,6 @@
         <ant dir="../../apps/sam/java/" target="build" />
         <ant dir="../../apps/netmonitor/java/" target="build" />
         <ant dir="../../apps/heartbeat/java/" target="build" />
-        <ant dir="../../apps/time/java/" target="build" />
     </target>
     <target name="compile">
         <mkdir dir="./build" />
@@ -40,7 +39,6 @@
             <fileset file="../../apps/sam/java/build/sam.jar" />
             <fileset file="../../apps/heartbeat/java/build/heartbeat.jar" />
             <fileset file="../../apps/netmonitor/java/build/netmonitor.jar" />
-            <fileset file="../../apps/time/java/build/timestamper.jar" />
             <fileset file="../doc/COPYING" />
             <fileset file="../../readme.txt" />
             <fileset file="../../hosts.txt" />
@@ -64,7 +62,6 @@
             <fileset file="../../apps/sam/java/build/sam.jar" />
             <fileset file="../../apps/heartbeat/java/build/heartbeat.jar" />
             <fileset file="../../apps/netmonitor/java/build/netmonitor.jar" />
-            <fileset file="../../apps/time/java/build/timestamper.jar" />
             <fileset file="../doc/COPYING" />
             <fileset file="../../readme.txt" />
             <fileset file="../../hosts.txt" />
@@ -86,7 +83,6 @@
         <ant dir="../../apps/sam/java/" target="cleandep" />
         <ant dir="../../apps/heartbeat/java" target="cleandep" />
         <ant dir="../../apps/netmonitor/java" target="cleandep" />
-        <ant dir="../../apps/time/java" target="cleandep" />
     </target>
     <target name="distclean" depends="clean">
         <ant dir="../../core/java/" target="distclean" />
@@ -96,6 +92,5 @@
         <ant dir="../../apps/sam/java/" target="distclean" />
         <ant dir="../../apps/heartbeat/java" target="distclean" />
         <ant dir="../../apps/netmonitor/java" target="distclean" />
-        <ant dir="../../apps/time/java" target="distclean" />
     </target>
 </project>
diff --git a/installer/java/src/Install.java b/installer/java/src/Install.java
index 37a6b92bb1..28de764e79 100644
--- a/installer/java/src/Install.java
+++ b/installer/java/src/Install.java
@@ -325,10 +325,6 @@ public abstract class Install {
         _i2cpPort = ((Integer)_answers.get("i2cpPort")).intValue();
         _inBPS = ((Integer)_answers.get("inBPS")).intValue();
         _outBPS = ((Integer)_answers.get("outBPS")).intValue();
-        long num = new java.util.Random().nextLong();
-        if (num < 0)
-            num = 0 - num;
-        _answers.put("timestamperPassword", new Long(num));
     }
 
     private void useTemplate(String templateName, File destFile) {
diff --git a/installer/java/src/install.config b/installer/java/src/install.config
index 99b0acb553..3ae943c1f7 100644
--- a/installer/java/src/install.config
+++ b/installer/java/src/install.config
@@ -115,5 +115,3 @@ libs.0012.name=harvester.config
     libs.0012.islib=false
 libs.0013.name=heartbeat.config
     libs.0013.islib=false
-libs.0014.name=timestamper.jar
-    libs.0014.islib=true
\ No newline at end of file
diff --git a/installer/java/src/router.config.template b/installer/java/src/router.config.template
index a2de6049c0..bbe7b48d1a 100644
--- a/installer/java/src/router.config.template
+++ b/installer/java/src/router.config.template
@@ -109,13 +109,7 @@ tunnels.tunnelDuration=600000
 # http://localhost:7655/shutdown?password=thisIsASecret)
 #router.shutdownPassword=thisIsASecret
 
-#
-# the remaining lines describe how you can get your router to fire up client 
-# applications it is up and running, all within the router's JVM.  Uncomment the
-# ones you want (revising the numbers and ports accordingly)
-
-# Keep the router's clock in sync by querying one of the specified NTP servers once
-# a minute (uses UDP port 123)
+# Comma delimited list of SNTP servers to query.  pool.ntp.org is a DNS trick to
 # This defaults to the DNS round-robin ntp pool - see http://www.pool.ntp.org/
 # Please change the NTP server specified to include ones closer to you - see 
 # http://www.eecis.udel.edu/~mills/ntp/clock2a.html for a list (you can specify as
@@ -126,28 +120,30 @@ tunnels.tunnelDuration=600000
 #  BR: ntp1.pucpr.br
 #  BE: ntp2.belbone.be
 #  AU: ntp.saard.net
-clientApp.0.main=net.i2p.time.Timestamper
-clientApp.0.name=Timestamper
-clientApp.0.onBoot=true
-clientApp.0.args=http://localhost:7655/setTime?##timestamperPassword## pool.ntp.org pool.ntp.org pool.ntp.org
+time.sntpServerList=pool.ntp.org,pool.ntp.org,pool.ntp.org
+
+# Query an SNTP server every 5 minutes
+# time.queryFrequencyMs=300000
 
-# The admin time passphrase, used to prevent unauthorized people from updating your
-# routers time.  The value should be included in the timestamper's args above, 
-# otherwise it wont honor timestamp updates.  You shouldnt include any spaces or funky 
-# characters - just pick some random numbers.
-adminTimePassphrase=##timestamperPassword##
+# If you really really know that your computer's clock is ALWAYS correct, set this property
+# time.disabled=true
+
+#
+# the remaining lines describe how you can get your router to fire up client 
+# applications it is up and running, all within the router's JVM.  Uncomment the
+# ones you want (revising the numbers and ports accordingly)
 
 # SAM bridge (a simplified socket based protocol for using I2P - listens on port 7656.  see
 # the specs at http://www.i2p.net/node/view/144 for more info)
-clientApp.1.main=net.i2p.sam.SAMBridge
-clientApp.1.name=SAMBridge
-clientApp.1.args=sam.keys 0.0.0.0 7656 i2cp.tcp.host=localhost i2cp.tcp.port=##_router_i2cp_port##
+clientApp.0.main=net.i2p.sam.SAMBridge
+clientApp.0.name=SAMBridge
+clientApp.0.args=sam.keys 0.0.0.0 7656 i2cp.tcp.host=localhost i2cp.tcp.port=##_router_i2cp_port##
 
 # The eepProxy (HTTP proxy that lets you browse both eepsites and the normal web via squid.i2p) and
 # the ircProxy (which connects to the anonymously hosted ircd at irc.duck.i2p)
-clientApp.2.main=net.i2p.i2ptunnel.I2PTunnel
-clientApp.2.name=Tunnels
-clientApp.2.args=-nocli -e "config localhost ##_router_i2cp_port##" -e "httpclient 4444" -e "client 6668 irc.duck.i2p"
+clientApp.1.main=net.i2p.i2ptunnel.I2PTunnel
+clientApp.1.name=Tunnels
+clientApp.1.args=-nocli -e "config localhost ##_router_i2cp_port##" -e "httpclient 4444" -e "client 6668 irc.duck.i2p"
 # note: if you want the proxies to be reachable from other machines, add:
 #       -e "listen_on 0.0.0.0"   
 # before the -e "httpclient 4444".  otherwise, both of these proxies will only listen for connections on 127.0.0.1
@@ -157,10 +153,10 @@ clientApp.2.args=-nocli -e "config localhost ##_router_i2cp_port##" -e "httpclie
 # and add jetty-all.jar and routerconsole.jar in the router's classpath in the startRouter 
 # script
 # (don't bother trying to figure this out prior to the 0.4 release)
-#clientApp.3.main=net.i2p.router.web.RouterConsoleRunner
-#clientApp.3.name=webConsole
-#clientApp.3.args=7657 127.0.0.1 ./webapps/
-#clientApp.3.onBoot=true
+#clientApp.2.main=net.i2p.router.web.RouterConsoleRunner
+#clientApp.2.name=webConsole
+#clientApp.2.args=7657 127.0.0.1 ./webapps/
+#clientApp.2.onBoot=true
 
 # To require simple HTTP authentication for accessing any of the pages underneath the web console
 # (including any other webapps deployed), uncomment the following line and set the password
@@ -169,16 +165,3 @@ clientApp.2.args=-nocli -e "config localhost ##_router_i2cp_port##" -e "httpclie
 # settings, etc).  This is only used for the new jetty console (started in clientApp.3.* above)
 #
 #consolePassword=fooBarBaz
-
-# Network monitor (harvests data from the network database and stores it under 
-# monitorData/, and with the netviewer GUI you can browse through its results)
-#clientApp.4.main=net.i2p.netmonitor.NetMonitor
-#clientApp.4.name=NetMonitor
-#clientApp.4.args=
-
-# Heartbeat engine (ueber-simple ping/pong system, configured in heartbeat.config.  By itself
-# it just writes out stat data where its told to, but there's a seperate HeartbeatMonitor 
-# GUI to let you visualize things)
-#clientApp.5.main=net.i2p.heartbeat.Heartbeat
-#clientApp.5.name=Heartbeat
-#clientApp.5.args=heartbeat.config
diff --git a/installer/java/src/startRouter.bat.template b/installer/java/src/startRouter.bat.template
index e7267e5b1a..621fe4eec6 100644
--- a/installer/java/src/startRouter.bat.template
+++ b/installer/java/src/startRouter.bat.template
@@ -4,4 +4,4 @@ cd ##_scripts_installdir##
 
 REM the -XX args are workarounds for bugs in java 1.4.2's garbage collector
 
-java -cp lib\i2p.jar;lib\router.jar;lib\mstreaming.jar;lib\heartbeat.jar;lib\i2ptunnel.jar;lib\netmonitor.jar;lib\sam.jar;lib\timestamper.jar -Djava.library.path=. -DloggerFilenameOverride=logs\log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router
+java -cp lib\i2p.jar;lib\router.jar;lib\mstreaming.jar;lib\heartbeat.jar;lib\i2ptunnel.jar;lib\netmonitor.jar;lib\sam.jar -Djava.library.path=. -DloggerFilenameOverride=logs\log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router
diff --git a/installer/java/src/startRouter.sh.template b/installer/java/src/startRouter.sh.template
index 5785772588..ecf6d02a13 100644
--- a/installer/java/src/startRouter.sh.template
+++ b/installer/java/src/startRouter.sh.template
@@ -2,7 +2,7 @@
 cd ##_scripts_installdir##
 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
 # the -XX args are workarounds for bugs in java 1.4.2's garbage collector
-nohup nice java -cp lib/i2p.jar:lib/router.jar:lib/mstreaming.jar:lib/heartbeat.jar:lib/i2ptunnel.jar:lib/netmonitor.jar:lib/sam.jar:lib/timestamper.jar -Djava.library.path=. -DloggerFilenameOverride=logs/log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router --quiet > /dev/null &
+nohup nice java -cp lib/i2p.jar:lib/router.jar:lib/mstreaming.jar:lib/heartbeat.jar:lib/i2ptunnel.jar:lib/netmonitor.jar:lib/sam.jar -Djava.library.path=. -DloggerFilenameOverride=logs/log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router --quiet > /dev/null &
 # Save the pid just in case we ever want to stop the router
 echo $! > router.pid
 echo I2P Router started
diff --git a/router/java/src/net/i2p/router/admin/AdminRunner.java b/router/java/src/net/i2p/router/admin/AdminRunner.java
index 06143d4242..f8e16e5606 100644
--- a/router/java/src/net/i2p/router/admin/AdminRunner.java
+++ b/router/java/src/net/i2p/router/admin/AdminRunner.java
@@ -6,10 +6,7 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.Socket;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
 import java.util.Iterator;
-import java.util.Locale;
 import java.util.Set;
 
 import net.i2p.data.Hash;
@@ -58,13 +55,6 @@ class AdminRunner implements Runnable {
             }
         } else if (command.indexOf("/profile/") >= 0) {
             replyText(out, getProfile(command));
-        } else if (command.indexOf("setTime") >= 0) {
-            if (allowTimeUpdate(command)) {
-                setTime(command);
-                reply(out, "<html><body>Time updated</body></html>");
-            } else {
-                reply(out, "<html><body>Time not updated</body></html>");
-            }
         } else if (command.indexOf("/shutdown") >= 0) {
             reply(out, shutdown(command));
         } else if (true || command.indexOf("routerConsole.html") > 0) {
@@ -80,25 +70,6 @@ class AdminRunner implements Runnable {
         }
     }
     
-    private boolean allowTimeUpdate(String command) {
-        String pass = _context.getProperty("adminTimePassphrase");
-        if ( (pass == null) || (pass.trim().length() <= 0) ) {
-            if (_log.shouldLog(Log.ERROR))
-                _log.error("No passphrase for update time from " + _socket.getInetAddress() 
-                          + ":" + _socket.getPort());
-            return false;
-        }
-        
-        if (command.indexOf(pass) != -1) {
-            return true;
-        } else {
-            if (_log.shouldLog(Log.ERROR))
-                _log.error("Invalid passphrase for update time from " + _socket.getInetAddress() 
-                          + ":" + _socket.getPort());
-            return false;
-        }
-    }
-    
     private void reply(OutputStream out, String content) throws IOException {
         StringBuffer reply = new StringBuffer(10240);
         reply.append("HTTP/1.1 200 OK\n");
@@ -152,33 +123,6 @@ class AdminRunner implements Runnable {
         return "No such peer is being profiled\n";
     }
     
-    
-    private static final String FORMAT_STRING = "yyyyMMdd_HH:mm:ss.SSS";
-    private SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT_STRING, Locale.UK);
-    
-    private long getTime(String now) throws ParseException { 
-        synchronized (_fmt) {
-            return _fmt.parse(now).getTime();
-        }
-    }
-    private void setTime(String cmd) {
-        int start = cmd.indexOf("now=");
-        String str = cmd.substring(start + 4, start+4+FORMAT_STRING.length());
-        try {
-            long now = getTime(str);
-            if (_log.shouldLog(Log.INFO))
-                _log.log(Log.INFO, "Admin time set to " + str);
-            setTime(now);
-        } catch (ParseException pe) {
-            _log.error("Invalid time specified [" + str + "]", pe);
-        }
-    }
-    
-    private void setTime(long now) {
-        _context.clock().setNow(now);
-    }
-    
-    
     private static final String SHUTDOWN_PASSWORD_PROP = "router.shutdownPassword";
     private String shutdown(String cmd) {
         String password = _context.router().getConfigSetting(SHUTDOWN_PASSWORD_PROP);
-- 
GitLab