Source code: infranet/InfranetFilter.java
1 /* Copyright (c) 2003, Massachusetts Institute of Technology
2 * All rights reserved.
3 *
4 * Permission to use, copy, modify and distribute this software and its
5 * documentation for any purpose, without fee, and without written agreement is
6 * hereby granted, provided that the above copyright notice and the following
7 * paragraph appears in all copies of this software.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
12 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
13 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
14 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
15 * IN THE SOFTWARE.
16 *
17 * Author: Winston Wang
18 *
19 */
20
21 package infranet;
22
23 import java.util.*;
24 import java.io.*;
25 import java.net.*;
26
27 import javax.servlet.*;
28 import javax.servlet.http.*;
29 import java.io.IOException;
30
31 import java.security.*;
32 import javax.crypto.*;
33 import org.bouncycastle.openssl.PEMReader;
34 import org.bouncycastle.util.encoders.Hex;
35 import org.bouncycastle.jce.provider.BouncyCastleProvider;
36
37 public class InfranetFilter implements Filter {
38 private static final boolean SAVE_DATA = false;
39 private static final int SOCKET_TIMEOUT = 15000;
40
41 private static final int SHARED_KEY = 0;
42 private static final int UPDATE = 1;
43 private static final int DEMOD = 2;
44 private static final int SERVE = 3;
45
46 private static String initialKey, docBase;
47 private static Map maxBytes;
48
49 private static final String cdfPath = "WEB-INF/config/cdf.txt";
50 private static final String urlPath = "WEB-INF/config/url.txt";
51 private static final String symPath = "WEB-INF/config/symbol.txt";
52 private static final String capPath = "WEB-INF/config/outguess-table.txt";
53 private static final String rsaPath = "WEB-INF/config/rsa.priv";
54
55 private static ServletContext context;
56 private static boolean first = true;
57
58 private static class ClientState {
59 public int state;
60 public String key;
61 public Dictionary dict;
62 public InfranetMessage message;
63 public ClientState() throws IOException {
64 state = SHARED_KEY;
65 key = initialKey;
66 dict = new HexDictionary();
67 message = new InfranetMessage(dict.getDownstream());
68 }
69 }
70
71 private static int fileCount = 0;
72 private static synchronized String getFileName() {
73 ++fileCount;
74 return "data-"+fileCount;
75 }
76
77 public void init(FilterConfig config) {
78 try {
79 initialKey = config.getInitParameter("initialKey");
80 if (initialKey == null) {
81 System.out.println("web.xml error - No initial key specified");
82 }
83 context = config.getServletContext();
84 docBase = context.getRealPath("/");
85
86 RangeDictionary.initCdf(docBase + cdfPath, docBase + symPath);
87
88 if (!new File(docBase + urlPath).canRead()) {
89 AutoConfig.createUrlFile(urlPath, docBase);
90 }
91
92 if (!new File(docBase + capPath).canRead()) {
93 AutoConfig.createCapFile(capPath, docBase);
94 }
95
96 Security.addProvider(new BouncyCastleProvider());
97
98 String cmd = config.getInitParameter("stegCmd");
99 if (cmd == null) {
100 System.out.println("web.xml error - No stegCmd specified");
101 }
102
103 String shell = config.getInitParameter("shell");
104 if (shell == null) {
105 System.out.println("web.xml error - No shell specified.");
106 }
107 EmbeddedDataStream.init(shell, cmd);
108 }
109 catch (Exception e) {
110 e.printStackTrace(System.out);
111 }
112 }
113
114 /* For some reason, the servlet context does not provide the context
115 * path. So the filter waits for the first request to come in, gets
116 * the context path from the incoming request object, and finishes
117 * initialization
118 */
119 private static void init2(String contextPath) throws FileNotFoundException, IOException {
120 BufferedReader urlReader = new BufferedReader(new FileReader(docBase + urlPath));
121 StringBuffer sb = new StringBuffer();
122 while(urlReader.ready()) sb.append(urlReader.readLine() + "\n");
123
124 StringTokenizer st = new StringTokenizer(sb.toString());
125 Vector v = new Vector();
126 while(st.hasMoreTokens()) v.add(contextPath + st.nextToken());
127 HexDictionary.initUrl(v);
128 RangeDictionary.initUrl(v);
129
130
131 maxBytes = new HashMap();
132 BufferedReader capReader = new BufferedReader(new FileReader(docBase + capPath));
133 while (capReader.ready()) {
134 StringTokenizer st2 = new StringTokenizer(capReader.readLine());
135 String uri = contextPath + st2.nextToken();
136 Integer amount = new Integer(st2.nextToken());
137 maxBytes.put(uri, amount);
138 }
139 }
140
141 public void destroy() {}
142
143 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
144 long startTime = new Date().getTime();
145 try {
146 HttpServletRequest request = (HttpServletRequest)req;
147 HttpServletResponse response = (HttpServletResponse)res;
148 HttpSession session = request.getSession();
149
150 if (first) {
151 first = false;
152 init2(request.getContextPath());
153 }
154
155 String uri = request.getRequestURI();
156
157 ClientState clientState = (ClientState)session.getAttribute("clientState");
158 if (clientState == null) {
159 clientState = new ClientState();
160 session.setAttribute("clientState", clientState);
161 }
162
163 boolean filterCalled = false;
164 if (!clientState.message.isEmpty()) {
165 Integer sizeInt = (Integer)maxBytes.get(uri);
166 if (sizeInt != null) {
167 int size = sizeInt.intValue();
168 byte[] chunk = clientState.message.getChunk(size);
169 EmbeddedResponse embed = new EmbeddedResponse(response, clientState.key, chunk);
170
171 filterCalled = true;
172 chain.doFilter(request, embed);
173 if (embed.success()) embed.flushBuffer(); else clientState.message.undo();
174 }
175 }
176
177 if (!filterCalled) chain.doFilter(request, response);
178
179 switch (clientState.state) {
180 case SHARED_KEY :
181 clientState.dict.setUpstream(uri);
182 if (clientState.dict.isDone()) {
183 clientState.key = getStegoKey((String)clientState.dict.getMessage());
184 System.out.println("Stego key is " + clientState.key);
185 clientState.dict = new RangeDictionary();
186 clientState.message = new InfranetMessage(clientState.dict.getDownstream());
187 clientState.state = UPDATE;
188 }
189 break;
190
191 case UPDATE :
192 if (clientState.message.isEmpty()) {
193 clientState.state = DEMOD;
194 }
195 break;
196
197 case DEMOD :
198 boolean success = clientState.dict.setUpstream(uri);
199 if (clientState.dict.isDone()) {
200 Request r = (Request)clientState.dict.getMessage();
201
202 byte[] data;
203 try {
204 data = fetchRequest(r);
205 clientState.dict.addGuesses(HtmlParser.parseHtml(r, data));
206 }
207 catch (Exception e) {
208 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
209 PrintStream printStream = new PrintStream(byteStream);
210 printStream.println("<pre>");
211 e.printStackTrace(printStream);
212 printStream.println("</pre>");
213 data = byteStream.toByteArray();
214 }
215 clientState.message = new InfranetMessage(data);
216 clientState.state = SERVE;
217 }
218 else {
219 if (success) {
220 clientState.message = new InfranetMessage(clientState.dict.getDownstream());
221 clientState.state = UPDATE;
222 }
223 }
224 break;
225
226 case SERVE :
227 if (clientState.message.isEmpty()) {
228 clientState.state = UPDATE;
229 clientState.dict.clear();
230 clientState.message = new InfranetMessage(clientState.dict.getDownstream());
231 }
232 break;
233 }
234
235 long endTime = new Date().getTime();
236 System.out.println("Time: "+(endTime-startTime) + " url: " + uri);
237
238 }
239 catch (Exception e) {
240 e.printStackTrace(System.out);
241 }
242 }
243
244 private static byte[] fetchRequest(Request req) throws Exception {
245 String host = req.getHost();
246 int port = req.getPort();
247
248 Socket s = new Socket(host, port);
249 s.setSoTimeout(SOCKET_TIMEOUT);
250
251 Writer out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
252 out.write(req.getRequest());
253 System.out.println(req.getRequest());
254 out.flush();
255
256 InputStream in = new BufferedInputStream(s.getInputStream());
257 ByteArrayOutputStream buf = new ByteArrayOutputStream();
258
259 byte[] temp = new byte[s.getReceiveBufferSize()];
260 int len = in.read(temp);
261 while (len >= 0) {
262 buf.write(temp, 0, len);
263 len = in.read(temp);
264 }
265
266 if (SAVE_DATA) {
267 OutputStream o = new FileOutputStream(getFileName());
268 o.write(buf.toByteArray());
269 o.close();
270 }
271
272 return buf.toByteArray();
273 }
274
275 private static String getStegoKey(String seed) {
276 try {
277 Reader reader = new InputStreamReader(context.getResourceAsStream(rsaPath));
278 PEMReader pem = new PEMReader(reader, null, "SunJSSE");
279 KeyPair keyPair = (KeyPair)pem.readObject();
280
281 Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
282 cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
283
284 byte[] encrypted = Hex.decode(seed);
285 return new String(cipher.doFinal(encrypted));
286 }
287 catch (Exception e) {
288 e.printStackTrace(System.out);
289 }
290 return null;
291 }
292 }