001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.proxy.filter;
021
022import org.apache.mina.core.buffer.IoBuffer;
023import org.apache.mina.core.filterchain.IoFilter;
024import org.apache.mina.core.filterchain.IoFilterAdapter;
025import org.apache.mina.core.filterchain.IoFilterChain;
026import org.apache.mina.core.session.IdleStatus;
027import org.apache.mina.core.session.IoSession;
028import org.apache.mina.core.write.WriteRequest;
029import org.apache.mina.proxy.ProxyAuthException;
030import org.apache.mina.proxy.ProxyConnector;
031import org.apache.mina.proxy.ProxyLogicHandler;
032import org.apache.mina.proxy.event.IoSessionEvent;
033import org.apache.mina.proxy.event.IoSessionEventQueue;
034import org.apache.mina.proxy.event.IoSessionEventType;
035import org.apache.mina.proxy.handlers.ProxyRequest;
036import org.apache.mina.proxy.handlers.http.HttpSmartProxyHandler;
037import org.apache.mina.proxy.handlers.socks.Socks4LogicHandler;
038import org.apache.mina.proxy.handlers.socks.Socks5LogicHandler;
039import org.apache.mina.proxy.handlers.socks.SocksProxyConstants;
040import org.apache.mina.proxy.handlers.socks.SocksProxyRequest;
041import org.apache.mina.proxy.session.ProxyIoSession;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045/**
046 * ProxyFilter.java - Proxy {@link IoFilter}. 
047 * Automatically inserted into the {@link IoFilter} chain by {@link ProxyConnector}.
048 * Sends the initial handshake message to the proxy and handles any response
049 * to the handshake. Once the handshake has completed and the proxied connection has been
050 * established this filter becomes transparent to data flowing through the connection.
051 * <p>
052 * Based upon SSLFilter from mina-filter-ssl.
053 * 
054 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
055 * @since MINA 2.0.0-M3
056 */
057public class ProxyFilter extends IoFilterAdapter {
058    private final static Logger LOGGER = LoggerFactory.getLogger(ProxyFilter.class);
059
060    /**
061     * Create a new {@link ProxyFilter}.
062     */
063    public ProxyFilter() {
064        // Do nothing
065    }
066
067    /**
068     * Called before the filter is added into the filter chain.
069     * Checks if chain already holds an {@link ProxyFilter} instance. 
070     * 
071     * @param chain the filter chain
072     * @param name the name assigned to this filter
073     * @param nextFilter the next filter
074     * @throws IllegalStateException if chain already contains an instance of 
075     * {@link ProxyFilter}
076     */
077    @Override
078    public void onPreAdd(final IoFilterChain chain, final String name, final NextFilter nextFilter) {
079        if (chain.contains(ProxyFilter.class)) {
080            throw new IllegalStateException("A filter chain cannot contain more than one ProxyFilter.");
081        }
082    }
083
084    /**
085     * Called when the filter is removed from the filter chain.
086     * Cleans the {@link ProxyIoSession} instance from the session.
087     * 
088     * @param chain the filter chain
089     * @param name the name assigned to this filter
090     * @param nextFilter the next filter
091     */
092    @Override
093    public void onPreRemove(final IoFilterChain chain, final String name, final NextFilter nextFilter) {
094        IoSession session = chain.getSession();
095        session.removeAttribute(ProxyIoSession.PROXY_SESSION);
096    }
097
098    /**
099     * 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 nextFilter next filter in the filter chain
104     * @param session the MINA session
105     * @param cause the original exception
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}