package com.subgraph.orchid;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.net.SocketFactory;
import com.subgraph.orchid.circuits.TorInitializationTracker;
import com.subgraph.orchid.crypto.PRNGFixes;
import com.subgraph.orchid.dashboard.Dashboard;
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
import com.subgraph.orchid.sockets.OrchidSocketFactory;
/**
* This class is the main entry-point for running a Tor proxy
* or client.
*/
public class TorClient {
private final static Logger logger = Logger.getLogger(TorClient.class.getName());
private final TorConfig config;
private final Directory directory;
private final TorInitializationTracker initializationTracker;
private final ConnectionCache connectionCache;
private final CircuitManager circuitManager;
private final SocksPortListener socksListener;
private final DirectoryDownloaderImpl directoryDownloader;
private final Dashboard dashboard;
private boolean isStarted = false;
private boolean isStopped = false;
private final CountDownLatch readyLatch;
public TorClient() {
this(null);
}
public TorClient(DirectoryStore customDirectoryStore) {
if(Tor.isAndroidRuntime()) {
PRNGFixes.apply();
}
config = Tor.createConfig();
directory = Tor.createDirectory(config, customDirectoryStore);
initializationTracker = Tor.createInitalizationTracker();
initializationTracker.addListener(createReadyFlagInitializationListener());
connectionCache = Tor.createConnectionCache(config, initializationTracker);
directoryDownloader = Tor.createDirectoryDownloader(config, initializationTracker);
circuitManager = Tor.createCircuitManager(config, directoryDownloader, directory, connectionCache, initializationTracker);
socksListener = Tor.createSocksPortListener(config, circuitManager);
readyLatch = new CountDownLatch(1);
dashboard = new Dashboard();
dashboard.addRenderables(circuitManager, directoryDownloader, socksListener);
}
public TorConfig getConfig() {
return config;
}
public SocketFactory getSocketFactory() {
return new OrchidSocketFactory(this);
}
/**
* Start running the Tor client service.
*/
public synchronized void start() {
if(isStarted) {
return;
}
if(isStopped) {
throw new IllegalStateException("Cannot restart a TorClient instance. Create a new instance instead.");
}
logger.info("Starting Orchid (version: "+ Tor.getFullVersion() +")");
verifyUnlimitedStrengthPolicyInstalled();
directoryDownloader.start(directory);
circuitManager.startBuildingCircuits();
if(dashboard.isEnabledByProperty()) {
dashboard.startListening();
}
isStarted = true;
}
public synchronized void stop() {
if(!isStarted || isStopped) {
return;
}
try {
socksListener.stop();
if(dashboard.isListening()) {
dashboard.stopListening();
}
directoryDownloader.stop();
circuitManager.stopBuildingCircuits(true);
directory.close();
connectionCache.close();
} catch (Exception e) {
logger.log(Level.WARNING, "Unexpected exception while shutting down TorClient instance: "+ e, e);
} finally {
isStopped = true;
}
}
public Directory getDirectory() {
return directory;
}
public ConnectionCache getConnectionCache() {
return connectionCache;
}
public CircuitManager getCircuitManager() {
return circuitManager;
}
public void waitUntilReady() throws InterruptedException {
readyLatch.await();
}
public void waitUntilReady(long timeout) throws InterruptedException, TimeoutException {
if(!readyLatch.await(timeout, TimeUnit.MILLISECONDS)) {
throw new TimeoutException();
}
}
public Stream openExitStreamTo(String hostname, int port) throws InterruptedException, TimeoutException, OpenFailedException {
ensureStarted();
return circuitManager.openExitStreamTo(hostname, port);
}
private synchronized void ensureStarted() {
if(!isStarted) {
throw new IllegalStateException("Must call start() first");
}
}
public void enableSocksListener(int port) {
socksListener.addListeningPort(port);
}
public void enableSocksListener() {
enableSocksListener(9150);
}
public void enableDashboard() {
if(!dashboard.isListening()) {
dashboard.startListening();
}
}
public void enableDashboard(int port) {
dashboard.setListeningPort(port);
enableDashboard();
}
public void disableDashboard() {
if(dashboard.isListening()) {
dashboard.stopListening();
}
}
public void addInitializationListener(TorInitializationListener listener) {
initializationTracker.addListener(listener);
}
public void removeInitializationListener(TorInitializationListener listener) {
initializationTracker.removeListener(listener);
}
private TorInitializationListener createReadyFlagInitializationListener() {
return new TorInitializationListener() {
public void initializationProgress(String message, int percent) {}
public void initializationCompleted() {
readyLatch.countDown();
}
};
}
public static void main(String[] args) {
final TorClient client = new TorClient();
client.addInitializationListener(createInitalizationListner());
client.start();
client.enableSocksListener();
}
private static TorInitializationListener createInitalizationListner() {
return new TorInitializationListener() {
public void initializationProgress(String message, int percent) {
System.out.println(">>> [ "+ percent + "% ]: "+ message);
}
public void initializationCompleted() {
System.out.println("Tor is ready to go!");
}
};
}
private void verifyUnlimitedStrengthPolicyInstalled() {
try {
if(Cipher.getMaxAllowedKeyLength("AES") < 256) {
final String message = "Unlimited Strength Jurisdiction Policy Files are required but not installed.";
logger.severe(message);
throw new TorException(message);
}
} catch (NoSuchAlgorithmException e) {
logger.log(Level.SEVERE, "No AES provider found");
throw new TorException(e);
} catch (NoSuchMethodError e) {
logger.info("Skipped check for Unlimited Strength Jurisdiction Policy Files");
}
}
}