/*
* Copyright 2012, Square, Inc.
* Copyright 2012, Mahmood Ali.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Mahmood Ali. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.notnoop.apns.internal;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.Socket;
import javax.net.ssl.SSLSocketFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.httpclient.ConnectMethod;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.ProxyClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Establishes a TLS connection using an HTTP proxy. See <a
* href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817 5.2</a>. This class does
* not support proxies requiring a "Proxy-Authorization" header.
*/
public final class TlsTunnelBuilder {
private static final Logger logger = LoggerFactory.getLogger(TlsTunnelBuilder.class);
public Socket build(SSLSocketFactory factory, Proxy proxy, String proxyUsername, String proxyPassword, String host, int port)
throws IOException {
boolean success = false;
Socket proxySocket = null;
try {
logger.debug("Attempting to use proxy : " + proxy.toString());
InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
proxySocket = makeTunnel(host, port, proxyUsername, proxyPassword, proxyAddress);
// Handshake with the origin server.
if(proxySocket == null) {
throw new ProtocolException("Unable to create tunnel through proxy server.");
}
Socket socket = factory.createSocket(proxySocket, host, port, true /* auto close */);
success = true;
return socket;
} finally {
if (!success) {
Utilities.close(proxySocket);
}
}
}
@SuppressFBWarnings(value = "VA_FORMAT_STRING_USES_NEWLINE",
justification = "use <CR><LF> as according to RFC, not platform-linefeed")
Socket makeTunnel(String host, int port, String proxyUsername,
String proxyPassword, InetSocketAddress proxyAddress) throws IOException {
if(host == null || port < 0 || host.isEmpty() || proxyAddress == null){
throw new ProtocolException("Incorrect parameters to build tunnel.");
}
logger.debug("Creating socket for Proxy : " + proxyAddress.getAddress() + ":" + proxyAddress.getPort());
Socket socket;
try {
ProxyClient client = new ProxyClient();
client.getParams().setParameter("http.useragent", "java-apns");
client.getHostConfiguration().setHost(host, port);
String proxyHost = proxyAddress.getAddress().toString().substring(0, proxyAddress.getAddress().toString().indexOf("/"));
client.getHostConfiguration().setProxy(proxyHost, proxyAddress.getPort());
ProxyClient.ConnectResponse response = client.connect();
socket = response.getSocket();
if (socket == null) {
ConnectMethod method = response.getConnectMethod();
// Read the proxy's HTTP response.
if(method.getStatusLine().toString().matches("HTTP/1\\.\\d 407 Proxy Authentication Required")) {
// Proxy server returned 407. We will now try to connect with auth Header
if(proxyUsername != null && proxyPassword != null) {
socket = AuthenticateProxy(method, client,proxyHost, proxyAddress.getPort(),
proxyUsername, proxyPassword);
} else {
throw new ProtocolException("Socket not created: " + method.getStatusLine());
}
}
}
} catch (Exception e) {
throw new ProtocolException("Error occurred while creating proxy socket : " + e.toString());
}
if (socket != null) {
logger.debug("Socket for proxy created successfully : " + socket.getRemoteSocketAddress().toString());
}
return socket;
}
private Socket AuthenticateProxy(ConnectMethod method, ProxyClient client,
String proxyHost, int proxyPort,
String proxyUsername, String proxyPassword) throws IOException {
if(method.getProxyAuthState().getAuthScheme().getSchemeName().equalsIgnoreCase("ntlm")) {
// If Auth scheme is NTLM, set NT credentials with blank host and domain name
client.getState().setProxyCredentials(new AuthScope(proxyHost, proxyPort),
new NTCredentials(proxyUsername, proxyPassword,"",""));
} else {
// If Auth scheme is Basic/Digest, set regular Credentials
client.getState().setProxyCredentials(new AuthScope(proxyHost, proxyPort),
new UsernamePasswordCredentials(proxyUsername, proxyPassword));
}
ProxyClient.ConnectResponse response = client.connect();
Socket socket = response.getSocket();
if (socket == null) {
method = response.getConnectMethod();
throw new ProtocolException("Proxy Authentication failed. Socket not created: "
+ method.getStatusLine());
}
return socket;
}
}