0N/A/*
2362N/A * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/A/**
0N/A *
0N/A * This class includes a proxy server that processes HTTP CONNECT requests,
0N/A * and tunnels the data from the client to the server, once the CONNECT
0N/A * request is accepted.
0N/A * It is used by the TunnelThroughProxy test.
0N/A */
0N/A
0N/Aimport java.io.*;
0N/Aimport java.net.*;
0N/Aimport javax.net.ssl.*;
0N/Aimport javax.net.ServerSocketFactory;
0N/Aimport sun.net.www.*;
0N/A
0N/Apublic class ProxyTunnelServer extends Thread {
0N/A
0N/A private static ServerSocket ss = null;
0N/A /*
0N/A * holds the registered user's username and password
0N/A * only one such entry is maintained
0N/A */
0N/A private String userPlusPass;
0N/A
0N/A // client requesting for a tunnel
0N/A private Socket clientSocket = null;
0N/A
0N/A /*
0N/A * Origin server's address and port that the client
0N/A * wants to establish the tunnel for communication.
0N/A */
0N/A private InetAddress serverInetAddr;
0N/A private int serverPort;
0N/A
0N/A /*
0N/A * denote whether the proxy needs to authorize
0N/A * CONNECT requests.
0N/A */
0N/A static boolean needAuth = false;
0N/A
0N/A public ProxyTunnelServer() throws IOException {
0N/A if (ss == null) {
0N/A ss = (ServerSocket) ServerSocketFactory.getDefault().
0N/A createServerSocket(0);
0N/A }
0N/A }
0N/A
0N/A public void needUserAuth(boolean auth) {
0N/A needAuth = auth;
0N/A }
0N/A
0N/A /*
0N/A * register users with the proxy, by providing username and
0N/A * password. The username and password are used for authorizing the
0N/A * user when a CONNECT request is made and needAuth is set to true.
0N/A */
0N/A public void setUserAuth(String uname, String passwd) {
0N/A userPlusPass = uname + ":" + passwd;
0N/A }
0N/A
0N/A public void run() {
0N/A try {
0N/A clientSocket = ss.accept();
0N/A processRequests();
0N/A } catch (Exception e) {
0N/A System.out.println("Proxy Failed: " + e);
0N/A e.printStackTrace();
0N/A try {
0N/A ss.close();
0N/A }
0N/A catch (IOException excep) {
0N/A System.out.println("ProxyServer close error: " + excep);
0N/A excep.printStackTrace();
0N/A }
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * Processes the CONNECT requests, if needAuth is set to true, then
0N/A * the name and password are extracted from the Proxy-Authorization header
0N/A * of the request. They are checked against the one that is registered,
0N/A * if there is a match, connection is set in tunneling mode. If
0N/A * needAuth is set to false, Proxy-Authorization checks are not made
0N/A */
0N/A private void processRequests() throws Exception {
0N/A
0N/A InputStream in = clientSocket.getInputStream();
0N/A MessageHeader mheader = new MessageHeader(in);
0N/A String statusLine = mheader.getValue(0);
0N/A
0N/A if (statusLine.startsWith("CONNECT")) {
0N/A // retrieve the host and port info from the status-line
0N/A // retrieveConnectInfo(statusLine);
0N/A if (needAuth) {
0N/A String authInfo;
0N/A if ((authInfo = mheader.findValue("Proxy-Authorization"))
0N/A != null) {
0N/A if (authenticate(authInfo)) {
0N/A needAuth = false;
0N/A System.out.println(
0N/A "Proxy: client authentication successful");
0N/A }
0N/A }
0N/A }
0N/A respondForConnect(needAuth);
0N/A
0N/A // connection set to the tunneling mode
0N/A if (!needAuth) {
0N/A // doTunnel();
0N/A /*
0N/A * done with tunneling, we process only one successful
0N/A * tunneling request
0N/A */
0N/A ss.close();
0N/A } else {
0N/A // we may get another request with Proxy-Authorization set
0N/A in.close();
0N/A clientSocket.close();
0N/A restart();
0N/A }
0N/A } else {
0N/A System.out.println("proxy server: processes only "
0N/A + "CONNECT method requests, recieved: "
0N/A + statusLine);
0N/A }
0N/A }
0N/A
0N/A private void respondForConnect(boolean needAuth) throws Exception {
0N/A
0N/A OutputStream out = clientSocket.getOutputStream();
0N/A PrintWriter pout = new PrintWriter(out);
0N/A
0N/A if (needAuth) {
0N/A pout.println("HTTP/1.1 407 Proxy Auth Required");
0N/A pout.println("Proxy-Authenticate: Basic realm=\"WallyWorld\"");
0N/A pout.println();
0N/A pout.flush();
0N/A out.close();
0N/A } else {
0N/A pout.println("HTTP/1.1 500 Server Error");
0N/A pout.println();
0N/A pout.flush();
0N/A out.close();
0N/A }
0N/A }
0N/A
0N/A private void restart() throws IOException {
0N/A (new Thread(this)).start();
0N/A }
0N/A
0N/A /*sc
0N/A * note: Tunneling has to be provided in both directions, i.e
0N/A * from client->server and server->client, even if the application
0N/A * data may be unidirectional, SSL handshaking data flows in either
0N/A * direction.
0N/A */
0N/A private void doTunnel() throws Exception {
0N/A
0N/A Socket serverSocket = new Socket(serverInetAddr, serverPort);
0N/A ProxyTunnel clientToServer = new ProxyTunnel(
0N/A clientSocket, serverSocket);
0N/A ProxyTunnel serverToClient = new ProxyTunnel(
0N/A serverSocket, clientSocket);
0N/A clientToServer.start();
0N/A serverToClient.start();
0N/A System.out.println("Proxy: Started tunneling.......");
0N/A
0N/A clientToServer.join();
0N/A serverToClient.join();
0N/A System.out.println("Proxy: Finished tunneling........");
0N/A
0N/A clientToServer.close();
0N/A serverToClient.close();
0N/A }
0N/A
0N/A /*
0N/A * This inner class provides unidirectional data flow through the sockets
0N/A * by continuously copying bytes from the input socket onto the output
0N/A * socket, until both sockets are open and EOF has not been received.
0N/A */
0N/A class ProxyTunnel extends Thread {
0N/A Socket sockIn;
0N/A Socket sockOut;
0N/A InputStream input;
0N/A OutputStream output;
0N/A
0N/A public ProxyTunnel(Socket sockIn, Socket sockOut)
0N/A throws Exception {
0N/A this.sockIn = sockIn;
0N/A this.sockOut = sockOut;
0N/A input = sockIn.getInputStream();
0N/A output = sockOut.getOutputStream();
0N/A }
0N/A
0N/A public void run() {
0N/A int BUFFER_SIZE = 400;
0N/A byte[] buf = new byte[BUFFER_SIZE];
0N/A int bytesRead = 0;
0N/A int count = 0; // keep track of the amount of data transfer
0N/A
0N/A try {
0N/A while ((bytesRead = input.read(buf)) >= 0) {
0N/A output.write(buf, 0, bytesRead);
0N/A output.flush();
0N/A count += bytesRead;
0N/A }
0N/A } catch (IOException e) {
0N/A /*
0N/A * The peer end has closed the connection
0N/A * we will close the tunnel
0N/A */
0N/A close();
0N/A }
0N/A }
0N/A
0N/A public void close() {
0N/A try {
0N/A if (!sockIn.isClosed())
0N/A sockIn.close();
0N/A if (!sockOut.isClosed())
0N/A sockOut.close();
0N/A } catch (IOException ignored) { }
0N/A }
0N/A }
0N/A
0N/A /*
0N/A ***************************************************************
0N/A * helper methods follow
0N/A ***************************************************************
0N/A */
0N/A
0N/A /*
0N/A * This method retrieves the hostname and port of the destination
0N/A * that the connect request wants to establish a tunnel for
0N/A * communication.
0N/A * The input, connectStr is of the form:
0N/A * CONNECT server-name:server-port HTTP/1.x
0N/A */
0N/A private void retrieveConnectInfo(String connectStr) throws Exception {
0N/A int starti;
0N/A int endi;
0N/A String connectInfo;
0N/A String serverName = null;
0N/A try {
0N/A starti = connectStr.indexOf(' ');
0N/A endi = connectStr.lastIndexOf(' ');
0N/A connectInfo = connectStr.substring(starti+1, endi).trim();
0N/A // retrieve server name and port
0N/A endi = connectInfo.indexOf(':');
0N/A serverName = connectInfo.substring(0, endi);
0N/A serverPort = Integer.parseInt(connectInfo.substring(endi+1));
0N/A } catch (Exception e) {
0N/A throw new IOException("Proxy recieved a request: "
0N/A + connectStr);
0N/A }
0N/A serverInetAddr = InetAddress.getByName(serverName);
0N/A }
0N/A
0N/A public int getPort() {
0N/A return ss.getLocalPort();
0N/A }
0N/A
0N/A /*
0N/A * do "basic" authentication, authInfo is of the form:
0N/A * Basic <encoded username":"password>
0N/A * reference RFC 2617
0N/A */
0N/A private boolean authenticate(String authInfo) throws IOException {
0N/A boolean matched = false;
0N/A try {
0N/A authInfo.trim();
0N/A int ind = authInfo.indexOf(' ');
0N/A String recvdUserPlusPass = authInfo.substring(ind + 1).trim();
0N/A // extract encoded (username:passwd
0N/A if (userPlusPass.equals(
0N/A new String(
0N/A (new sun.misc.BASE64Decoder()).
0N/A decodeBuffer(recvdUserPlusPass)
0N/A ))) {
0N/A matched = true;
0N/A }
0N/A } catch (Exception e) {
0N/A throw new IOException(
0N/A "Proxy received invalid Proxy-Authorization value: "
0N/A + authInfo);
0N/A }
0N/A return matched;
0N/A }
0N/A}