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