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.getLogger(ProxyFilter.class);
59
60 /**
61 * Create a new {@link ProxyFilter}.
62 */
63 public ProxyFilter() {
64 // Do nothing
65 }
66
67 /**
68 * Called before the filter is added into the filter chain.
69 * Checks if chain already holds an {@link ProxyFilter} instance.
70 *
71 * @param chain the filter chain
72 * @param name the name assigned to this filter
73 * @param nextFilter the next filter
74 * @throws IllegalStateException if chain already contains an instance of
75 * {@link ProxyFilter}
76 */
77 @Override
78 public void onPreAdd(final IoFilterChain chain, final String name, final NextFilter nextFilter) {
79 if (chain.contains(ProxyFilter.class)) {
80 throw new IllegalStateException("A filter chain cannot contain more than one ProxyFilter.");
81 }
82 }
83
84 /**
85 * Called when the filter is removed from the filter chain.
86 * Cleans the {@link ProxyIoSession} instance from the session.
87 *
88 * @param chain the filter chain
89 * @param name the name assigned to this filter
90 * @param nextFilter the next filter
91 */
92 @Override
93 public void onPreRemove(final IoFilterChain chain, final String name, final NextFilter nextFilter) {
94 IoSession session = chain.getSession();
95 session.removeAttribute(ProxyIoSession.PROXY_SESSION);
96 }
97
98 /**
99 * Called when an exception occurs in the chain. A flag is set in the
100 * {@link ProxyIoSession} session's instance to signal that handshake
101 * failed.
102 *
103 * @param chain the filter chain
104 * @param name the name assigned to this filter
105 * @param nextFilter the next filter
106 */
107 @Override
108 public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception {
109 ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
110 proxyIoSession.setAuthenticationFailed(true);
111 super.exceptionCaught(nextFilter, session, cause);
112 }
113
114 /**
115 * Get the {@link ProxyLogicHandler} for a given session.
116 *
117 * @param session the session object
118 * @return the handler which will handle handshaking with the proxy
119 */
120 private ProxyLogicHandler getProxyHandler(final IoSession session) {
121 ProxyLogicHandler handler = ((ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION)).getHandler();
122
123 if (handler == null) {
124 throw new IllegalStateException();
125 }
126
127 // Sanity check
128 if (handler.getProxyIoSession().getProxyFilter() != this) {
129 throw new IllegalArgumentException("Not managed by this filter.");
130 }
131
132 return handler;
133 }
134
135 /**
136 * Receives data from the remote host, passes to the handler if a handshake is in progress,
137 * otherwise passes on transparently.
138 *
139 * @param nextFilter the next filter in filter chain
140 * @param session the session object
141 * @param message the object holding the received data
142 */
143 @Override
144 public void messageReceived(final NextFilter nextFilter, final IoSession session, final Object message)
145 throws ProxyAuthException {
146 ProxyLogicHandler handler = getProxyHandler(session);
147
148 synchronized (handler) {
149 IoBuffer buf = (IoBuffer) message;
150
151 if (handler.isHandshakeComplete()) {
152 // Handshake done - pass data on as-is
153 nextFilter.messageReceived(session, buf);
154
155 } else {
156 LOGGER.debug(" Data Read: {} ({})", handler, buf);
157
158 // Keep sending handshake data to the handler until we run out
159 // of data or the handshake is finished
160 while (buf.hasRemaining() && !handler.isHandshakeComplete()) {
161 LOGGER.debug(" Pre-handshake - passing to handler");
162
163 int pos = buf.position();
164 handler.messageReceived(nextFilter, buf);
165
166 // Data not consumed or session closing
167 if (buf.position() == pos || session.isClosing()) {
168 return;
169 }
170 }
171
172 // Pass on any remaining data to the next filter
173 if (buf.hasRemaining()) {
174 LOGGER.debug(" Passing remaining data to next filter");
175
176 nextFilter.messageReceived(session, buf);
177 }
178 }
179 }
180 }
181
182 /**
183 * Filters outgoing writes, queueing them up if necessary while a handshake
184 * is ongoing.
185 *
186 * @param nextFilter the next filter in filter chain
187 * @param session the session object
188 * @param writeRequest the data to write
189 */
190 @Override
191 public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) {
192 writeData(nextFilter, session, writeRequest, false);
193 }
194
195 /**
196 * Actually write data. Queues the data up unless it relates to the handshake or the
197 * handshake is done.
198 *
199 * @param nextFilter the next filter in filter chain
200 * @param session the session object
201 * @param writeRequest the data to write
202 * @param isHandshakeData true if writeRequest is written by the proxy classes.
203 */
204 public void writeData(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest,
205 final boolean isHandshakeData) {
206 ProxyLogicHandler handler = getProxyHandler(session);
207
208 synchronized (handler) {
209 if (handler.isHandshakeComplete()) {
210 // Handshake is done - write data as normal
211 nextFilter.filterWrite(session, writeRequest);
212 } else if (isHandshakeData) {
213 LOGGER.debug(" handshake data: {}", writeRequest.getMessage());
214
215 // Writing handshake data
216 nextFilter.filterWrite(session, writeRequest);
217 } else {
218 // Writing non-handshake data before the handshake finished
219 if (!session.isConnected()) {
220 // Not even connected - ignore
221 LOGGER.debug(" Write request on closed session. Request ignored.");
222 } else {
223 // Queue the data to be sent as soon as the handshake completes
224 LOGGER.debug(" Handshaking is not complete yet. Buffering write request.");
225 handler.enqueueWriteRequest(nextFilter, writeRequest);
226 }
227 }
228 }
229 }
230
231 /**
232 * Filter handshake related messages from reaching the messageSent callbacks of
233 * downstream filters.
234 *
235 * @param nextFilter the next filter in filter chain
236 * @param session the session object
237 * @param writeRequest the data written
238 */
239 @Override
240 public void messageSent(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest)
241 throws Exception {
242 if (writeRequest.getMessage() != null && writeRequest.getMessage() instanceof ProxyHandshakeIoBuffer) {
243 // Ignore buffers used in handshaking
244 return;
245 }
246
247 nextFilter.messageSent(session, writeRequest);
248 }
249
250 /**
251 * Called when the session is created. Will create the handler able to handle
252 * the {@link ProxyIoSession#getRequest()} request stored in the session. Event
253 * is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
254 * in the chain when the handshake would have succeed. This will prevent the rest of
255 * the filter chain from being affected by this filter internals.
256 *
257 * Please note that this event can occur multiple times because of some http
258 * proxies not handling keep-alive connections thus needing multiple sessions
259 * during the handshake.
260 *
261 * @param nextFilter the next filter in filter chain
262 * @param session the session object
263 */
264 @Override
265 public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
266 LOGGER.debug("Session created: " + session);
267 ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
268 LOGGER.debug(" get proxyIoSession: " + proxyIoSession);
269 proxyIoSession.setProxyFilter(this);
270
271 // Create a HTTP proxy handler and start handshake.
272 ProxyLogicHandler handler = proxyIoSession.getHandler();
273
274 // This test prevents from loosing handler conversationnal state when
275 // reconnection occurs during an http handshake.
276 if (handler == null) {
277 ProxyRequest request = proxyIoSession.getRequest();
278
279 if (request instanceof SocksProxyRequest) {
280 SocksProxyRequest req = (SocksProxyRequest) request;
281 if (req.getProtocolVersion() == SocksProxyConstants.SOCKS_VERSION_4) {
282 handler = new Socks4LogicHandler(proxyIoSession);
283 } else {
284 handler = new Socks5LogicHandler(proxyIoSession);
285 }
286 } else {
287 handler = new HttpSmartProxyHandler(proxyIoSession);
288 }
289
290 proxyIoSession.setHandler(handler);
291 handler.doHandshake(nextFilter);
292 }
293
294 proxyIoSession.getEventQueue().enqueueEventIfNecessary(
295 new IoSessionEvent(nextFilter, session, IoSessionEventType.CREATED));
296 }
297
298 /**
299 * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
300 * in the chain when the handshake would have succeed. This will prevent the rest of
301 * the filter chain from being affected by this filter internals.
302 *
303 * @param nextFilter the next filter in filter chain
304 * @param session the session object
305 */
306 @Override
307 public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
308 ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
309 proxyIoSession.getEventQueue().enqueueEventIfNecessary(
310 new IoSessionEvent(nextFilter, session, IoSessionEventType.OPENED));
311 }
312
313 /**
314 * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
315 * in the chain when the handshake would have succeed. This will prevent the rest of
316 * the filter chain from being affected by this filter internals.
317 *
318 * @param nextFilter the next filter in filter chain
319 * @param session the session object
320 */
321 @Override
322 public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
323 ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
324 proxyIoSession.getEventQueue().enqueueEventIfNecessary(new IoSessionEvent(nextFilter, session, status));
325 }
326
327 /**
328 * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
329 * in the chain when the handshake would have succeed. This will prevent the rest of
330 * the filter chain from being affected by this filter internals.
331 *
332 * @param nextFilter the next filter in filter chain
333 * @param session the session object
334 */
335 @Override
336 public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception {
337 ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
338 proxyIoSession.getEventQueue().enqueueEventIfNecessary(
339 new IoSessionEvent(nextFilter, session, IoSessionEventType.CLOSED));
340 }
341 }