View Javadoc

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 The Apache MINA Project (dev@mina.apache.org)
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 }