diff --git a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java
index 38283aab56e4d4b6b5851ac6b099d32ae0e8f8b9..46660fb4e919312a159de8a262fe28a247b88187 100644
--- a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java
+++ b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java
@@ -574,40 +574,32 @@ public class Daemon {
                                             }
                                         }
                                         // reverse lookup, delete all
-                                        // There's no NamingService API to get a list of all reverse
-                                        String rev;
-                                        String rev2 = null;
-                                        while ((rev = router.reverseLookup(pod)) != null) {
-                                            // prevent getting stuck from buggy NS
-                                            if (rev.equals(rev2))
-                                                break;
-                                            rev2 = rev;
-                                            // forward check in case hash collision or something
-                                            List<Destination> fwd = router.lookupAll(rev);
-                                            if (!fwd.contains(pod))
-                                                break;  // can't go around again, fail
-                                            if (knownNames != null)
-                                                knownNames.remove(rev);
-                                            boolean success = router.remove(rev, pod);
-                                            if (success)
-                                                deleted++;
-                                            if (log != null) {
+                                        List<String> revs = router.reverseLookupAll(pod);
+                                        if (revs != null) {
+                                            for (String rev : revs) {
+                                                if (knownNames != null)
+                                                    knownNames.remove(rev);
+                                                boolean success = router.remove(rev, pod);
                                                 if (success)
-                                                    log.append("Removed: " + rev +
-                                                               " as requested" +
-                                                               ". From: " + addressbook.getLocation());
-                                                else
-                                                    log.append("Remove failed for: " + rev +
-                                                               " as requested" +
-                                                               ". From: " + addressbook.getLocation());
-                                            }
-                                            // now update the published addressbook
-                                            if (published != null) {
-                                                if (publishedNS == null)
-                                                    publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
-                                                success = publishedNS.remove(rev, pod);
-                                                if (log != null && !success)
-                                                    log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + rev);
+                                                    deleted++;
+                                                if (log != null) {
+                                                    if (success)
+                                                        log.append("Removed: " + rev +
+                                                                   " as requested" +
+                                                                   ". From: " + addressbook.getLocation());
+                                                    else
+                                                        log.append("Remove failed for: " + rev +
+                                                                   " as requested" +
+                                                                   ". From: " + addressbook.getLocation());
+                                                }
+                                                // now update the published addressbook
+                                                if (published != null) {
+                                                    if (publishedNS == null)
+                                                        publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
+                                                    success = publishedNS.remove(rev, pod);
+                                                    if (log != null && !success)
+                                                        log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + rev);
+                                                }
                                             }
                                         }
                                     } else {
diff --git a/apps/i2ptunnel/jsp/register.jsp b/apps/i2ptunnel/jsp/register.jsp
index e16e3ad346a67ff57b065bbc30ce011074e54914..0dfb2e10742ce6b967231479779f5e641653e2a6 100644
--- a/apps/i2ptunnel/jsp/register.jsp
+++ b/apps/i2ptunnel/jsp/register.jsp
@@ -128,7 +128,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
                 <label for="signature">
                     <%=intl._t("Authentication for adding host")%>
                 </label>
-                <textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeProps(out); %></textarea>               
+                <textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.write(out); %></textarea>               
             </div>
         </div>
         <div id="tunnelAdvancedNetworking" class="panel">
@@ -180,7 +180,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
                    props.setProperty(HostTxtEntry.PROP_ACTION, HostTxtEntry.ACTION_CHANGENAME);
                    props.setProperty(HostTxtEntry.PROP_OLDNAME, oldname);
                    he.sign(spk);
-                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeProps(out); %></textarea>               
+                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.write(out); %></textarea>               
                 <span class="comment"><%=intl._t("This will change the name from {0} to {1}, using the same destination", oldname, name)%></span>
 <%
                } else {
@@ -203,7 +203,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
                    props.setProperty(HostTxtEntry.PROP_ACTION, HostTxtEntry.ACTION_ADDNAME);
                    props.setProperty(HostTxtEntry.PROP_OLDNAME, oldname);
                    he.sign(spk);
-                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeProps(out); %></textarea>               
+                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.write(out); %></textarea>               
                 <span class="comment"><%=intl._t("This will add an alias {0} for {1}, using the same destination", name, oldname)%></span>
 <%
                } else {
@@ -227,7 +227,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
                    props.setProperty(HostTxtEntry.PROP_OLDDEST, olddest);
                    he.signInner(spk2);
                    he.sign(spk);
-                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeProps(out); %></textarea>               
+                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.write(out); %></textarea>               
                 <span class="comment"><%=intl._t("This will change the destination for {0}", name)%></span>
 <%
                } else {
@@ -252,7 +252,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
                    props.setProperty(HostTxtEntry.PROP_OLDDEST, olddest);
                    he.signInner(spk2);
                    he.sign(spk);
-                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeProps(out); %></textarea>               
+                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.write(out); %></textarea>               
                 <span class="comment"><%=intl._t("This will add an alternate destination for {0}", name)%></span>
 <%
                } else {
@@ -278,7 +278,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
                    props.setProperty(HostTxtEntry.PROP_OLDDEST, olddest);
                    he.signInner(spk2);
                    he.sign(spk);
-                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeProps(out); %></textarea>               
+                %><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.write(out); %></textarea>               
                 <span class="comment"><%=intl._t("This will add a subdomain {0} of {1}, with a different destination", name, oldname)%></span>
 <%
                } else {
diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java
index 5dd389adac9871bea63be4300bdd9d4bfd006f97..d51fec18b05486cd2aa038ce825c492343463f5c 100644
--- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java
+++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java
@@ -620,10 +620,10 @@ public class BlockfileNamingService extends DummyNamingService {
      *  Returns null without exception on error (logs only).
      *  Returns without logging if no reverse skiplist (version 1).
      *
-     *  @return the first one found if more than one
-     *  @since 0.8.9
+     *  @return all found if more than one
+     *  @since 0.9.26 from getReverseEntry() 0.8.9
      */
-    private String getReverseEntry(Hash hash) {
+    private List<String> getReverseEntries(Hash hash) {
         try {
             SkipList<Integer, Properties> rev = _bf.getIndex(REVERSE_SKIPLIST, _hashIndexSerializer, _infoSerializer);
             if (rev == null)
@@ -633,13 +633,21 @@ public class BlockfileNamingService extends DummyNamingService {
             Properties props = rev.get(idx);
             if (props == null)
                 return null;
-            for (Object okey : props.keySet()) {
-                String key = (String) okey;
+            List<String> rv = new ArrayList<String>(props.size());
+            for (String key : props.stringPropertyNames()) {
                 // now do the forward lookup to verify (using the cache)
-                Destination d = lookup(key);
-                if (d != null && d.calculateHash().equals(hash))
-                    return key;
+                List<Destination> ld = lookupAll(key);
+                if (ld != null) {
+                    for (Destination d : ld) {
+                        if (d.calculateHash().equals(hash)) {
+                            rv.add(key);
+                            break;
+                        }
+                    }
+                }
             }
+            if (!rv.isEmpty())
+                return rv;
         } catch (IOException ioe) {
             _log.error("DB get reverse error", ioe);
         } catch (RuntimeException e) {
@@ -1385,10 +1393,33 @@ public class BlockfileNamingService extends DummyNamingService {
      */
     @Override
     public String reverseLookup(Hash h) {
+        List<String> ls;
+        synchronized(_bf) {
+            if (_isClosed)
+                return null;
+            ls = getReverseEntries(h);
+        }
+        return (ls != null) ? ls.get(0) : null;
+    }
+
+    /**
+     * @param options ignored
+     * @since 0.9.26
+     */
+    @Override
+    public List<String> reverseLookupAll(Destination d, Properties options) {
+        return reverseLookupAll(d.calculateHash());
+    }
+
+    /**
+     * @since 0.9.26
+     */
+    @Override
+    public List<String> reverseLookupAll(Hash h) {
         synchronized(_bf) {
             if (_isClosed)
                 return null;
-            return getReverseEntry(h);
+            return getReverseEntries(h);
         }
     }
 
diff --git a/core/java/src/net/i2p/client/naming/HostTxtEntry.java b/core/java/src/net/i2p/client/naming/HostTxtEntry.java
index d68a4c80c16e94d57fedb226e8ba35ad8ad3e4c7..8d6f967e01f955d5409a925eabf85032e2c15055 100644
--- a/core/java/src/net/i2p/client/naming/HostTxtEntry.java
+++ b/core/java/src/net/i2p/client/naming/HostTxtEntry.java
@@ -134,13 +134,21 @@ public class HostTxtEntry {
      * Includes newline.
      */
     public void write(BufferedWriter out) throws IOException {
+        write((Writer) out);
+        out.newLine();
+    }
+
+    /**
+     * Write as a standard line name=dest[#!k1=v1#k2=v2...]
+     * Does not include newline.
+     */
+    public void write(Writer out) throws IOException {
         if (name != null && dest != null) {
             out.write(name);
             out.write(KV_SEPARATOR);
             out.write(dest);
         }
         writeProps(out);
-        out.newLine();
     }
 
     /**
diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java
index 8d3d9e03ff9ea60ff44cfc6a72da099915a6f1ef..d71702d88b25c17330dd4ed1ab62a5b3b02790b2 100644
--- a/core/java/src/net/i2p/client/naming/NamingService.java
+++ b/core/java/src/net/i2p/client/naming/NamingService.java
@@ -691,6 +691,51 @@ public abstract class NamingService {
         return remove(hostname, options);
     }
 
+    /**
+     * Reverse lookup a hash.
+     * This implementation returns the result from reverseLookup, or null.
+     * Subclasses implementing reverse lookups should override.
+     *
+     * @param h non-null
+     * @return a non-empty list of host names for this hash, or <code>null</code>
+     * if none is known. It is safe for subclasses to always return
+     * <code>null</code> if no reverse lookup is possible.
+     * @since 0.9.26
+     */
+    public List<String> reverseLookupAll(Hash h) {
+        String s = reverseLookup(h);
+        return (s != null) ? Collections.singletonList(s) : null;
+    }
+
+    /**
+     * Reverse lookup a destination
+     * This implementation returns reverseLookupAll(dest, null).
+     *
+     * @param dest non-null
+     * @return a non-empty list of host names for this Destination, or <code>null</code>
+     * if none is known. It is safe for subclasses to always return
+     * <code>null</code> if no reverse lookup is possible.
+     * @since 0.9.26
+     */
+    public List<String> reverseLookupAll(Destination dest) {
+        return reverseLookupAll(dest, null);
+    }
+
+    /**
+     *  Same as reverseLookupAll(dest) but with options
+     *  This implementation returns the result from reverseLookup, or null.
+     *  Subclasses implementing reverse lookups should override.
+     *
+     *  @param d non-null
+     *  @param options NamingService-specific, can be null
+     *  @return a non-empty list of host names for this Destination, or <code>null</code>
+     *  @since 0.9.26
+     */
+    public List<String> reverseLookupAll(Destination d, Properties options) {
+        String s = reverseLookup(d, options);
+        return (s != null) ? Collections.singletonList(s) : null;
+    }
+
     //// End new API for multiple Destinations
 
     /**