import java.io.*;
import java.net.*;
import java.util.*;
import javax.net.ssl.*;
import java.security.*;
import javaforce.JFLog;
public class Service extends Thread {
public static final String version = "1.5.0";
public Service(int port, String proxyHost, Logger logger, boolean alwaysSecure, boolean neverSecure, String ext) {
this.proxyHost = proxyHost;
this.logger = logger;
this.alwaysSecure = alwaysSecure;
this.neverSecure = neverSecure;
this.ext = ext;
if (port != -1) this.port = port;
new File(getUserPath() + "/.jfwebproxy").mkdir();
deleteKeys();
try {
InputStream is = getClass().getClassLoader().getResourceAsStream("localhost.key");
FileOutputStream fos = new FileOutputStream(getUserPath() + "/.jfwebproxy/localhost.key");
copyAll(is, fos);
fos.close();
} catch (Exception e) {
logger.log(e);
}
keytool(new String[] {
"-exportcert",
"-alias",
"localhost",
"-keystore",
"localhost.key",
"-storepass",
"password",
"-file",
"localhost.crt"
}, getUserPath() + "/.jfwebproxy");
initSSL();
}
//instance data
private int port = 8080;
//static shared data
private static String proxyHost;
private static boolean alwaysSecure = false;
private static boolean neverSecure = false;
private static Logger logger;
private static int nextSSLport = 8081;
private static int refID = 0;
private static synchronized int getRefID() {
return refID++;
}
private static String ext;
public static String getUserPath() {
return System.getProperty("user.home");
}
public static boolean copyAll(InputStream is, OutputStream os) {
try {
int len = is.available();
byte buf[] = new byte[1024];
int copied = 0;
while (copied < len) {
int read = is.read(buf);
if (read == 0) {
continue;
}
if (read == -1) {
return false;
}
os.write(buf, 0, read);
copied += read;
}
return true;
} catch (Exception e) {
logger.log(e);
return false;
}
}
private void log(String msg) {
logger.log(msg);
}
private void log(Throwable t) {
logger.log(t);
}
public void deleteKeys() {
//delete all temp files in ~/.jfwebproxy
File files[] = new File(getUserPath() + "/.jfwebproxy").listFiles();
if (files != null) {
for(int a=0;a<files.length;a++) {
files[a].delete();
}
}
}
public void close() {
try {
logger.log("Closing port:" + port);
ss.close();
SecureSite ssList[] = secureSites.values().toArray(new SecureSite[0]);
for(int a=0;a<ssList.length;a++) {
ssList[a].close();
}
secureSites.clear();
deleteKeys();
} catch (Exception e) {}
//close list
Session sess;
while (list.size() > 0) {
sess = list.get(0);
sess.close();
}
}
/** Checks if system is Windows only. */
public static boolean isWindows() {
return (File.separatorChar == '\\');
}
/** Executes keytool directly */
public static boolean keytool(String args[], String path) {
ArrayList<String> cmd = new ArrayList<String>();
try {
// sun.security.tools.KeyTool.main(args); //no longer available in Java 8
if (isWindows()) {
cmd.add(System.getProperty("java.home") + "\\bin\\keytool.exe");
} else {
cmd.add(System.getProperty("java.home") + "/bin/keytool");
}
for(int a=0;a<args.length;a++) {
cmd.add(args[a]);
}
Process p = Runtime.getRuntime().exec(cmd.toArray(new String[0]), null, new File(path));
p.waitFor();
return true;
} catch (Exception e) {
logger.log(e);
return false;
}
}
private static ServerSocket ss;
private static Vector<Session> list = new Vector<Session>();
private static HashMap<String, SecureSite> secureSites = new HashMap<String,SecureSite>();
private static SSLContext gsc;
private static SSLSocketFactory socketFactory;
public static void initSSL() {
try {
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
}
};
// Let us create the factory where we can set some parameters for the connection
gsc = SSLContext.getInstance("SSL");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(getUserPath() + "/.jfwebproxy/localhost.key"), "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "password".toCharArray());
gsc.init(kmf.getKeyManagers(), trustAllCerts, new java.security.SecureRandom());
socketFactory = (SSLSocketFactory) gsc.getSocketFactory(); //this method will work with untrusted certs
} catch (Exception e) {
e.printStackTrace();
}
}
public static class SecureSite extends Thread {
public String domain;
public SSLContext sc;
public SSLServerSocketFactory serverSocketFactory;
public SSLServerSocket ss;
public int port;
public Object lock = new Object();
public SecureSite(String domain, int port) {
this.domain = domain;
this.port = port;
}
public void run() {
//create a cert for domain signed by localhost
keytool(new String[] {
"-genkeypair",
"-alias",
domain,
"-keystore",
domain + ".key",
"-storepass",
"password",
"-keypass",
"password",
"-keyalg",
"RSA",
"-dname",
"CN=" + domain + ", OU=JavaForce, O=JavaForce, C=CA",
"-validity",
"3650"
}, getUserPath() + "/.jfwebproxy");
//create csr
keytool(new String[] {
"-certreq",
"-alias",
domain,
"-keystore",
domain + ".key",
"-file",
domain + ".csr",
"-storepass",
"password"
}, getUserPath() + "/.jfwebproxy");
//sign key
keytool(new String[] {
"-gencert",
"-alias",
"localhost",
"-keystore",
"localhost.key",
"-storepass",
"password",
"-keyalg",
"RSA",
"-infile",
domain + ".csr",
"-outfile",
domain + ".crt",
"-validity",
"3650"
}, getUserPath() + "/.jfwebproxy");
//import cert auth (optional ???)
keytool(new String[] {
"-import",
"-alias",
"root",
"-file",
"localhost.crt",
"-keystore",
domain + ".key",
"-storepass",
"password",
"-noprompt"
}, getUserPath() + "/.jfwebproxy");
//import signed cert
keytool(new String[] {
"-import",
"-alias",
domain,
"-file",
domain + ".crt",
"-keystore",
domain + ".key",
"-storepass",
"password"
}, getUserPath() + "/.jfwebproxy");
try {
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
}
};
// Let us create the factory where we can set some parameters for the connection
sc = SSLContext.getInstance("SSL");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(getUserPath() + "/.jfwebproxy/" + domain + ".key"), "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "password".toCharArray());
sc.init(kmf.getKeyManagers(), trustAllCerts, new java.security.SecureRandom());
serverSocketFactory = (SSLServerSocketFactory) sc.getServerSocketFactory(); //this method will work with untrusted certs
SSLSocket s;
Session sess;
ss = (SSLServerSocket) serverSocketFactory.createServerSocket(port);
synchronized(lock) {
lock.notify();
}
while (!ss.isClosed()) {
s = (SSLSocket)ss.accept();
sess = new Session(s, true);
sess.start();
}
} catch (Exception e) {
logger.log(e);
}
}
public void close() {
try {
logger.log("Closing port:" + port);
ss.close();
} catch (Exception e) {
logger.log(e);
}
}
}
public void run() {
Socket s;
Session sess;
try {
ss = new ServerSocket(port);
while (!ss.isClosed()) {
s = ss.accept();
sess = new Session(s, false);
sess.start();
}
} catch (Exception e) {
log(e);
}
}
public static class Session extends Thread {
private Socket p, i; //proxy client, internet
private InputStream pis, iis;
private OutputStream pos, ios;
private byte buf[] = new byte[4096];
private final int bufsiz = 4096;
private int refID = -1;
private String httpver; //"1.0" or "1.1"
private boolean secure;
public synchronized void close() {
try {
if ((p!=null) && (p.isConnected())) p.close();
p = null;
} catch (Exception e1) {}
try {
if ((i!=null) && (i.isConnected())) i.close();
i = null;
} catch (Exception e2) {}
list.remove(this);
}
public Session(Socket s, boolean secure) {
p = s;
this.secure = secure;
}
public void run() {
refID = getRefID();
StringBuilder req;
int ch;
list.add(this);
httpver = "1.1"; //unless 1.0
try {
pis = p.getInputStream();
pos = p.getOutputStream();
do {
log("reading request");
req = new StringBuilder();
do {
ch = pis.read();
if (ch == -1) {close(); return;}
req.append((char)ch);
} while (!req.toString().endsWith("\r\n\r\n") && p.isConnected());
proxy(req.toString());
} while (httpver.equals("1.1"));
p.close();
} catch (Exception e) {}
close();
}
private void proxy(String req) throws Exception {
String headers[] = req.split("\r\n");
int hostidx = -1;
if (headers[0].endsWith("1.0")) httpver = "1.0";
for(int a=0;a<headers.length;a++) {
if (headers[a].regionMatches(true, 0, "Host: ", 0, 6)) hostidx = a;
}
try {
log("request=" + headers[0] + ":port=" + p.getPort());
if (headers[0].regionMatches(true, 0, "POST ", 0, 5)) {
if (hostidx == -1) {
log("ERROR : No host specified : " + req);
sendError(505, "No host specified");
return;
}
sendRequest(req, headers, getPost(req));
return;
}
if (headers[0].regionMatches(true, 0, "GET ", 0, 4)) {
if (hostidx == -1) {
log("ERROR : No host specified : " + req);
sendError(505, "No host specified");
return;
}
sendRequest(req, headers, null);
return;
}
if (headers[0].regionMatches(true, 0, "CONNECT ", 0, 8)) {
log("CONNECT");
if (secure) {
sendError(505, "Already secure?");
return;
}
connectCommand(headers[0]);
return;
}
sendError(505, "Unknown request");
}
catch (IOException ioe) {
/*do nothing*/
log(ioe);
}
catch (Exception e) {
sendError(505, "Exception:" + e); e.printStackTrace(new PrintStream(pos));
log(e);
}
}
private void sendError(int code, String msg) throws Exception {
String str = "HTTP/" + httpver + " " + code + " " + msg + "\r\n\r\n" + "<h1>Error : " + code + " : " + msg + "</h1>";
pos.write(str.getBytes());
pos.flush();
}
private String getPost(String req) throws Exception {
String ln[] = req.split("\r\n");
int length = -1;
for(int a=0;a<ln.length;a++) {
if (ln[a].regionMatches(true, 0, "Content-Length: ", 0, 16)) {
length = Integer.valueOf(ln[a].substring(16));
break;
}
}
if (length == -1) {
log("error:post length unknown");
return null;
}
byte post[] = new byte[length];
int pos = 0;
while (pos != length) {
int read = pis.read(post, pos, length - pos);
if (read == -1) {
log("error:post read data failed");
return null;
}
pos += read;
}
return new String(post,0,length);
}
private void sendRequest(String proxyreq, String headers[], String proxypost) throws Exception {
StringBuilder post = new StringBuilder();
post.append("headers=" + URLEncoder.encode(proxyreq, "UTF-8"));
post.append("&secure=" + secure);
if (proxypost != null) {
post.append("&post=" + URLEncoder.encode(proxypost, "UTF-8"));
} else {
post.append("&post="); //avoid errors on server-side
}
if (alwaysSecure || (secure && !neverSecure)) {
i = socketFactory.createSocket(proxyHost, 443);
} else {
i = new Socket(proxyHost, 80);
}
ios = i.getOutputStream();
StringBuilder request = new StringBuilder();
request.append("POST /redir2." + ext + " HTTP/1.0\r\n");
request.append("Host: " + proxyHost + "\r\n");
request.append("Content-Length:" + post.length() + "\r\n");
request.append("Content-Type: application/x-www-form-urlencoded\r\n");
request.append("\r\n");
ios.write(request.toString().getBytes());
ios.write(post.toString().getBytes());
iis = i.getInputStream();
//relayReply
int read;
// FileOutputStream fos = new FileOutputStream("test.dat");
int total = 0;
byte pheaders[] = new byte[0]; //proxy headers (ignored)
boolean header = false;
do {
read = iis.read(buf, 0, bufsiz);
if (read == -1) {
break;
}
if (read > 0) {
if (!header) {
int oldLength = pheaders.length;
pheaders = Arrays.copyOf(pheaders, pheaders.length + read);
System.arraycopy(buf, 0, pheaders, oldLength, read);
if (pheaders.length > 4) {
int len = pheaders.length-4;
for(int a=0;a<=len;a++) {
if (pheaders[a] == '\r' && pheaders[a+1] == '\n' && pheaders[a+2] == '\r' && pheaders[a+3] == '\n') {
header = true;
a+=4;
if (a < pheaders.length) {
pos.write(pheaders, a, pheaders.length-a);
// fos.write(pheaders, a, pheaders.length-a);
}
pheaders = null;
break;
}
}
}
} else {
pos.write(buf, 0, read);
// fos.write(buf, 0, read);
}
total += read;
}
} while (true);
log("size=" + total);
// fos.close();
pos.flush();
}
private void connectCommand(String reqln0) throws Exception {
String ln[] = reqln0.split(" ");
if (ln.length != 3) {
sendError(505, "Bad CONNECT syntax");
return;
}
String host = ln[1];
int portidx = ln[1].indexOf(':');
if (portidx != -1) {
int port = Integer.valueOf(ln[1].substring(portidx+1));
if (port != 443) {
sendError(505, "CONNECT is for port 443 only");
return;
}
host = ln[1].substring(0, portidx);
}
int sslport = getSSLPort(host);
i = new Socket("localhost", sslport); //connecting to SECURE localport
iis = i.getInputStream();
ios = i.getOutputStream();
pos.write(("HTTP/" + httpver + " 200 Connection established\r\n\r\n").getBytes());
pos.flush();
ConnectRelay i2p = new ConnectRelay(iis, pos, "i2p", p);
ConnectRelay p2i = new ConnectRelay(pis, ios, "p2i", i);
i2p.start();
p2i.start();
i2p.join();
p2i.join();
httpver = "1.0"; //discard socket
}
private void log(String msg) {
logger.log("[" + refID + (secure ? ":S" : "") + "]" + msg);
}
private void log(Throwable t) {
logger.log("[" + refID + (secure ? ":S" : "") + "] Exception...");
logger.log(t);
}
private class ConnectRelay extends Thread {
private InputStream is;
private OutputStream os;
private byte buf[] = new byte[4096];
private final int buflen = 4096;
private String name;
private Socket other;
public ConnectRelay(InputStream is, OutputStream os, String name, Socket other) {
this.is = is;
this.os = os;
this.name = name;
this.other = other;
}
public void run() {
int read;
try {
// log("ConnectRelay start:" + name);
while (true) {
read = is.read(buf, 0, buflen);
// log("read:" + name + "=" + read);
if (read == -1) break;
if (read > 0) {os.write(buf, 0, read); os.flush();}
// log("write:" + name + "=" + read);
}
// log("ConnectRelay stop:" + name);
} catch (Exception e) {
// log("ConnectRelay exception:" + name);
// log(e);
}
try { other.close(); } catch (Exception e) {}
}
}
}
public static int getSSLPort(String host) {
if (host.equals("localhost")) return -1; //should not happen
if (host.equals("127.0.0.1")) return -1; //should not happen
SecureSite secureSite;
synchronized(secureSites) {
secureSite = secureSites.get(host);
if (secureSite != null) {
return secureSite.port;
}
logger.log("Creating new SSL site:" + host);
secureSite = new SecureSite(host, nextSSLport++);
secureSites.put(host, secureSite);
}
synchronized(secureSite.lock) {
secureSite.start();
try {secureSite.lock.wait();} catch (Exception e) {
logger.log(e);
}
}
return secureSite.port;
}
public static void initHttps() {
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
}
};
// Let us create the factory where we can set some parameters for the connection
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) sc.getSocketFactory(); //this method will work with untrusted certs
HttpsURLConnection.setDefaultSSLSocketFactory(sslsocketfactory);
} catch (Exception e) {
JFLog.log(e);
}
//trust any hostname
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
if (!urlHostName.equalsIgnoreCase(session.getPeerHost())) {
System.out.println("Warning: URL host '" + urlHostName + "' is different to SSLSession host '" + session.getPeerHost() + "'.");
}
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
}