001: /*
002: * This file is part of PFIXCORE.
003: *
004: * PFIXCORE is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU Lesser General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * PFIXCORE is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public License
015: * along with PFIXCORE; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: */
019:
020: package de.schlund.pfixxml;
021:
022: import java.io.OutputStream;
023: import java.io.OutputStreamWriter;
024: import java.net.URLEncoder;
025: import java.util.Properties;
026:
027: import javax.servlet.ServletException;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.log4j.Logger;
032:
033: import de.schlund.pfixxml.config.ServletManagerConfig;
034: import de.schlund.pfixxml.config.impl.ServletManagerConfigImpl;
035: import de.schlund.pfixxml.resources.FileResource;
036: import de.schlund.pfixxml.serverutil.SessionHelper;
037: import de.schlund.pfixxml.util.Base64Utils;
038: import de.schlund.pfixxml.util.MD5Utils;
039:
040: /**
041: * This class implements a "Dereferer" servlet to get rid of Referer
042: * headers. <b>ALL LINKS THAT GO TO AN OUTSIDE DOMAIN MUST USE THIS SERVLET!</b>
043: * If this servlet is bound to e.g. /xml/deref than every outside link
044: * (say to http://www.gimp.org) must be written like <a href="/xml/deref?link=http://www.gimp.org">Gimp</a>
045: *
046: */
047:
048: public class DerefServlet extends ServletManager {
049:
050: private static final long serialVersionUID = 4003807093421866709L;
051:
052: protected final static Logger DEREFLOG = Logger
053: .getLogger("LOGGER_DEREF");
054: protected final static Logger LOG = Logger
055: .getLogger(DerefServlet.class);
056: public final static String PROP_DEREFKEY = "derefserver.signkey";
057: public final static String PROP_IGNORESIGN = "derefserver.ignoresign";
058: private ServletManagerConfig config;
059:
060: protected boolean allowSessionCreate() {
061: return (false);
062: }
063:
064: protected boolean needsSession() {
065: return (false);
066: }
067:
068: public static String signString(String input, String key) {
069: return MD5Utils.hex_md5(input + key, "utf8");
070: }
071:
072: public static boolean checkSign(String input, String key,
073: String sign) {
074: if (input == null || sign == null) {
075: return false;
076: }
077: return MD5Utils.hex_md5(input + key, "utf8").equals(sign);
078: }
079:
080: protected void process(PfixServletRequest preq,
081: HttpServletResponse res) throws Exception {
082: RequestParam linkparam = preq.getRequestParam("link");
083: RequestParam enclinkparam = preq.getRequestParam("__enclink");
084: RequestParam signparam = preq.getRequestParam("__sign");
085: String key = config.getProperties().getProperty(PROP_DEREFKEY);
086: String ign = config.getProperties()
087: .getProperty(PROP_IGNORESIGN);
088:
089: // This is currently set to true by default for backward compatibility.
090: boolean ignore_nosign = true;
091: if (ign != null && ign.equals("false")) {
092: ignore_nosign = false;
093: }
094:
095: HttpServletRequest req = preq.getRequest();
096: String referer = req.getHeader("Referer");
097:
098: LOG.debug("===> sign key: " + key);
099: LOG.debug("===> Referer: " + referer);
100:
101: if (linkparam != null && linkparam.getValue() != null) {
102: LOG.debug(" ==> Handle link: " + linkparam.getValue());
103: if (signparam != null && signparam.getValue() != null) {
104: LOG.debug(" with sign: " + signparam.getValue());
105: }
106: handleLink(linkparam.getValue(), signparam, ignore_nosign,
107: preq, res, key);
108: return;
109: } else if (enclinkparam != null
110: && enclinkparam.getValue() != null && signparam != null
111: && signparam.getValue() != null) {
112: LOG
113: .debug(" ==> Handle enclink: "
114: + enclinkparam.getValue());
115: LOG.debug(" with sign: " + signparam.getValue());
116: handleEnclink(enclinkparam.getValue(),
117: signparam.getValue(), preq, res, key);
118: return;
119: } else {
120: res.sendError(HttpServletResponse.SC_BAD_REQUEST);
121: return;
122: }
123: }
124:
125: private void handleLink(String link, RequestParam signparam,
126: boolean ignore_nosign, PfixServletRequest preq,
127: HttpServletResponse res, String key) throws Exception {
128: boolean checked = false;
129: boolean signed = false;
130: boolean addallparams = false;
131:
132: if (link.startsWith("/") || link.startsWith("addallparams:/")) {
133: // This is a "relative absolute" link, no other domain.
134: // It doesn't matter if any JS tricks or other stuff is played here, because
135: // the link will only be used in the second stage when we do relocate via 302
136: ignore_nosign = true;
137: }
138:
139: if (signparam != null && signparam.getValue() != null) {
140: signed = true;
141: }
142: if (signed && checkSign(link, key, signparam.getValue())) {
143: checked = true;
144: }
145:
146: if (link.startsWith("addallparams:")) {
147: addallparams = true;
148: link = link.substring("addallparams:".length());
149: }
150:
151: // We don't currently enforce the signing at this stage. We may change this to enforcing mode,
152: // or maybe we will use some clear warning pages in the case of a not signed request.
153: if (checked || (!signed && ignore_nosign)) {
154: OutputStream out = res.getOutputStream();
155: OutputStreamWriter writer = new OutputStreamWriter(out, res
156: .getCharacterEncoding());
157: if (addallparams) {
158: String[] allparamnames = preq.getRequestParamNames();
159: StringBuffer urlextension = new StringBuffer();
160: for (String tmpname : allparamnames) {
161: if (tmpname.equals("link")
162: || tmpname.equals("__sign")
163: || tmpname.equals("__enclink")) {
164: continue;
165: }
166: RequestParam tmpparam = preq
167: .getRequestParam(tmpname);
168: if (tmpparam.getValue() != null) {
169: urlextension
170: .append("&"
171: + URLEncoder
172: .encode(
173: tmpname,
174: preq
175: .getRequest()
176: .getCharacterEncoding())
177: + "="
178: + URLEncoder
179: .encode(
180: tmpparam
181: .getValue(),
182: preq
183: .getRequest()
184: .getCharacterEncoding()));
185: }
186: }
187: String urltail = urlextension.toString();
188: if (urltail != null && urltail.length() > 0) {
189: if (link.contains("?")) {
190: link = link + urlextension;
191: } else {
192: link = link + "?" + urlextension.substring(1);
193: }
194: }
195: }
196: String enclink = Base64Utils.encode(link.getBytes("utf8"),
197: false);
198: String reallink = getServerURL(preq)
199: + SessionHelper.getClearedURI(preq) + "?__enclink="
200: + URLEncoder.encode(enclink, "utf8") + "&__sign="
201: + signString(enclink, key);
202:
203: LOG.debug("===> Meta refresh to link: " + reallink);
204: DEREFLOG.info(preq.getServerName() + "|" + link + "|"
205: + preq.getRequest().getHeader("Referer"));
206:
207: writer.write("<html><head>");
208: writer
209: .write("<meta http-equiv=\"refresh\" content=\"0; URL="
210: + reallink + "\">");
211:
212: writer
213: .write("<script language=\"JavaScript\" type=\"text/javascript\">\n");
214: writer.write("<!--\n");
215: writer
216: .write("function redirect() { setTimeout(\"window.location.replace('"
217: + reallink + "')\", 10); }\n");
218: writer.write("-->\n");
219: writer.write("</script>\n");
220:
221: writer
222: .write("</head><body onload=\"redirect()\" bgcolor=\"#ffffff\"><center>");
223: writer.write("<a style=\"color:#cccccc;\" href=\""
224: + reallink + "\">" + "-> Redirect ->" + "</a>");
225: writer.write("</center></body></html>");
226: writer.flush();
227: } else {
228: LOG
229: .warn("===> No meta refresh because signature is wrong.");
230: res.sendError(HttpServletResponse.SC_BAD_REQUEST);
231: return;
232: }
233: }
234:
235: private String getServerURL(PfixServletRequest preq) {
236: String url = preq.getScheme() + "://" + preq.getServerName();
237: //only add port if non-default
238: if (!((preq.getScheme().equals("http") && preq.getServerPort() == 80) || (preq
239: .getScheme().equals("https") && preq.getServerPort() == 443))) {
240: url += ":" + preq.getServerPort();
241: }
242: return url;
243: }
244:
245: private void handleEnclink(String enclink, String sign,
246: PfixServletRequest preq, HttpServletResponse res, String key)
247: throws Exception {
248: if (checkSign(enclink, key, sign)) {
249: String link = new String(Base64Utils.decode(enclink),
250: "utf8");
251: if (link.startsWith("/")) {
252: link = getServerURL(preq) + link;
253: }
254: LOG.debug("===> Relocate to link: " + link);
255:
256: res.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
257: res.setHeader("Pragma", "no-cache");
258: res.setHeader("Cache-Control",
259: "no-cache, no-store, private, must-revalidate");
260: res.setHeader("Location", link);
261: res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
262: } else {
263: LOG.warn("===> Won't relocate because signature is wrong.");
264: res.sendError(HttpServletResponse.SC_BAD_REQUEST);
265: return;
266: }
267: }
268:
269: protected ServletManagerConfig getServletManagerConfig() {
270: return this .config;
271: }
272:
273: protected void reloadServletConfig(FileResource configFile,
274: Properties globalProperties) throws ServletException {
275: // Deref server does not use a servlet specific configuration
276: // So simply initialize configuration with global properties
277: ServletManagerConfigImpl sConf = new ServletManagerConfigImpl();
278: sConf.setProperties(globalProperties);
279: sConf.setSSL(false);
280: this.config = sConf;
281: }
282:
283: }
|