From ddd438de35fbe3d75565ba0a1660cac2e44a66b7 Mon Sep 17 00:00:00 2001 From: jrandom Date: Sun, 13 Nov 2005 14:15:26 +0000 Subject: [PATCH] protect against spoofing in syndie with a per-jvm-instance nonce (which should prevent the spurious "are you being spoofed" things) fixed the syndie previewing --- .../java/src/net/i2p/syndie/BlogManager.java | 2 + .../i2p/syndie/sml/ThreadedHTMLRenderer.java | 32 ++++++++- .../net/i2p/syndie/web/AddressesServlet.java | 12 ++++ .../src/net/i2p/syndie/web/AdminServlet.java | 3 + .../src/net/i2p/syndie/web/BaseServlet.java | 68 +++++++++++++++++-- .../src/net/i2p/syndie/web/PostServlet.java | 25 +++++-- .../net/i2p/syndie/web/ProfileServlet.java | 3 + .../src/net/i2p/syndie/web/SwitchServlet.java | 45 ++++++------ .../i2p/syndie/web/ViewThreadedServlet.java | 5 +- 9 files changed, 157 insertions(+), 38 deletions(-) diff --git a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java index cc15b6450..d250ea903 100644 --- a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java +++ b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java @@ -749,6 +749,8 @@ public class BlogManager { int split = line.indexOf('='); int split2 = line.indexOf(':'); if ( (split < 0) || ( (split2 > 0) && (split2 < split) ) ) split = split2; + if ( (split < 0) && (split2 < 0) ) + continue; String key = line.substring(0,split).trim(); String val = line.substring(split+1).trim(); raw.append(key).append(": ").append(val).append('\n'); diff --git a/apps/syndie/java/src/net/i2p/syndie/sml/ThreadedHTMLRenderer.java b/apps/syndie/java/src/net/i2p/syndie/sml/ThreadedHTMLRenderer.java index 9c34631b8..fe0da2216 100644 --- a/apps/syndie/java/src/net/i2p/syndie/sml/ThreadedHTMLRenderer.java +++ b/apps/syndie/java/src/net/i2p/syndie/sml/ThreadedHTMLRenderer.java @@ -17,6 +17,7 @@ import net.i2p.util.Log; public class ThreadedHTMLRenderer extends HTMLRenderer { private Log _log; private String _baseURI; + private boolean _inlineReply; public ThreadedHTMLRenderer(I2PAppContext ctx) { super(ctx); @@ -118,8 +119,11 @@ public class ThreadedHTMLRenderer extends HTMLRenderer { private static final boolean empty(String val) { return (val == null) || (val.trim().length() <= 0); } + /** + * @param replyHiddenFields HTML of hidden input fields necessary for the reply form to be honored + */ public void render(User user, Writer out, Archive archive, BlogURI post, - boolean inlineReply, ThreadIndex index, String baseURI, + boolean inlineReply, ThreadIndex index, String baseURI, String replyHiddenFields, String offset, String requestTags, String filteredAuthor) throws IOException { EntryContainer entry = archive.getEntry(post); if (entry == null) return; @@ -131,6 +135,7 @@ public class ThreadedHTMLRenderer extends HTMLRenderer { _archive = archive; _cutBody = false; _showImages = true; + _inlineReply = inlineReply; _headers = new HashMap(); _bodyBuffer = new StringBuffer(1024); _postBodyBuffer = new StringBuffer(1024); @@ -192,6 +197,30 @@ public class ThreadedHTMLRenderer extends HTMLRenderer { out.write("\npermalink\n"); + + + if (!inlineReply) { + String refuseReply = (String)_headers.get(HEADER_REFUSE_REPLIES); + boolean allowReply = false; + if ( (refuseReply != null) && (Boolean.valueOf(refuseReply).booleanValue()) ) { + if (_entry == null ) + allowReply = false; + else if ( (_user == null) || (_user.getBlog() == null) ) + allowReply = false; + else if (_entry.getURI().getKeyHash().equals(_user.getBlog())) + allowReply = true; + else + allowReply = false; + } else { + allowReply = true; + } + if (allowReply && (_entry != null) ) { + out.write("Reply
\n"); + } + } out.write("\n\n"); out.write("\n"); @@ -229,6 +258,7 @@ public class ThreadedHTMLRenderer extends HTMLRenderer { (refuseReplies == null) || (!Boolean.valueOf(refuseReplies).booleanValue()) ) { out.write("\n"); out.write("
\n"); + out.write(replyHiddenFields); out.write(""); diff --git a/apps/syndie/java/src/net/i2p/syndie/web/AddressesServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/AddressesServlet.java index 56dfd7ded..d7359b9f9 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/AddressesServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/AddressesServlet.java @@ -96,6 +96,7 @@ public class AddressesServlet extends BaseServlet { out.write(""); out.write(""); out.write(""); + writeAuthActionFields(out); out.write(""); out.write("\n"); out.write("Name: " + pn.getName() + " "); @@ -120,6 +121,7 @@ public class AddressesServlet extends BaseServlet { } out.write(""); + writeAuthActionFields(out); out.write(""); out.write(""); out.write(""); @@ -156,6 +158,7 @@ public class AddressesServlet extends BaseServlet { for (Iterator iter = names.iterator(); iter.hasNext(); ) { PetName pn = db.getByName((String)iter.next()); out.write(""); + writeAuthActionFields(out); out.write(""); out.write(""); out.write(""); @@ -177,6 +180,7 @@ public class AddressesServlet extends BaseServlet { } out.write(""); + writeAuthActionFields(out); out.write(""); out.write(""); out.write(""); @@ -208,6 +212,7 @@ public class AddressesServlet extends BaseServlet { for (Iterator iter = names.iterator(); iter.hasNext(); ) { PetName pn = db.getByName((String)iter.next()); out.write(""); + writeAuthActionFields(out); out.write(""); out.write(""); out.write(""); @@ -222,6 +227,7 @@ public class AddressesServlet extends BaseServlet { } out.write(""); + writeAuthActionFields(out); out.write(""); out.write(""); out.write(""); @@ -248,6 +254,7 @@ public class AddressesServlet extends BaseServlet { for (Iterator iter = names.iterator(); iter.hasNext(); ) { PetName pn = db.getByName((String)iter.next()); out.write(""); + writeAuthActionFields(out); out.write(""); out.write(""); out.write(""); @@ -262,6 +269,7 @@ public class AddressesServlet extends BaseServlet { } out.write(""); + writeAuthActionFields(out); out.write(""); out.write(""); out.write(""); @@ -288,6 +296,7 @@ public class AddressesServlet extends BaseServlet { for (Iterator iter = names.iterator(); iter.hasNext(); ) { PetName pn = db.getByName((String)iter.next()); out.write(""); + writeAuthActionFields(out); out.write(""); out.write("\n"); out.write("Network: "); @@ -302,6 +311,7 @@ public class AddressesServlet extends BaseServlet { } out.write(""); + writeAuthActionFields(out); out.write(""); out.write("\n"); @@ -372,4 +382,6 @@ public class AddressesServlet extends BaseServlet { return proto.equals(reqProto); } } + + protected String getTitle() { return "Syndie :: Addressbook"; } } diff --git a/apps/syndie/java/src/net/i2p/syndie/web/AdminServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/AdminServlet.java index 747655012..41a5c8fa0 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/AdminServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/AdminServlet.java @@ -31,6 +31,7 @@ public class AdminServlet extends BaseServlet { private void displayForm(User user, HttpServletRequest req, PrintWriter out) throws IOException { out.write("\n"); + writeAuthActionFields(out); out.write(""); out.write("Single user? \n"); out.write("
\n"); } + + protected String getTitle() { return "Syndie :: Configuration"; } } diff --git a/apps/syndie/java/src/net/i2p/syndie/web/BaseServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/BaseServlet.java index 51f006a49..f6387a200 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/BaseServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/BaseServlet.java @@ -20,6 +20,53 @@ import net.i2p.syndie.sml.*; * */ public abstract class BaseServlet extends HttpServlet { + protected static final String PARAM_AUTH_ACTION = "syndie.auth"; + private static long _authNonce; + + public void init() throws ServletException { + super.init(); + _authNonce = I2PAppContext.getGlobalContext().random().nextLong(); + } + + protected boolean authAction(HttpServletRequest req) { + return authAction(req.getParameter(PARAM_AUTH_ACTION)); + } + protected boolean authAction(String auth) { + if (auth == null) { + return false; + } else { + try { + boolean rv = (Long.valueOf(auth).longValue() == _authNonce); + return rv; + } catch (NumberFormatException nfe) { + return false; + } + } + } + + /** + * write out hidden fields for params that need to be tacked onto an http request that updates + * data, to prevent spoofing + */ + protected void writeAuthActionFields(Writer out) throws IOException { + out.write(""); + } + protected String getAuthActionFields() throws IOException { + return ""; + } + /** + * key=value& of params that need to be tacked onto an http request that updates data, to + * prevent spoofing + */ + protected String getAuthActionParams() { return PARAM_AUTH_ACTION + '=' + _authNonce + '&'; } + /** + * key=value& of params that need to be tacked onto an http request that updates data, to + * prevent spoofing + */ + protected void addAuthActionParams(StringBuffer buf) { + buf.append(PARAM_AUTH_ACTION).append('=').append(_authNonce).append('&'); + } + public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); @@ -30,12 +77,15 @@ public abstract class BaseServlet extends HttpServlet { String pass = req.getParameter("password"); String action = req.getParameter("action"); boolean forceNewIndex = false; + + boolean authAction = authAction(req); if (req.getParameter("regenerateIndex") != null) forceNewIndex = true; User oldUser = user; - user = handleRegister(user, req); + if (authAction) + user = handleRegister(user, req); if (oldUser != user) forceNewIndex = true; @@ -48,23 +98,25 @@ public abstract class BaseServlet extends HttpServlet { user = BlogManager.instance().getDefaultUser(); } forceNewIndex = true; - } else if ("Login".equals(action)) { + } else if (authAction && "Login".equals(action)) { user = BlogManager.instance().login(login, pass); // ignore failures - user will just be unauthorized if (!user.getAuthenticated()) user = BlogManager.instance().getDefaultUser(); forceNewIndex = true; - } else if ("Logout".equals(action)) { + } else if (authAction && "Logout".equals(action)) { user = BlogManager.instance().getDefaultUser(); forceNewIndex = true; } req.getSession().setAttribute("user", user); - handleAdmin(user, req); + if (authAction) { + handleAdmin(user, req); - forceNewIndex = handleAddressbook(user, req) || forceNewIndex; - forceNewIndex = handleBookmarking(user, req) || forceNewIndex; - handleUpdateProfile(user, req); + forceNewIndex = handleAddressbook(user, req) || forceNewIndex; + forceNewIndex = handleBookmarking(user, req) || forceNewIndex; + handleUpdateProfile(user, req); + } FilteredThreadIndex index = (FilteredThreadIndex)req.getSession().getAttribute("threadIndex"); @@ -677,6 +729,7 @@ public abstract class BaseServlet extends HttpServlet { if (!empty(filteredAuthor)) buf.append(ThreadedHTMLRenderer.PARAM_AUTHOR).append('=').append(filteredAuthor).append('&'); + addAuthActionParams(buf); return buf.toString(); } protected String getRemoveFromGroupLink(User user, String name, String group, String uri, String visible, @@ -703,6 +756,7 @@ public abstract class BaseServlet extends HttpServlet { if (!empty(filteredAuthor)) buf.append(ThreadedHTMLRenderer.PARAM_AUTHOR).append('=').append(filteredAuthor).append('&'); + addAuthActionParams(buf); return buf.toString(); } protected String getViewPostLink(HttpServletRequest req, ThreadNode node, User user, boolean isPermalink) { diff --git a/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java index 1a869728c..9727a485c 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java @@ -60,6 +60,13 @@ public class PostServlet extends BaseServlet { } private void previewPostedData(User user, HttpServletRequest rawRequest, Archive archive, String contentType, PostBean post, PrintWriter out) throws IOException { + MultiPartRequest req = new MultiPartRequest(rawRequest); + + if (!authAction(req.getString(PARAM_AUTH_ACTION))) { + out.write("Invalid form submission... stale data?"); + return; + } + // not confirmed but they posted stuff... gobble up what they give // and display it as a prview (then we show the confirm form @@ -68,8 +75,6 @@ public class PostServlet extends BaseServlet { post.reinitialize(); post.setUser(user); - MultiPartRequest req = new MultiPartRequest(rawRequest); - boolean inNewThread = getInNewThread(req.getString(PARAM_IN_NEW_THREAD)); boolean refuseReplies = getRefuseReplies(req.getString(PARAM_REFUSE_REPLIES)); @@ -86,8 +91,8 @@ public class PostServlet extends BaseServlet { if ( (replyTo != null) && (replyTo.trim().length() > 0) ) { byte r[] = Base64.decode(replyTo); if (r != null) { - if (entryHeaders == null) entryHeaders = HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r, "UTF-8"); - else entryHeaders = entryHeaders + '\n' + HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r, "UTF-8"); + if (entryHeaders == null) entryHeaders = HTMLRenderer.HEADER_IN_REPLY_TO + ": entry://" + new String(r, "UTF-8"); + else entryHeaders = entryHeaders + '\n' + HTMLRenderer.HEADER_IN_REPLY_TO + ": entry://" + new String(r, "UTF-8"); } else { replyTo = null; } @@ -137,6 +142,7 @@ public class PostServlet extends BaseServlet { post.renderPreview(out); out.write("
\n"); + writeAuthActionFields(out); out.write("Please confirm that the above is ok"); if (BlogManager.instance().authorizeRemote(user)) { out.write(", and select what additional archives you want the post transmitted to."); @@ -166,6 +172,10 @@ public class PostServlet extends BaseServlet { } private void postEntry(User user, HttpServletRequest req, Archive archive, PostBean post, PrintWriter out) throws IOException { + if (!authAction(req)) { + out.write("Invalid form submission... stale data?"); + return; + } String remArchive = req.getParameter(PARAM_REMOTE_ARCHIVE); post.setArchive(remArchive); BlogURI uri = post.postEntry(); @@ -185,6 +195,7 @@ public class PostServlet extends BaseServlet { post.setUser(user); out.write("\n"); + writeAuthActionFields(out); out.write("\n"); out.write("Post subject: "); out.write(" 0) ) - out.write("\n"); + out.write("\n"); out.write(" Tags: \n"); @@ -223,7 +234,8 @@ public class PostServlet extends BaseServlet { if (parentURI != null) { out.write("
"); - post.renderReplyPreview(out, DataHelper.getUTF8(Base64.decode(parentURI))); + String decoded = DataHelper.getUTF8(Base64.decode(parentURI)); + post.renderReplyPreview(out, "entry://" + decoded); out.write("
\n"); } @@ -273,4 +285,5 @@ public class PostServlet extends BaseServlet { + "Attachment 2:
" + "Attachment 3:
\n"; + protected String getTitle() { return "Syndie :: Post new content"; } } diff --git a/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java index abb94154f..0432298d3 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java @@ -57,6 +57,7 @@ public class ProfileServlet extends BaseServlet { out.write("\n"); out.write("\n"); + writeAuthActionFields(out); // now add the form to update out.write("Your profile\n"); out.write("Name: The profile requested is invalid\n"; } diff --git a/apps/syndie/java/src/net/i2p/syndie/web/SwitchServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/SwitchServlet.java index 153c820f1..38b5fad1a 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/SwitchServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/SwitchServlet.java @@ -19,30 +19,31 @@ import net.i2p.syndie.sml.*; * Login/register form * */ -public class SwitchServlet extends BaseServlet { - private final String FORM = "\n" + - "Log in to an existing account\n" + - "Login: \n" + - "Password: \n" + - "\n" + - "\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - "Register a new account\n" + - "Login: (only known locally)\n" + - "Password: \n" + - "Public name: \n" + - "Description: \n" + - "Contact URL: \n" + - "Registration password: " + - " (only necessary if the Syndie administrator requires it)\n" + - "\n" + - "
\n"; +public class SwitchServlet extends BaseServlet { + protected String getTitle() { return "Syndie :: Login/Register"; } protected void renderServletDetails(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index, int threadOffset, BlogURI visibleEntry, Archive archive) throws IOException { - out.write(FORM); + out.write("
\n"); + writeAuthActionFields(out); + out.write("Log in to an existing account\n" + + "Login: \n" + + "Password: \n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "Register a new account\n" + + "Login: (only known locally)\n" + + "Password: \n" + + "Public name: \n" + + "Description: \n" + + "Contact URL: \n" + + "Registration password: " + + " (only necessary if the Syndie administrator requires it)\n" + + "\n" + + "
\n"); } } diff --git a/apps/syndie/java/src/net/i2p/syndie/web/ViewThreadedServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/ViewThreadedServlet.java index da805d42f..b3de4531b 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ViewThreadedServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ViewThreadedServlet.java @@ -40,7 +40,7 @@ public class ViewThreadedServlet extends BaseServlet { for (int i = 0; i < posts.size(); i++) { BlogURI post = (BlogURI)posts.get(i); - renderer.render(user, out, archive, post, posts.size() == 1, index, uri, off, tags, author); + renderer.render(user, out, archive, post, posts.size() == 1, index, uri, getAuthActionFields(), off, tags, author); } } @@ -136,7 +136,7 @@ public class ViewThreadedServlet extends BaseServlet { int written = 0; for (int curRoot = threadOffset; curRoot < numThreads + threadOffset; curRoot++) { ThreadNode node = index.getRoot(curRoot); - out.write("\n"); + out.write("\n"); renderThread(user, out, index, archive, req, node, 0, visibleEntry, state); out.write("\n"); written++; @@ -302,4 +302,5 @@ public class ViewThreadedServlet extends BaseServlet { return " "; } + protected String getTitle() { return "Syndie :: View threads"; } }