1 /* 2 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.net.*; 25 import java.io.*; 26 import java.util.*; 27 import java.security.*; 28 29 /** 30 * A minimal proxy server that supports CONNECT tunneling. It does not do 31 * any header transformations. In future this could be added. 32 * Two threads are created per client connection. So, it's not 33 * intended for large numbers of parallel connections. 34 */ 35 public class ProxyServer extends Thread implements Closeable { 36 37 ServerSocket listener; 38 int port; 39 volatile boolean debug; 40 41 /** 42 * Create proxy on port (zero means don't care). Call getPort() 43 * to get the assigned port. 44 */ 45 public ProxyServer(Integer port) throws IOException { 46 this(port, false); 47 } 48 49 public ProxyServer(Integer port, Boolean debug) throws IOException { 50 this.debug = debug; 51 listener = new ServerSocket(); 52 listener.setReuseAddress(false); 53 listener.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port)); 54 this.port = listener.getLocalPort(); 55 setName("ProxyListener"); 56 setDaemon(true); 57 connections = new LinkedList<>(); 58 start(); 59 } 60 61 public ProxyServer(String s) { } 62 63 /** 64 * Returns the port number this proxy is listening on 65 */ 66 public int getPort() { 67 return port; 68 } 69 70 /** 71 * Shuts down the proxy, probably aborting any connections 72 * currently open 73 */ 74 public void close() throws IOException { 75 if (debug) System.out.println("Proxy: closing"); 76 done = true; 77 listener.close(); 78 for (Connection c : connections) { 79 c.close(); 80 } 81 } 177 return out.isAlive() || in.isAlive(); 178 } 179 180 private volatile boolean closing; 181 public synchronized void close() throws IOException { 182 closing = true; 183 if (debug) System.out.println("Closing connection (proxy)"); 184 if (serverSocket != null) serverSocket.close(); 185 if (clientSocket != null) clientSocket.close(); 186 } 187 188 int findCRLF(byte[] b) { 189 for (int i=0; i<b.length-1; i++) { 190 if (b[i] == CR && b[i+1] == LF) { 191 return i; 192 } 193 } 194 return -1; 195 } 196 197 public void init() { 198 try { 199 byte[] buf = readHeaders(clientIn); 200 int p = findCRLF(buf); 201 if (p == -1) { 202 close(); 203 return; 204 } 205 String cmd = new String(buf, 0, p, "US-ASCII"); 206 String[] params = cmd.split(" "); 207 if (params[0].equals("CONNECT")) { 208 doTunnel(params[1]); 209 } else { 210 doProxy(params[1], buf, p, cmd); 211 } 212 } catch (Throwable e) { 213 if (debug) { 214 System.out.println (e); 215 } 216 try {close(); } catch (IOException e1) {} 217 } 218 } 219 220 void doProxy(String dest, byte[] buf, int p, String cmdLine) 221 throws IOException 222 { 223 try { 224 URI uri = new URI(dest); 225 if (!uri.isAbsolute()) { 226 throw new IOException("request URI not absolute"); | 1 /* 2 * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.net.*; 25 import java.io.*; 26 import java.util.*; 27 import java.security.*; 28 import static java.nio.charset.StandardCharsets.UTF_8; 29 import static java.util.Arrays.asList; 30 import static java.util.stream.Collectors.toList; 31 32 /** 33 * A minimal proxy server that supports CONNECT tunneling. It does not do 34 * any header transformations. In future this could be added. 35 * Two threads are created per client connection. So, it's not 36 * intended for large numbers of parallel connections. 37 */ 38 public class ProxyServer extends Thread implements Closeable { 39 40 ServerSocket listener; 41 int port; 42 volatile boolean debug; 43 private final Credentials credentials; // may be null 44 45 private static class Credentials { 46 private final String name; 47 private final String password; 48 private Credentials(String name, String password) { 49 this.name = name; 50 this.password = password; 51 } 52 public String name() { return name; } 53 public String password() { return password; } 54 } 55 56 /** 57 * Create proxy on port (zero means don't care). Call getPort() 58 * to get the assigned port. 59 */ 60 public ProxyServer(Integer port) throws IOException { 61 this(port, false); 62 } 63 64 public ProxyServer(Integer port, 65 Boolean debug, 66 String username, 67 String password) 68 throws IOException 69 { 70 this(port, debug, new Credentials(username, password)); 71 } 72 73 public ProxyServer(Integer port, 74 Boolean debug) 75 throws IOException 76 { 77 this(port, debug, null); 78 } 79 80 public ProxyServer(Integer port, 81 Boolean debug, 82 Credentials credentials) 83 throws IOException 84 { 85 this.debug = debug; 86 listener = new ServerSocket(); 87 listener.setReuseAddress(false); 88 listener.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port)); 89 this.port = listener.getLocalPort(); 90 this.credentials = credentials; 91 setName("ProxyListener"); 92 setDaemon(true); 93 connections = new LinkedList<>(); 94 start(); 95 } 96 97 public ProxyServer(String s) { 98 credentials = null; 99 } 100 101 /** 102 * Returns the port number this proxy is listening on 103 */ 104 public int getPort() { 105 return port; 106 } 107 108 /** 109 * Shuts down the proxy, probably aborting any connections 110 * currently open 111 */ 112 public void close() throws IOException { 113 if (debug) System.out.println("Proxy: closing"); 114 done = true; 115 listener.close(); 116 for (Connection c : connections) { 117 c.close(); 118 } 119 } 215 return out.isAlive() || in.isAlive(); 216 } 217 218 private volatile boolean closing; 219 public synchronized void close() throws IOException { 220 closing = true; 221 if (debug) System.out.println("Closing connection (proxy)"); 222 if (serverSocket != null) serverSocket.close(); 223 if (clientSocket != null) clientSocket.close(); 224 } 225 226 int findCRLF(byte[] b) { 227 for (int i=0; i<b.length-1; i++) { 228 if (b[i] == CR && b[i+1] == LF) { 229 return i; 230 } 231 } 232 return -1; 233 } 234 235 // Checks credentials in the request against those allowable by the proxy. 236 private boolean authorized(Credentials credentials, 237 List<String> requestHeaders) { 238 List<String> authorization = requestHeaders.stream() 239 .filter(n -> n.toLowerCase(Locale.US).startsWith("proxy-authorization")) 240 .collect(toList()); 241 242 if (authorization.isEmpty()) 243 return false; 244 245 if (authorization.size() != 1) { 246 throw new IllegalStateException("Authorization unexpected count:" + authorization); 247 } 248 String value = authorization.get(0).substring("proxy-authorization".length()).trim(); 249 if (!value.startsWith(":")) 250 throw new IllegalStateException("Authorization malformed: " + value); 251 value = value.substring(1).trim(); 252 253 if (!value.startsWith("Basic ")) 254 throw new IllegalStateException("Authorization not Basic: " + value); 255 256 value = value.substring("Basic ".length()); 257 String values = new String(Base64.getDecoder().decode(value), UTF_8); 258 int sep = values.indexOf(':'); 259 if (sep < 1) { 260 throw new IllegalStateException("Authorization no colon: " + values); 261 } 262 String name = values.substring(0, sep); 263 String password = values.substring(sep + 1); 264 265 if (name.equals(credentials.name()) && password.equals(credentials.password())) 266 return true; 267 268 return false; 269 } 270 271 public void init() { 272 try { 273 byte[] buf; 274 while (true) { 275 buf = readHeaders(clientIn); 276 if (findCRLF(buf) == -1) { 277 close(); 278 return; 279 } 280 281 List<String> headers = asList(new String(buf, UTF_8).split("\r\n")); 282 // check authorization credentials, if required by the server 283 if (credentials != null && !authorized(credentials, headers)) { 284 String resp = "HTTP/1.1 407 Proxy Authentication Required\r\n" + 285 "Content-Length: 0\r\n" + 286 "Proxy-Authenticate: Basic realm=\"proxy realm\"\r\n\r\n"; 287 288 clientOut.write(resp.getBytes(UTF_8)); 289 } else { 290 break; 291 } 292 } 293 294 int p = findCRLF(buf); 295 String cmd = new String(buf, 0, p, "US-ASCII"); 296 String[] params = cmd.split(" "); 297 298 if (params[0].equals("CONNECT")) { 299 doTunnel(params[1]); 300 } else { 301 doProxy(params[1], buf, p, cmd); 302 } 303 } catch (Throwable e) { 304 if (debug) { 305 System.out.println (e); 306 } 307 try {close(); } catch (IOException e1) {} 308 } 309 } 310 311 void doProxy(String dest, byte[] buf, int p, String cmdLine) 312 throws IOException 313 { 314 try { 315 URI uri = new URI(dest); 316 if (!uri.isAbsolute()) { 317 throw new IOException("request URI not absolute"); |