You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1166 lines
36 KiB
Java

/*
* Copyright (C) 2009-2017 Alistair Neil <info@dazzleships.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package client;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import lib.SimpleFile;
/**
*
* @author Alistair Neil <info@dazzleships.net>
*/
public class TorController extends TorProcess {
// Event constants, which supplement the ones provide by TorProcess
public static final int EVENT_CIRCUITS_BUILT = 15;
public static final int EVENT_CIRCUITS_FAILED = 16;
public static final int EVENT_CIRCUIT_BUILT = 17;
public static final int EVENT_CIRCUIT_FAILED = 18;
public static final int EVENT_LATENCY_DONE = 19;
public static final int EVENT_TESTING_DONE = 20;
public static final int EVENT_CIRCUIT_CHANGED = 21;
public static final int EVENT_ABORTED = 22;
private static final String LOCALHOST = "127.0.0.1";
private static final String[] EVENTMESSAGES = new String[]{
"EVENT_CIRCUITS_BUILT", "EVENT_CIRCUITS_FAILED", "EVENT_CIRCUIT_BUILT",
"EVENT_CIRCUIT_FAILED", "EVENT_LATENCY_DONE", "EVENT_TESTING_DONE",
"EVENT_CIRCUIT_CHANGED", "EVENT_ABORTED"
};
// Status constants
public static final int STATUS_DEAD = 0;
public static final int STATUS_BOOTING = 1;
public static final int STATUS_IDLE = 2;
public static final int STATUS_CIRCUIT_CREATION = 3;
public static final int STATUS_LATENCY_CHECKING = 4;
// Other constants
public static final long LATENCY_FAIL = 9999;
public static final int STREAM_IP = 5;
public static final int NODE_GUARD = 0;
public static final int NODE_MIDDLE = 1;
public static final int NODE_EXIT = 2;
private static final int DEFBUILDTIME = 60;
private volatile Socket sockControl;
private volatile BufferedReader brSocket;
private volatile PrintWriter pwSocket;
private String strLatencyURL;
private final ConcurrentHashMap<String, TorCircuit> chmUseableCircuits;
private ArrayList<String> alActiveStreams;
private int intStatus = STATUS_DEAD;
private volatile Socket sockProxy;
private long lngLatency;
private String strBestHops;
private long lngBestLatency;
private String entrynodes = "";
private Thread tActive;
private boolean haveEntryNode;
/**
* Constructor
*
* @param clientpath Path to Tor client
* @param configfolder Location of configuration file torrc
*/
public TorController(String clientpath, String configfolder) {
super(clientpath, configfolder);
this.chmUseableCircuits = new ConcurrentHashMap<>();
this.alActiveStreams = new ArrayList<>();
}
/**
* Tor process event
*
* @param event
* @param data
*/
@Override
public final void torProcessEventFired(int event, String data) {
Logger.getGlobal().logp(Level.FINE, TorProcess.class.getName(),
"torProcessEventFired() on Port=" + getListenPort(), getEventMessage(event) + ", Data=" + data);
switch (event) {
case TOR_BRIDGE:
haveEntryNode = true;
break;
case TOR_STOPPED:
setStatus(STATUS_DEAD);
break;
case TOR_BOOTED:
case TOR_RESTARTED:
openControlSocket();
// Authenticate our control
authenticateTor(getSecret());
takeOwnership();
waitForBridgeNodes(DEFBUILDTIME);
setStatus(STATUS_IDLE);
break;
}
controllerEventFired(event, data);
}
@Override
public String getEventMessage(int event) {
if (event < EVENT_CIRCUITS_BUILT) {
return super.getEventMessage(event);
} else {
return EVENTMESSAGES[event - EVENT_CIRCUITS_BUILT];
}
}
/**
* Handle for controllerEventFired should be overridden by parent class
*
* @param event
* @param data
*/
public void controllerEventFired(int event, Object data) {
}
/**
* Set status flag
*
* @param status
*/
public synchronized void setStatus(int status) {
intStatus = status;
}
/**
* Get current status
*
* @return status integer constant, see defined status constants
*/
public synchronized int getStatus() {
return intStatus;
}
/**
* Convenience test for idle status
*
* @return true if idle
*/
public boolean isIdle() {
return (getStatus() <= STATUS_IDLE);
}
/**
* Stop the tor controller process completely
*
*/
public final void stop() {
haveEntryNode = false;
abortActions();
Logger.getGlobal().logp(Level.INFO, TorController.class.getName(),
"stop() on Port=" + getListenPort(), "Stop requested");
sendCommand("QUIT");
closeControlSocket();
stopProcess();
setStatus(STATUS_DEAD);
}
/**
* Start tor controller process and issue the TOR_BOOTED event
*
*/
public final void start() {
createDefaultConfig();
start(TOR_BOOTED);
}
/**
* Start tor controller process and issue the given event
*
* @param bootevent
*/
public final void start(int bootevent) {
if (getStatus() == STATUS_DEAD) {
setInitialBootEvent(bootevent);
setStatus(STATUS_BOOTING);
startProcess();
return;
}
if (getStatus() > STATUS_BOOTING) {
setStatus(STATUS_IDLE);
controllerEventFired(bootevent, null);
}
}
/**
* Abort all current actions
*/
public final void abortActions() {
if (getStatus() < STATUS_IDLE) {
return;
}
setStatus(STATUS_IDLE);
// Abort latency checking
abortLatencyCheck();
// Interrupt sleep thread if active
Thread t = getActiveThread();
if (t != null) {
t.interrupt();
}
}
/**
* Abort latency checking
*/
public final void abortLatencyCheck() {
// Kill off latency checking proxy socket
if (sockProxy != null) {
try {
sockProxy.close();
} catch (IOException ex) {
}
}
sockProxy = null;
}
/**
* Save configuration file
*/
public void saveConf() {
SimpleFile sf = new SimpleFile(getConfigFilePath());
sf.delete();
sendCommand("SAVECONF");
}
/**
* Load configuration file
*/
public void loadConf() {
SimpleFile sf = new SimpleFile(getConfigFilePath());
sf.openBufferedRead();
String text = sf.readEntireFile();
sf.closeFile();
sendCommand("+loadconf\r\n" + text + "\r\n.");
}
/**
* Attempts to return the Country associated with an ip address
*
* @param ip
* @return Country or null if not found
*/
public String getCountryFromIP(String ip) {
// Get country from ip address
String cmd = "ip-to-country/" + ip;
ArrayList<String> infoList = getInfo(cmd);
try {
if (infoList != null) {
return infoList.get(0).toUpperCase();
}
} catch (Exception ex) {
}
return null;
}
/**
* Set the url used for latency checking
*
* @param url
*/
public void setTestingURL(String url) {
strLatencyURL = url;
}
/**
* Take ownership of the Tor client process so that it shuts down if the
* process is destroyed, particular useful for linux desktops that dont
* issue proper terminations.
*/
private void takeOwnership() {
sendCommand("TAKEOWNERSHIP");
sendCommand("RESETCONF __OwningControllerProcess");
}
/**
* Authenticate a password protected tor control socket
*
* @param password
*/
private void authenticateTor(String password) {
if (password == null) {
sendCommand("AUTHENTICATE");
} else {
sendCommand("AUTHENTICATE " + "\"" + password + "\"");
}
}
/**
* Enable/Disable predictive circuit building
*
* @param enabled
*/
public void enablePredictiveCircuits(boolean enabled) {
if (enabled) {
setConf("__DisablePredictedCircuits=0");
} else {
setConf("__DisablePredictedCircuits=1");
}
}
/**
* Get tor status information for a given property. See TOR control-spec
* documentation for valid properties
*
* @param property
* @return Arraylist containg the result of the command
*/
public final ArrayList<String> getInfo(String property) {
return sendCommand("GETINFO " + property);
}
/**
* Close a circuit with ID
*
* @param id
*/
public final void closeCircuit(String id) {
sendCommand("CLOSECIRCUIT " + id);
}
/**
* Set a tor configuration property
*
* @param property
* @return Arraylist containg the result of the command
*/
public final ArrayList<String> setConf(String property) {
return sendCommand("SETCONF " + property);
}
/**
* reset a tor configuration property
*
* @param property
* @return Arraylist containing the result of the command
*/
public final ArrayList<String> resetConf(String property) {
return sendCommand("RESETCONF " + property);
}
/**
* Send signal to tor
*
* @param cmd
* @return Arraylist containing the result of the command
*/
public final ArrayList<String> signal(String cmd) {
return sendCommand("SIGNAL " + cmd);
}
/**
* Get entry guards chosen by tor client
*
* @return entry guards as comma separated fingerprints
*/
public String getEntryGuardsAsCSV() {
ArrayList<String> al = getInfo("entry-guards");
StringBuilder sbResult = new StringBuilder();
if (!al.contains("250 OK")) {
return sbResult.toString();
}
// Remove 250 OK entry
al.remove("250 OK");
String sep = "";
for (String s : al) {
if (s.contains("~")) {
sbResult.append(sep);
sbResult.append(s.substring(0, s.indexOf('~')));
if (sep.isEmpty()) {
sep = ",";
}
}
}
return sbResult.toString();
}
/**
* Triggers a socks latency check, EVENT_LATENCY_CHECK is fired on
* completion
*
* @param timeout
*/
public final void doLatencyCheck(final int timeout) {
Thread t = new Thread(new java.lang.Runnable() {
long latency;
@Override
public void run() {
latency = getTorLatency(timeout);
SwingUtilities.invokeLater(new java.lang.Runnable() {
@Override
public void run() {
lngLatency = latency;
//? controllerEventFired(EVENT_LATENCY_DONE, null);
}
});
}
});
if (getStatus() < STATUS_IDLE) {
return;
}
alActiveStreams = getInfo("stream-status");
alActiveStreams.remove("250 OK");
t.start();
}
/**
* Get current latency
*
* @return latency in ms as long
*/
public final long getLatency() {
return lngLatency;
}
/**
* Returns measured latency for the active circuit without creating a
* stream, this blocks so be careful
*
* @param timeout
* @return latency
*/
public long getTorLatency(int timeout) {
long lngResult = LATENCY_FAIL;
DataInputStream dis = null;
try {
sockProxy = createTorSocketToURL(strLatencyURL, true);
if (sockProxy != null) {
sockProxy.setSoTimeout(timeout);
dis = new DataInputStream(sockProxy.getInputStream());
long lngStart = System.currentTimeMillis();
dis.skipBytes(1);
lngResult = System.currentTimeMillis() - lngStart;
}
} catch (IOException ex) {
Logger.getGlobal().logp(Level.INFO, TorController.class.getName(),
"getTorLatency Exception " + getListenPort(), ex.getMessage());
}
try {
if (!sockProxy.isClosed()) {
sockProxy.close();
}
sockProxy = null;
} catch (Exception ex) {
}
try {
if (dis != null) {
dis.close();
}
} catch (Exception ex) {
}
return lngResult;
}
/**
* Close open circuits except circuit specified by id
*
* @param id
* @param filtered
*/
public void closeCircuitsExcept(String id, boolean filtered) {
Logger.getGlobal().logp(Level.FINEST, TorController.class.getName(),
"closeCircuitsExcept() Port=" + getListenPort(), "");
Set keys = getBuiltCircuits(filtered).keySet();
Iterator i = keys.iterator();
String hopid;
while (i.hasNext()) {
hopid = (String) i.next();
if (!hopid.contentEquals(id)) {
closeCircuit(hopid);
}
}
}
private String getCircuitIdFromStream(String stream) {
String data[];
Pattern pat = Pattern.compile(" ");
data = pat.split(stream);
return data[2];
}
/**
* Get list of active streams
*
* @return active streams as a list
*/
public final ArrayList<String> getActiveStreams() {
return alActiveStreams;
}
/**
* Set the csv list of exit node fingers to be used by tor, a single exit
* node may also be specified, this does not block
*
* @param fingers
* @param nocircs
*/
public final void activateNodes(final String fingers, final int nocircs) {
Thread t = new Thread(new java.lang.Runnable() {
@Override
public void run() {
activateNodesBlocking(fingers, nocircs);
SwingUtilities.invokeLater(new java.lang.Runnable() {
@Override
public void run() {
if (isIdle()) {
controllerEventFired(EVENT_ABORTED, null);
return;
}
setStatus(STATUS_IDLE);
if (chmUseableCircuits.isEmpty()) {
controllerEventFired(EVENT_CIRCUITS_FAILED, 0);
} else {
controllerEventFired(EVENT_CIRCUITS_BUILT, chmUseableCircuits.size());
}
}
});
}
});
if (getStatus() < STATUS_IDLE) {
return;
}
abortActions();
setStatus(STATUS_CIRCUIT_CREATION);
t.start();
}
/**
* Activate given circuit, does not block
*
* @param hops
*/
public final void activateCircuit(final String hops) {
Thread t = new Thread(new java.lang.Runnable() {
@Override
public void run() {
activateCircuitBlocking(hops);
SwingUtilities.invokeLater(new java.lang.Runnable() {
@Override
public void run() {
if (isIdle()) {
controllerEventFired(EVENT_ABORTED, null);
return;
}
setStatus(STATUS_IDLE);
if (chmUseableCircuits.isEmpty()) {
controllerEventFired(EVENT_CIRCUIT_FAILED, null);
} else {
controllerEventFired(EVENT_CIRCUIT_BUILT, null);
}
}
});
}
});
if (getStatus() < STATUS_IDLE) {
return;
}
setStatus(STATUS_CIRCUIT_CREATION);
t.start();
}
/**
* Set entry nodes
*
* @param fingers
*/
public void setEntryNodes(String fingers) {
entrynodes = "";
if (fingers == null || !getBridges().isEmpty()) {
return;
}
entrynodes = fingers;
}
/**
* Get configured entry nodes
*
* @return entry nodes in CSV format
*/
public String getEntryNodes() {
return entrynodes;
}
/**
* Set the csv list of exit node fingers to be used by tor, a single exit
* node may also be specified, this blocks
*
* @param fingers
* @param nocircs
*/
public void activateNodesBlocking(String fingers, int nocircs) {
chmUseableCircuits.clear();
enablePredictiveCircuits(true);
setConf("EntryNodes=" + entrynodes);
setConf("ExitNodes=" + fingers);
if (fingers.isEmpty() || fingers.contains("{")) {
fingers = null;
}
waitForCircuits(DEFBUILDTIME, nocircs, fingers);
enablePredictiveCircuits(false);
}
/**
* Activate given circuit, blocks
*
* @param hops
*/
private void activateCircuitBlocking(String hops) {
chmUseableCircuits.clear();
sendCommand("EXTENDCIRCUIT 0 " + hops + " PURPOSE=GENERAL");
waitForCircuit(DEFBUILDTIME, hops);
}
/**
* Wait so many seconds for valid bridges to appear if bridges are set
*
* @param secs
*/
private void waitForBridgeNodes(int secs) {
long timeout = System.currentTimeMillis() + (secs * 1000);
while (!haveValidEntryNode()) {
try {
Thread.sleep(250);
if (System.currentTimeMillis() > timeout) {
break;
}
} catch (InterruptedException ex) {
break;
}
}
}
public boolean haveValidEntryNode() {
if (getBridges().isEmpty()) {
return true;
}
return haveEntryNode;
}
private void waitForCircuits(long secs, int nocircs, String reqFinger) {
long timeout = System.currentTimeMillis() + (secs * nocircs * 1000);
setActiveThread(Thread.currentThread());
while (chmUseableCircuits.size() < nocircs) {
try {
Thread.sleep(250);
} catch (InterruptedException ex) {
break;
}
if (System.currentTimeMillis() > timeout) {
Logger.getGlobal().logp(Level.INFO, TorController.class
.getName(),
"waitForCircuits() on Port=" + getListenPort(), "Timed Out");
break;
}
if (isIdle()) {
Logger.getGlobal().logp(Level.INFO, TorController.class
.getName(),
"waitForCircuits() on Port=" + getListenPort(), "Aborting");
chmUseableCircuits.clear();
break;
}
TorCircuit tc = getLatestCircuit();
if (tc != null) {
// If we have a node request then ensure that the circuit contains the requested node fingerprint
if (reqFinger != null && !reqFinger.contains(tc.getExit(TorCircuit.FINGER))) {
continue;
}
// If we have custom guards, then ensure circuit contains guard
if (entrynodes.isEmpty()) {
chmUseableCircuits.put(tc.getID(), tc);
} else if (entrynodes.contains(tc.getGuard(TorCircuit.FINGER))) {
chmUseableCircuits.put(tc.getID(), tc);
}
}
}
}
private void waitForCircuit(long secs, String reqHops) {
long timeout = System.currentTimeMillis() + (secs * 1000);
setActiveThread(Thread.currentThread());
while (chmUseableCircuits.size() < 1) {
try {
Thread.sleep(250);
} catch (InterruptedException ex) {
break;
}
if (System.currentTimeMillis() > timeout) {
Logger.getGlobal().logp(Level.INFO, TorController.class
.getName(),
"waitForCircuit() on Port=" + getListenPort(), "Timed Out");
break;
}
if (isIdle()) {
Logger.getGlobal().logp(Level.INFO, TorController.class
.getName(),
"waitForCircuit() on Port=" + getListenPort(), "Aborting");
chmUseableCircuits.clear();
break;
}
TorCircuit tcActive = getLatestCircuit();
if (tcActive != null) {
String hops = tcActive.getHops();
if (hops.contains(reqHops)) {
chmUseableCircuits.put(tcActive.getID(), tcActive);
}
}
}
}
private synchronized void setActiveThread(Thread t) {
tActive = t;
}
private synchronized Thread getActiveThread() {
return tActive;
}
/**
* Test specified node, non blocking
*
* @param finger
*/
public void testNode(final String finger) {
SwingWorker<Void, Integer> sw = new SwingWorker<Void, Integer>() {
private long bestLatency = LATENCY_FAIL;
private String bestHops = null;
@Override
protected Void doInBackground() {
closeCircuitsExcept("", true);
activateNodesBlocking(finger, 1);
if (isIdle()) {
return null;
}
if (chmUseableCircuits.size() > 0) {
String id = chmUseableCircuits.keys().nextElement();
TorCircuit tc = chmUseableCircuits.get(id);
closeCircuitsExcept("", true);
activateCircuitBlocking(tc.getHops());
publish(EVENT_CIRCUIT_BUILT);
long latency = getTorLatency(5000);
if (latency < bestLatency) {
bestLatency = latency;
bestHops = tc.getHops();
}
}
return null;
}
@Override
protected void process(List<Integer> chunks) {
for (Integer i : chunks) {
controllerEventFired(i, chmUseableCircuits.size());
}
}
@Override
protected void done() {
if (isIdle()) {
lngBestLatency = LATENCY_FAIL;
strBestHops = null;
controllerEventFired(EVENT_ABORTED, null);
} else {
setStatus(STATUS_IDLE);
lngBestLatency = bestLatency;
strBestHops = bestHops;
controllerEventFired(EVENT_TESTING_DONE, chmUseableCircuits.size());
}
}
};
setStatus(STATUS_CIRCUIT_CREATION);
strBestHops = null;
lngBestLatency = LATENCY_FAIL;
sw.execute();
}
/**
* Get best latency
*
* @return best latency value in ms as long
*/
public final long getBestLatency() {
return lngBestLatency;
}
/**
* Get best hops
*
* @return hops info as string
*/
public final String getBestHops() {
return strBestHops;
}
/**
* Get built circuits
*
* @param filtered Filter out unwanted circuits
* @return HashMap of built circuits keyed to their circuit id
*/
public final HashMap<String, TorCircuit> getBuiltCircuits(boolean filtered) {
HashMap<String, TorCircuit> hm = new HashMap<>();
ArrayList<String> circuits = getInfo("circuit-status");
circuits.remove("250 OK");
for (String circuit : circuits) {
if (circuit.contains("BUILT")) {
if (filtered) {
if (circuit.contains("ONEHOP_TUNNEL")) {
continue;
}
if (circuit.contains("IS_INTERNAL")) {
continue;
}
if (!circuit.contains("PURPOSE=GENERAL")) {
continue;
}
}
TorCircuit tc = new TorCircuit(circuit);
hm.put(tc.getID(), tc);
// Debug purposes
// if (getListenPort() == 9054) {
// System.out.println("Circuit=" + circuit);
// }
}
}
return hm;
}
/**
* Verify we have comms on the control socket
*
* @return true if its good
*/
public final boolean verifyControlComms() {
ArrayList<String> circuits = getInfo("circuit-status");
return !circuits.isEmpty();
}
/**
* Get the latest circuit
*
* @return circuit
*/
public final TorCircuit getLatestCircuit() {
HashMap<String, TorCircuit> hm = getBuiltCircuits(true);
Iterator i = getBuiltCircuits(true).keySet().iterator();
String strId;
TorCircuit tcRecent = null;
int hid = 0;
int id;
while (i.hasNext()) {
strId = (String) i.next();
id = Integer.parseInt(strId);
if (id > hid) {
hid = id;
tcRecent = hm.get(strId);
}
}
return tcRecent;
}
private void openControlSocket() {
try {
sockControl = new Socket(LOCALHOST, getControlPort());
sockControl.setKeepAlive(true);
sockControl.setSoTimeout(2000);
pwSocket = new PrintWriter(sockControl.getOutputStream());
brSocket = new BufferedReader(new InputStreamReader(sockControl.getInputStream()), 1024);
} catch (IOException ex) {
Logger.getGlobal().throwing(TorController.class
.getName(),
"openControlSocket() on Port=" + getListenPort(), ex);
}
}
private void closeControlSocket() {
try {
if (sockControl != null) {
sockControl.setKeepAlive(false);
sockControl.close();
sockControl = null;
pwSocket.close();
brSocket.close();
pwSocket = null;
brSocket = null;
}
} catch (IOException ex) {
Logger.getGlobal().throwing(TorController.class
.getName(),
"closeControlSocket() on Port=" + getListenPort(), ex);
}
}
/**
* Send a command to tor client
*
* @param command
* @return Arraylist containing the result of the command
*/
public final synchronized ArrayList<String> sendCommand(String command) {
ArrayList<String> result = new ArrayList<>();
if (sockControl == null) {
Logger.getGlobal().logp(Level.WARNING, TorController.class
.getName(),
"sendCommand() Port=" + getListenPort(), "Cmd=" + command
+ ", Non-existent socket");
return result;
}
if (sockControl.isClosed()) {
Logger.getGlobal().logp(Level.WARNING, TorController.class
.getName(),
"sendCommand() Port=" + getListenPort(), "Cmd=" + command
+ ", Socket is closed.");
return result;
}
flushReadBuffer();
// Write out the command
if (pwSocket.checkError()) {
Logger.getGlobal().logp(Level.FINEST, TorController.class
.getName(),
"sendCommand() Port=" + getListenPort(), "Cmd=" + command
+ ", Write Socket Error");
return result;
}
pwSocket.write(command + "\r\n");
pwSocket.flush();
// Dont wait for command response if quitting
if (command.contentEquals("QUIT")) {
return result;
}
result = getCommandResponse();
if (!result.isEmpty()) {
Logger.getGlobal().logp(Level.FINEST, TorController.class
.getName(),
"sendCommand() Port=" + getListenPort(), "Cmd=" + command
+ ", Response=" + result.toString());
} else {
Logger.getGlobal().logp(Level.WARNING, TorController.class
.getName(),
"sendCommand() Port=" + getListenPort(), "Cmd=" + command
+ ", Response=Timed out");
}
return result;
}
private ArrayList<String> getCommandResponse() {
boolean boolMultiReply = false;
String response;
ArrayList<String> result = new ArrayList<>();
try {
while (true) {
response = brSocket.readLine();
if (response == null) {
break;
}
// Check to see if we are in the middle of a multi line reply
if (boolMultiReply) {
// A multiline extended reply is part terminated by a fullstop
if (response.startsWith(".")) {
boolMultiReply = false;
} else {
result.add(response);
}
continue;
}
// Check for the start of a multi line reply
if (response.startsWith("250+")) {
boolMultiReply = true;
response = response.substring(response.indexOf('=') + 1).trim();
if (!response.isEmpty()) {
result.add(response);
}
continue;
}
// Check for single line reply
if (response.startsWith("250-")) {
response = response.substring(response.indexOf('=') + 1).trim();
if (!response.isEmpty()) {
result.add(response);
}
continue;
}
result.add(response);
if (response.startsWith("250 ") || response.startsWith("251 ")
|| response.startsWith("4") || response.startsWith("5")) {
break;
}
}
} catch (IOException ex) {
result.clear();
}
return result;
}
private void flushReadBuffer() {
try {
while (brSocket.ready()) {
brSocket.readLine();
}
} catch (IOException ex) {
}
}
/**
* Create a socks4a socket to this URL on this tor connection, if nowww is
* true then remove www. from domain
*
* @param url
* @param nowww
* @return socket
*/
public Socket createTorSocketToURL(String url, boolean nowww) {
try {
URI uri = new URI(url);
String host = uri.getHost().toLowerCase();
if (nowww) {
host = host.replace("www.", "");
}
String protocol = uri.getScheme();
int port = uri.getPort();
if (port == -1) {
switch (protocol) {
default:
port = 80;
break;
case "https":
port = 443;
break;
}
}
return createSocks4aSocket(LOCALHOST, getListenPort(), host, port);
} catch (URISyntaxException ex) {
Logger.getLogger(TorController.class
.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
/**
* Create a Socks4a socket Taken from Wikipedia SOCKS4a is a simple
* extension to SOCKS4 protocol that allows a client that cannot resolve the
* destination host's domain name to specify it.
*
* The client should set the first three bytes of DSTIP to NULL and the last
* byte to a non-zero value. (This corresponds to IP address 0.0.0.x, with x
* nonzero, an inadmissible destination address and thus should never occur
* if the client can resolve the domain name.) Following the NULL byte
* terminating USERID, the client must send the destination domain name and
* terminate it with another NULL byte. This is used for both "connect" and
* "bind" requests.
*
* Client to SOCKS server: field 1: SOCKS version number, 1 byte, must be
* 0x04 for this version field 2: command code, 1 byte: 0x01 = establish a
* TCP/IP stream connection 0x02 = establish a TCP/IP port binding field 3:
* network byte order port number, 2 bytes field 4: deliberate invalid IP
* address, 4 bytes, first three must be 0x00 and the last one must not be
* 0x00 field 5: the user ID string, variable length, terminated with a null
* (0x00) field 6: the domain name of the host we want to contact, variable
* length, terminated with a null (0x00)
*
*
* Server to SOCKS client: field 1: null byte field 2: status, 1 byte: 0x5a
* = request granted 0x5b = request rejected or failed 0x5c = request failed
* because client is not running identd (or not reachable from the server)
* 0x5d = request failed because client's identd could not confirm the user
* ID string in the request field 3: network byte order port number, 2 bytes
* field 4: network byte order IP address, 4 bytes
*
* A server using protocol SOCKS4A must check the DSTIP in the request
* packet. If it represents address 0.0.0.x with nonzero x, the server must
* read in the domain name that the client sends in the packet. The server
* should resolve the domain name and make connection to the destination
* host if it can.
*
* @param socksaddr Socks ip address
* @param socksport Socks port
* @param remotehost Remote host
* @param remoteport Remote port
* @return Socket
*/
public Socket createSocks4aSocket(String socksaddr, int socksport, String remotehost, int remoteport) {
try {
Socket s = new Socket(socksaddr, socksport);
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeByte(0x04); // Version 4 Socks
dos.writeByte(0x01); // Connect command code
dos.writeShort(remoteport); // Remote Port number
dos.writeInt(0x01); // IP address of 0.0.0.1 means use Socks 4a
dos.writeByte(0x00); // Null terminator
dos.writeBytes(remotehost); // Remote host IP address
dos.writeByte(0x00); // Null terminator
return s;
} catch (IOException ex) {
Logger.getGlobal().logp(Level.FINE, this.getClass().getName(), "createSocks4aSocket", "", ex);
}
return null;
}
}