1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *
19 */
20 package org.apache.mina.proxy.filter;
21
22 import org.apache.mina.core.buffer.IoBuffer;
23 import org.apache.mina.core.filterchain.IoFilter;
24 import org.apache.mina.core.filterchain.IoFilterAdapter;
25 import org.apache.mina.core.filterchain.IoFilterChain;
26 import org.apache.mina.core.session.IdleStatus;
27 import org.apache.mina.core.session.IoSession;
28 import org.apache.mina.core.write.WriteRequest;
29 import org.apache.mina.proxy.ProxyAuthException;
30 import org.apache.mina.proxy.ProxyConnector;
31 import org.apache.mina.proxy.ProxyLogicHandler;
32 import org.apache.mina.proxy.event.IoSessionEvent;
33 import org.apache.mina.proxy.event.IoSessionEventQueue;
34 import org.apache.mina.proxy.event.IoSessionEventType;
35 import org.apache.mina.proxy.handlers.ProxyRequest;
36 import org.apache.mina.proxy.handlers.http.HttpSmartProxyHandler;
37 import org.apache.mina.proxy.handlers.socks.Socks4LogicHandler;
38 import org.apache.mina.proxy.handlers.socks.Socks5LogicHandler;
39 import org.apache.mina.proxy.handlers.socks.SocksProxyConstants;
40 import org.apache.mina.proxy.handlers.socks.SocksProxyRequest;
41 import org.apache.mina.proxy.session.ProxyIoSession;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46 * ProxyFilter.java - Proxy {@link IoFilter}.
47 * Automatically inserted into the {@link IoFilter} chain by {@link ProxyConnector}.
48 * Sends the initial handshake message to the proxy and handles any response
49 * to the handshake. Once the handshake has completed and the proxied connection has been
50 * established this filter becomes transparent to data flowing through the connection.
51 * <p>
52 * Based upon SSLFilter from mina-filter-ssl.
53 *
54 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
55 * @since MINA 2.0.0-M3
56 */
57 public class ProxyFilter extends IoFilterAdapter {
58 private final static Logger LOGGER = LoggerFactory
59 .getLogger(ProxyFilter.class);
60
61 /**
62 * Create a new {@link ProxyFilter}.
63 */
64 public ProxyFilter() {
65 // Do nothing
66 }
67
68 /**
69 * Called before the filter is added into the filter chain.
70 * Checks if chain already holds an {@link ProxyFilter} instance.
71 *
72 * @param chain the filter chain
73 * @param name the name assigned to this filter
74 * @param nextFilter the next filter
75 * @throws IllegalStateException if chain already contains an instance of
76 * {@link ProxyFilter}
77 */
78 @Override
79 public void onPreAdd(final IoFilterChain chain, final String name,
80 final NextFilter nextFilter) {
81 if (chain.contains(ProxyFilter.class)) {
82 throw new IllegalStateException(
83 "A filter chain cannot contain more than one ProxyFilter.");
84 }
85 }
86
87 /**
88 * Called when the filter is removed from the filter chain.
89 * Cleans the {@link ProxyIoSession} instance from the session.
90 *
91 * @param chain the filter chain
92 * @param name the name assigned to this filter
93 * @param nextFilter the next filter
94 */
95 @Override
96 public void onPreRemove(final IoFilterChain chain, final String name,
97 final NextFilter nextFilter) {
98 IoSession session = chain.getSession();
99 session.removeAttribute(ProxyIoSession.PROXY_SESSION);
100 }
101
102 /**
103 * Called when an exception occurs in the chain. A flag is set in the
104 * {@link ProxyIoSession} session's instance to signal that handshake
105 * failed.
106 *
107 * @param chain the filter chain
108 * @param name the name assigned to this filter
109 * @param nextFilter the next filter
110 */
111 @Override
112 public void exceptionCaught(NextFilter nextFilter, IoSession session,
113 Throwable cause) throws Exception {
114 ProxyIoSession proxyIoSession = (ProxyIoSession) session
115 .getAttribute(ProxyIoSession.PROXY_SESSION);
116 proxyIoSession.setAuthenticationFailed(true);
117 super.exceptionCaught(nextFilter, session, cause);
118 }
119
120 /**
121 * Get the {@link ProxyLogicHandler} for a given session.
122 *
123 * @param session the session object
124 * @return the handler which will handle handshaking with the proxy
125 */
126 private ProxyLogicHandler getProxyHandler(final IoSession session) {
127 ProxyLogicHandler handler = ((ProxyIoSession) session
128 .getAttribute(ProxyIoSession.PROXY_SESSION)).getHandler();
129
130 if (handler == null) {
131 throw new IllegalStateException();
132 }
133
134 // Sanity check
135 if (handler.getProxyIoSession().getProxyFilter() != this) {
136 throw new IllegalArgumentException("Not managed by this filter.");
137 }
138
139 return handler;
140 }
141
142 /**
143 * Receives data from the remote host, passes to the handler if a handshake is in progress,
144 * otherwise passes on transparently.
145 *
146 * @param nextFilter the next filter in filter chain
147 * @param session the session object
148 * @param message the object holding the received data
149 */
150 @Override
151 public void messageReceived(final NextFilter nextFilter,
152 final IoSession session, final Object message)
153 throws ProxyAuthException {
154 ProxyLogicHandler handler = getProxyHandler(session);
155
156 synchronized (handler) {
157 IoBuffer buf = (IoBuffer) message;
158
159 if (handler.isHandshakeComplete()) {
160 // Handshake done - pass data on as-is
161 nextFilter.messageReceived(session, buf);
162
163 } else {
164 LOGGER.debug(" Data Read: {} ({})", handler, buf);
165
166 // Keep sending handshake data to the handler until we run out
167 // of data or the handshake is finished
168 while (buf.hasRemaining() && !handler.isHandshakeComplete()) {
169 LOGGER.debug(" Pre-handshake - passing to handler");
170
171 int pos = buf.position();
172 handler.messageReceived(nextFilter, buf);
173
174 // Data not consumed or session closing
175 if (buf.position() == pos || session.isClosing()) {
176 return;
177 }
178 }
179
180 // Pass on any remaining data to the next filter
181 if (buf.hasRemaining()) {
182 LOGGER.debug(" Passing remaining data to next filter");
183
184 nextFilter.messageReceived(session, buf);
185 }
186 }
187 }
188 }
189
190 /**
191 * Filters outgoing writes, queueing them up if necessary while a handshake
192 * is ongoing.
193 *
194 * @param nextFilter the next filter in filter chain
195 * @param session the session object
196 * @param writeRequest the data to write
197 */
198 @Override
199 public void filterWrite(final NextFilter nextFilter,
200 final IoSession session, final WriteRequest writeRequest) {
201 writeData(nextFilter, session, writeRequest, false);
202 }
203
204 /**
205 * Actually write data. Queues the data up unless it relates to the handshake or the
206 * handshake is done.
207 *
208 * @param nextFilter the next filter in filter chain
209 * @param session the session object
210 * @param writeRequest the data to write
211 * @param isHandshakeData true if writeRequest is written by the proxy classes.
212 */
213 public void writeData(final NextFilter nextFilter, final IoSession session,
214 final WriteRequest writeRequest, final boolean isHandshakeData) {
215 ProxyLogicHandler handler = getProxyHandler(session);
216
217 synchronized (handler) {
218 if (handler.isHandshakeComplete()) {
219 // Handshake is done - write data as normal
220 nextFilter.filterWrite(session, writeRequest);
221 } else if (isHandshakeData) {
222 LOGGER.debug(" handshake data: {}", writeRequest.getMessage());
223
224 // Writing handshake data
225 nextFilter.filterWrite(session, writeRequest);
226 } else {
227 // Writing non-handshake data before the handshake finished
228 if (!session.isConnected()) {
229 // Not even connected - ignore
230 LOGGER.debug(" Write request on closed session. Request ignored.");
231 } else {
232 // Queue the data to be sent as soon as the handshake completes
233 LOGGER.debug(" Handshaking is not complete yet. Buffering write request.");
234 handler.enqueueWriteRequest(nextFilter, writeRequest);
235 }
236 }
237 }
238 }
239
240 /**
241 * Filter handshake related messages from reaching the messageSent callbacks of
242 * downstream filters.
243 *
244 * @param nextFilter the next filter in filter chain
245 * @param session the session object
246 * @param writeRequest the data written
247 */
248 @Override
249 public void messageSent(final NextFilter nextFilter,
250 final IoSession session, final WriteRequest writeRequest)
251 throws Exception {
252 if (writeRequest.getMessage() != null
253 && writeRequest.getMessage() instanceof ProxyHandshakeIoBuffer) {
254 // Ignore buffers used in handshaking
255 return;
256 }
257
258 nextFilter.messageSent(session, writeRequest);
259 }
260
261 /**
262 * Called when the session is created. Will create the handler able to handle
263 * the {@link ProxyIoSession#getRequest()} request stored in the session. Event
264 * is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
265 * in the chain when the handshake would have succeed. This will prevent the rest of
266 * the filter chain from being affected by this filter internals.
267 *
268 * Please note that this event can occur multiple times because of some http
269 * proxies not handling keep-alive connections thus needing multiple sessions
270 * during the handshake.
271 *
272 * @param nextFilter the next filter in filter chain
273 * @param session the session object
274 */
275 @Override
276 public void sessionCreated(NextFilter nextFilter, IoSession session)
277 throws Exception {
278 LOGGER.debug("Session created: " + session);
279 ProxyIoSession proxyIoSession = (ProxyIoSession) session
280 .getAttribute(ProxyIoSession.PROXY_SESSION);
281 LOGGER.debug(" get proxyIoSession: " + proxyIoSession);
282 proxyIoSession.setProxyFilter(this);
283
284 // Create a HTTP proxy handler and start handshake.
285 ProxyLogicHandler handler = proxyIoSession.getHandler();
286
287 // This test prevents from loosing handler conversationnal state when
288 // reconnection occurs during an http handshake.
289 if (handler == null) {
290 ProxyRequest request = proxyIoSession.getRequest();
291
292 if (request instanceof SocksProxyRequest) {
293 SocksProxyRequest req = (SocksProxyRequest) request;
294 if (req.getProtocolVersion() == SocksProxyConstants.SOCKS_VERSION_4) {
295 handler = new Socks4LogicHandler(proxyIoSession);
296 } else {
297 handler = new Socks5LogicHandler(proxyIoSession);
298 }
299 } else {
300 handler = new HttpSmartProxyHandler(proxyIoSession);
301 }
302
303 proxyIoSession.setHandler(handler);
304 handler.doHandshake(nextFilter);
305 }
306
307 proxyIoSession.getEventQueue().enqueueEventIfNecessary(
308 new IoSessionEvent(nextFilter, session,
309 IoSessionEventType.CREATED));
310 }
311
312 /**
313 * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
314 * in the chain when the handshake would have succeed. This will prevent the rest of
315 * the filter chain from being affected by this filter internals.
316 *
317 * @param nextFilter the next filter in filter chain
318 * @param session the session object
319 */
320 @Override
321 public void sessionOpened(NextFilter nextFilter, IoSession session)
322 throws Exception {
323 ProxyIoSession proxyIoSession = (ProxyIoSession) session
324 .getAttribute(ProxyIoSession.PROXY_SESSION);
325 proxyIoSession.getEventQueue().enqueueEventIfNecessary(
326 new IoSessionEvent(nextFilter, session,
327 IoSessionEventType.OPENED));
328 }
329
330 /**
331 * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
332 * in the chain when the handshake would have succeed. This will prevent the rest of
333 * the filter chain from being affected by this filter internals.
334 *
335 * @param nextFilter the next filter in filter chain
336 * @param session the session object
337 */
338 @Override
339 public void sessionIdle(NextFilter nextFilter, IoSession session,
340 IdleStatus status) throws Exception {
341 ProxyIoSession proxyIoSession = (ProxyIoSession) session
342 .getAttribute(ProxyIoSession.PROXY_SESSION);
343 proxyIoSession.getEventQueue().enqueueEventIfNecessary(
344 new IoSessionEvent(nextFilter, session, status));
345 }
346
347 /**
348 * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
349 * in the chain when the handshake would have succeed. This will prevent the rest of
350 * the filter chain from being affected by this filter internals.
351 *
352 * @param nextFilter the next filter in filter chain
353 * @param session the session object
354 */
355 @Override
356 public void sessionClosed(NextFilter nextFilter, IoSession session)
357 throws Exception {
358 ProxyIoSession proxyIoSession = (ProxyIoSession) session
359 .getAttribute(ProxyIoSession.PROXY_SESSION);
360 proxyIoSession.getEventQueue().enqueueEventIfNecessary(
361 new IoSessionEvent(nextFilter, session,
362 IoSessionEventType.CLOSED));
363 }
364 }