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.
758 lines
24 KiB
Java
758 lines
24 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.text.Collator;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.regex.Pattern;
|
|
import lib.Localisation;
|
|
import lib.SimpleFile;
|
|
|
|
/**
|
|
*
|
|
* @author Alistair Neil <info@dazzleships.net>
|
|
*/
|
|
public class NodeList {
|
|
|
|
public static final int NODELIST_IDLE = 0;
|
|
public static final int NODELIST_BUILDING = 1;
|
|
public static final int NODELIST_BUILT = 2;
|
|
public static final int NODELIST_FAILED = 3;
|
|
public static final int NODELIST_TERMINATED = 4;
|
|
private static final String EMPTYSTRING = "";
|
|
private static final Localisation LOCAL = new Localisation("resources/MessagesBundle");
|
|
private ExitNodeTableModel entm;
|
|
private GuardNodeTableModel gntm;
|
|
private int intStatus = NODELIST_IDLE;
|
|
private HashMap<String, NodeItem> hmNode;
|
|
private final HashMap<String, NodeItem> hmBridges;
|
|
private final ArrayList<String> alCountries;
|
|
private TorController tc;
|
|
private final String exitFavouritesFile;
|
|
private final String guardFavouritesFile;
|
|
private final String filepath;
|
|
private volatile boolean boolAbortActions;
|
|
private final Pattern pattspace = Pattern.compile(" ");
|
|
private int nooffavs;
|
|
|
|
public NodeList(String filepath, String exitfavourites, String guardfavouritesfile) {
|
|
this.hmNode = new HashMap<>();
|
|
this.hmBridges = new HashMap<>();
|
|
this.filepath = filepath;
|
|
this.alCountries = new ArrayList<>();
|
|
this.exitFavouritesFile = exitfavourites;
|
|
this.guardFavouritesFile = guardfavouritesfile;
|
|
}
|
|
|
|
/**
|
|
* Get node item from its fingerprint
|
|
*
|
|
* @param key or fingerprint
|
|
* @return NodeItem
|
|
*/
|
|
public final NodeItem getNode(String key) {
|
|
String temp;
|
|
NodeItem ni = hmNode.get(key);
|
|
if (ni == null) {
|
|
// No bridge found so must be a new node not currently in nodelist
|
|
ArrayList<String> alRouterInfo = getRouterDesc(key);
|
|
if (alRouterInfo != null) {
|
|
alRouterInfo.addAll(getRouterStatus(key));
|
|
ni = new NodeItem();
|
|
temp = filterRouterInfo(alRouterInfo, "router ");
|
|
if (temp == null) {
|
|
return null;
|
|
}
|
|
String[] data = pattspace.split(temp);
|
|
ni.setFingerprint(key);
|
|
ni.setNickName(data[0]);
|
|
ni.setIPAddress(data[1]);
|
|
String isocode = tc.getCountryFromIP(ni.getIPAddress());
|
|
if (isocode == null || isocode.contentEquals("??")) {
|
|
isocode = "U1";
|
|
}
|
|
ni.setCountryCode(isocode);
|
|
ni.setCountryName(LOCAL.getDisplayCountry(isocode));
|
|
temp = filterRouterInfo(alRouterInfo, "bandwidth ");
|
|
data = pattspace.split(temp);
|
|
ni.setBandwidth(getLowestBandwidth(data));
|
|
temp = filterRouterInfo(alRouterInfo, "s ");
|
|
if (temp == null) {
|
|
// We probably have a bridge
|
|
ni.setType(NodeItem.TYPE_GUARD);
|
|
ni.setStable(LOCAL.getString("text_yes"));
|
|
return ni;
|
|
}
|
|
if (temp.contains("Guard")) {
|
|
ni.setType(NodeItem.TYPE_GUARD);
|
|
}
|
|
if (temp.contains("Stable")) {
|
|
ni.setStable(LOCAL.getString("text_yes"));
|
|
} else {
|
|
ni.setStable(LOCAL.getString("text_no"));
|
|
}
|
|
if (temp.contains("Exit") && !temp.contains("BadExit")) {
|
|
ni.setType(NodeItem.TYPE_EXIT);
|
|
temp = filterRouterInfo(alRouterInfo, "p ");
|
|
if (temp.startsWith("accept")) {
|
|
temp = temp.replace("accept ", "");
|
|
if (!containsPort(temp, 80) && !containsPort(temp, 443)) {
|
|
return ni;
|
|
}
|
|
} else {
|
|
temp = temp.replace("reject ", "");
|
|
if (containsPort(temp, 80) || containsPort(temp, 443)) {
|
|
return ni;
|
|
}
|
|
}
|
|
ni.setHttpSupported(true);
|
|
}
|
|
}
|
|
}
|
|
return ni;
|
|
}
|
|
|
|
public ArrayList<String> getRouterDesc(String finger) {
|
|
ArrayList<String> alResult = tc.getInfo("desc/id/" + finger);
|
|
if (alResult == null) {
|
|
return null;
|
|
}
|
|
alResult.remove("250 OK");
|
|
if (alResult.isEmpty()) {
|
|
return null;
|
|
}
|
|
return alResult;
|
|
}
|
|
|
|
public ArrayList<String> getRouterStatus(String finger) {
|
|
ArrayList<String> alResult = tc.getInfo("ns/id/" + finger);
|
|
if (alResult == null) {
|
|
return null;
|
|
}
|
|
alResult.remove("250 OK");
|
|
if (alResult.isEmpty()) {
|
|
return null;
|
|
}
|
|
return alResult;
|
|
}
|
|
|
|
public String filterRouterInfo(ArrayList<String> alInfo, String field) {
|
|
for (String s : alInfo) {
|
|
if (s.startsWith(field)) {
|
|
return s.replace(field, "");
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the process status
|
|
*
|
|
* @return status
|
|
*/
|
|
public final int getCurrentStatus() {
|
|
return intStatus;
|
|
}
|
|
|
|
/**
|
|
* Generate the nodelist
|
|
*
|
|
* @param tc
|
|
*/
|
|
public final void refreshNodelist(TorController tc) {
|
|
boolAbortActions = false;
|
|
intStatus = NODELIST_BUILDING;
|
|
this.tc = tc;
|
|
boolean boolSuccess = populateNodeMap();
|
|
if (boolSuccess) {
|
|
boolSuccess = fetchFingerData();
|
|
}
|
|
if (boolSuccess) {
|
|
intStatus = NODELIST_BUILT;
|
|
} else if (boolAbortActions) {
|
|
intStatus = NODELIST_TERMINATED;
|
|
} else {
|
|
intStatus = NODELIST_FAILED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update internal node mappings
|
|
*
|
|
*/
|
|
private boolean populateNodeMap() {
|
|
String strFlags;
|
|
String data[];
|
|
String temp;
|
|
String strNick;
|
|
String strIP;
|
|
String strCountryName;
|
|
String strCountryCode;
|
|
String strPerms;
|
|
String strDirPort;
|
|
String strOrPort;
|
|
|
|
NodeItem ni;
|
|
|
|
SimpleFile sfConsensus = new SimpleFile(tc.getDataFolder() + SimpleFile.getSeparator() + "cached-consensus");
|
|
|
|
hmNode.clear();
|
|
alCountries.clear();
|
|
sfConsensus.openBufferedRead();
|
|
while (!boolAbortActions) {
|
|
temp = sfConsensus.readLine();
|
|
// Test for end of node info file
|
|
if (temp.startsWith("directory-footer")) {
|
|
break;
|
|
}
|
|
|
|
// Test for node info line
|
|
if (temp.startsWith("r ")) {
|
|
data = pattspace.split(temp);
|
|
strNick = data[1];
|
|
strIP = data[6];
|
|
strOrPort = data[7];
|
|
strDirPort = data[8];
|
|
|
|
// Read flags line
|
|
while (true) {
|
|
strFlags = sfConsensus.readLine();
|
|
if (strFlags.startsWith("s ")) {
|
|
break;
|
|
}
|
|
}
|
|
// Ignore certain types of node
|
|
if (!strFlags.contains("Running")
|
|
|| !strFlags.contains("Valid")) {
|
|
continue;
|
|
}
|
|
|
|
// Read port permissions line
|
|
while (true) {
|
|
strPerms = sfConsensus.readLine();
|
|
if (strPerms.startsWith("p ")) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if Tor is fully active
|
|
if (tc.getStatus() < TorController.STATUS_IDLE) {
|
|
boolAbortActions = false;
|
|
alCountries.clear();
|
|
break;
|
|
}
|
|
|
|
// Get country code from ip address
|
|
strCountryCode = tc.getCountryFromIP(strIP);
|
|
if (strCountryCode == null || strCountryCode.contains("??")) {
|
|
strCountryCode = "U1";
|
|
}
|
|
|
|
// If we get GEOIP errors then abort
|
|
if (strCountryCode.startsWith("551 GEOIP")) {
|
|
alCountries.clear();
|
|
break;
|
|
}
|
|
|
|
// Get country name
|
|
strCountryName = LOCAL.getDisplayCountry(strCountryCode);
|
|
|
|
ni = new NodeItem();
|
|
// Update our node item fields now that we have validated our node
|
|
ni.setCountryCode(strCountryCode);
|
|
ni.setCountryName(strCountryName);
|
|
ni.setNickName(strNick);
|
|
ni.setIPAddress(strIP);
|
|
// Get stableflag
|
|
if (strFlags.contains("Stable")) {
|
|
ni.setStable(LOCAL.getString("text_yes"));
|
|
} else {
|
|
ni.setStable(LOCAL.getString("text_no"));
|
|
}
|
|
|
|
hmNode.put(strOrPort + strDirPort + ":" + strIP, ni);
|
|
|
|
// Check if a guard node
|
|
if (strFlags.contains("Guard")) {
|
|
ni.setType(NodeItem.TYPE_GUARD);
|
|
}
|
|
|
|
// Check if an exit node also exclude nodes flagged as bad exits
|
|
if (strFlags.contains("Exit") && !strFlags.contains("BadExit")) {
|
|
ni.setType(NodeItem.TYPE_EXIT);
|
|
// Simple test on port permissions, we will only allow node
|
|
// if it accepts connections on port 80 and 443 for web browsing
|
|
if (strPerms.startsWith("p accept")) {
|
|
strPerms = strPerms.replace("p accept ", "");
|
|
if (!containsPort(strPerms, 80)) {
|
|
continue;
|
|
}
|
|
if (!containsPort(strPerms, 443)) {
|
|
continue;
|
|
}
|
|
} else {
|
|
strPerms = strPerms.replace("p reject ", "");
|
|
if (containsPort(strPerms, 80)) {
|
|
continue;
|
|
}
|
|
if (containsPort(strPerms, 443)) {
|
|
continue;
|
|
}
|
|
}
|
|
ni.setHttpSupported(true);
|
|
|
|
// Ensure we only add exit country info in once to validated countries list
|
|
if (!alCountries.contains(strCountryCode + "," + strCountryName)) {
|
|
alCountries.add(strCountryCode + "," + strCountryName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sfConsensus.closeFile();
|
|
|
|
// Sort our country validation list
|
|
if (alCountries.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
Collections.sort(alCountries, Collator.getInstance());
|
|
}
|
|
return !boolAbortActions;
|
|
}
|
|
|
|
/**
|
|
* Update internal finger mappings
|
|
*
|
|
*/
|
|
private boolean fetchFingerData() {
|
|
int idx;
|
|
String line;
|
|
String data[];
|
|
String ipaddress;
|
|
String finger;
|
|
String strOrPort;
|
|
String strDirPort;
|
|
String key;
|
|
boolean result;
|
|
|
|
String guardwhitelist = getGuardFavouritesAsCSV();
|
|
NodeItem ni;
|
|
HashMap<String, NodeItem> hmNodeReplacement = new HashMap<>();
|
|
|
|
SimpleFile sfDescriptors = new SimpleFile(tc.getDataFolder() + SimpleFile.getSeparator() + "cached-descriptors");
|
|
SimpleFile sfDescriptorsNew = new SimpleFile(tc.getDataFolder() + SimpleFile.getSeparator() + "cached-descriptors.new");
|
|
if (!sfDescriptorsNew.exists() || sfDescriptorsNew.getFile().length() == 0) {
|
|
sfDescriptorsNew = null;
|
|
}
|
|
|
|
sfDescriptors.openBufferedRead();
|
|
try {
|
|
while (!boolAbortActions) {
|
|
line = sfDescriptors.readLine();
|
|
// If line is null we have then processed the main descriptors file
|
|
if (line == null) {
|
|
// Check if their is a descriptors new file
|
|
if (sfDescriptorsNew == null) {
|
|
break;
|
|
} else {
|
|
sfDescriptors.closeFile();
|
|
sfDescriptors = sfDescriptorsNew;
|
|
sfDescriptors.openBufferedRead();
|
|
sfDescriptorsNew = null;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (line.startsWith("router ")) {
|
|
data = pattspace.split(line);
|
|
ipaddress = data[2];
|
|
strOrPort = data[3];
|
|
strDirPort = data[5];
|
|
key = strOrPort + strDirPort + ":";
|
|
if (!hmNode.containsKey(key + ipaddress)) {
|
|
continue;
|
|
}
|
|
|
|
// Get fingerprint
|
|
while (true) {
|
|
line = sfDescriptors.readLine();
|
|
idx = line.indexOf("fingerprint");
|
|
if (idx > -1) {
|
|
line = line.substring(idx + 12);
|
|
break;
|
|
}
|
|
}
|
|
finger = "$" + line.replace(" ", EMPTYSTRING);
|
|
|
|
// Get bandwidth
|
|
while (true) {
|
|
line = sfDescriptors.readLine();
|
|
idx = line.indexOf("bandwidth");
|
|
if (idx > -1) {
|
|
line = line.substring(idx + 10);
|
|
break;
|
|
}
|
|
}
|
|
data = pattspace.split(line);
|
|
ni = hmNode.get(key + ipaddress);
|
|
ni.setFingerprint(finger);
|
|
ni.setGuardEnabled(guardwhitelist.contains(finger));
|
|
ni.setBandwidth(getLowestBandwidth(data));
|
|
hmNodeReplacement.put(finger, ni);
|
|
}
|
|
}
|
|
result = !boolAbortActions;
|
|
} catch (Exception ex) {
|
|
result = false;
|
|
}
|
|
sfDescriptors.closeFile();
|
|
// Replace our original ipaddress keyed nodemap with our fingerprint keyed nodemap
|
|
hmNode = hmNodeReplacement;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Return the lowest bandwidth
|
|
*
|
|
* @param data
|
|
* @return bandwidth as float
|
|
*/
|
|
private float getLowestBandwidth(String[] data) {
|
|
float bw = 1000000;
|
|
float tmp;
|
|
for (String s : data) {
|
|
tmp = Float.parseFloat(s) / 1000000;
|
|
if (tmp < bw) {
|
|
bw = tmp;
|
|
}
|
|
}
|
|
return bw;
|
|
}
|
|
|
|
/**
|
|
* Add newly learned bridge to nodelist
|
|
*
|
|
* @param bridgedata
|
|
*/
|
|
public void addBridge(String bridgedata) {
|
|
Pattern pat = Pattern.compile(" ");
|
|
bridgedata = bridgedata.substring(bridgedata.indexOf('$'));
|
|
String[] data = pat.split(bridgedata);
|
|
int index = data[0].indexOf('~');
|
|
if (index < 0) {
|
|
index = data[0].indexOf('=');
|
|
}
|
|
NodeItem ni = new NodeItem();
|
|
ni.setFingerprint(data[0].substring(0, index));
|
|
ni.setIPAddress(data[2]);
|
|
ni.setNickName(data[0].substring(index + 1));
|
|
hmBridges.put(ni.getFingerprint(), ni);
|
|
}
|
|
|
|
/**
|
|
* Clear any learned bridges
|
|
*/
|
|
public void clearBridges() {
|
|
hmBridges.clear();
|
|
}
|
|
|
|
/**
|
|
* Test to check supplied data contains the specified port
|
|
*
|
|
* @param data
|
|
* @param port
|
|
* @return True if data contains specified port
|
|
*/
|
|
private boolean containsPort(String data, int port) {
|
|
String strRanges[];
|
|
String[] strPorts;
|
|
int rangemin;
|
|
int rangemax;
|
|
Pattern pattcomma = Pattern.compile(",");
|
|
Pattern pattminus = Pattern.compile("-");
|
|
strRanges = pattcomma.split(data);
|
|
for (String s : strRanges) {
|
|
// Test to see if its a range of ports
|
|
if (s.contains("-")) {
|
|
strPorts = pattminus.split(s);
|
|
rangemin = Integer.parseInt(strPorts[0]);
|
|
rangemax = Integer.parseInt(strPorts[1]);
|
|
if (port >= rangemin && port <= rangemax) {
|
|
return true;
|
|
}
|
|
} else {
|
|
// We have a single port
|
|
rangemin = Integer.parseInt(s);
|
|
if (port == rangemin) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets validated countries, a valid country is any country with active exit
|
|
* nodes
|
|
*
|
|
* @return String array of countries
|
|
*/
|
|
public final String[] getValidatedCountries() {
|
|
return alCountries.toArray(new String[alCountries.size()]);
|
|
}
|
|
|
|
/**
|
|
* Gets validated country codes, a valid country is any country with active
|
|
* exit nodes
|
|
*
|
|
* @return String array of country abbreviations
|
|
*/
|
|
public final String[] getValidatedCountryCodes() {
|
|
String[] result = new String[alCountries.size()];
|
|
int idx = 0;
|
|
for (String s : alCountries) {
|
|
result[idx++] = s.substring(0, 2);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Set the guard node view table model
|
|
*
|
|
* @param gntm
|
|
*/
|
|
public final void setGuardNodeTableModel(GuardNodeTableModel gntm) {
|
|
this.gntm = gntm;
|
|
}
|
|
|
|
/**
|
|
* Set the exit node view table model
|
|
*
|
|
* @param entm
|
|
*/
|
|
public final void setExitNodeTableModel(ExitNodeTableModel entm) {
|
|
this.entm = entm;
|
|
}
|
|
|
|
/**
|
|
* Get a list of exitnodes, if all is false then it returns only favourited
|
|
* nodes, if omitfailednodes is true then don't include nodes that failed
|
|
* testing
|
|
*
|
|
* @param all
|
|
* @param omitfailednodes
|
|
* @return ArrayList of exit nodes
|
|
*/
|
|
public final ArrayList<String> getExitNodes(boolean all, boolean omitfailednodes) {
|
|
ArrayList<String> al = new ArrayList<>();
|
|
for (int i = 0; i < entm.getRowCount(); i++) {
|
|
int teststatus = (Integer) entm.getValueAt(i, 7);
|
|
if (omitfailednodes && teststatus == NodeItem.TESTSTATUS_FAILED) {
|
|
continue;
|
|
}
|
|
boolean fave = (Boolean) entm.getValueAt(i, 4) | all;
|
|
if (fave) {
|
|
al.add((String) entm.getValueAt(i, 5));
|
|
}
|
|
}
|
|
return al;
|
|
}
|
|
|
|
/**
|
|
* Get a string of comma separated exitnodes, if all is false then it
|
|
* returns only favourited nodes, if omitfailednodes is true then don't
|
|
* include nodes that failed testing
|
|
*
|
|
* @param all
|
|
* @param omitfailednodes
|
|
* @return String of exitnodes in csv format
|
|
*/
|
|
public final String getExitNodesAsString(boolean all, boolean omitfailednodes) {
|
|
String result = getExitNodes(all, omitfailednodes).toString();
|
|
result = result.replace("[", "").replace(" ", "").replace("]", "");
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all guard nodes guard nodes
|
|
*
|
|
* @return ArrayList of guard nodes
|
|
*/
|
|
public final ArrayList<String> getGuardNodes() {
|
|
ArrayList<String> al = new ArrayList<>();
|
|
if (gntm == null) {
|
|
return al;
|
|
}
|
|
for (int i = 0; i < gntm.getRowCount(); i++) {
|
|
al.add((String) gntm.getValueAt(i, 3));
|
|
}
|
|
return al;
|
|
}
|
|
|
|
/**
|
|
* Get guard favourites
|
|
*
|
|
* @return CSV string of guard entry fingerprints
|
|
*/
|
|
public final String getGuardFavouritesAsCSV() {
|
|
StringBuilder sbResult = new StringBuilder();
|
|
String line;
|
|
String sep = "";
|
|
if (guardFavouritesFile == null) {
|
|
return sbResult.toString();
|
|
}
|
|
SimpleFile sf = new SimpleFile(filepath + guardFavouritesFile);
|
|
if (!sf.exists()) {
|
|
return sbResult.toString();
|
|
}
|
|
sf.openBufferedRead();
|
|
while ((line = sf.readLine()) != null) {
|
|
sbResult.append(sep);
|
|
sbResult.append(line);
|
|
if (sep.isEmpty()) {
|
|
sep = ",";
|
|
}
|
|
}
|
|
sf.closeFile();
|
|
return sbResult.toString();
|
|
}
|
|
|
|
/**
|
|
* Update the guard node table model
|
|
*/
|
|
public final void refreshGuardTableModel() {
|
|
if (gntm == null) {
|
|
return;
|
|
}
|
|
NodeItem ni;
|
|
gntm.clear();
|
|
// Populate model
|
|
for (String s : hmNode.keySet()) {
|
|
ni = hmNode.get(s);
|
|
if (ni.isGuard()) {
|
|
Object[] rowData = new Object[]{ni};
|
|
gntm.addRow(rowData);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the table model based on supplied country
|
|
*
|
|
*
|
|
* @param isocountry in the format "GB,Great Britain"
|
|
*/
|
|
public final void refreshExitTableModel(String isocountry) {
|
|
if (entm == null) {
|
|
return;
|
|
}
|
|
NodeItem ni;
|
|
entm.clear();
|
|
String abbrv = isocountry;
|
|
String favourites = "";
|
|
if (exitFavouritesFile != null) {
|
|
SimpleFile sf = new SimpleFile(filepath + exitFavouritesFile);
|
|
if (sf.exists()) {
|
|
sf.openBufferedRead();
|
|
favourites = sf.readEntireFile().trim();
|
|
sf.closeFile();
|
|
}
|
|
}
|
|
|
|
// Populate model
|
|
nooffavs = 0;
|
|
for (String s : hmNode.keySet()) {
|
|
ni = hmNode.get(s);
|
|
if (ni.isExit() && ni.isHttpSupported() && ni.getCountryCode().contentEquals(abbrv)) {
|
|
ni.setLatency(9999);
|
|
ni.setTestingMessage(LOCAL.getString("textfield_unknown"));
|
|
// Whitelisting here
|
|
ni.setFavouriteEnabled(favourites.contains(ni.getIPAddress()));
|
|
if (ni.isFavourite()) {
|
|
nooffavs++;
|
|
}
|
|
Object[] rowData = new Object[]{ni};
|
|
entm.addRow(rowData);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the number of active favourites
|
|
*
|
|
* @return number of favs as int
|
|
*/
|
|
public int getNumberOfFavs() {
|
|
return nooffavs;
|
|
}
|
|
|
|
/**
|
|
* Ensures any threaded actions will terminate themselves
|
|
*/
|
|
public final void terminate() {
|
|
boolAbortActions = true;
|
|
}
|
|
|
|
/**
|
|
* Save exit node whitelist
|
|
*/
|
|
public void saveExitFavourites() {
|
|
if (exitFavouritesFile == null) {
|
|
return;
|
|
}
|
|
SimpleFile sf = new SimpleFile(filepath + exitFavouritesFile);
|
|
sf.openBufferedWrite();
|
|
NodeItem ni;
|
|
nooffavs = 0;
|
|
for (String s : hmNode.keySet()) {
|
|
ni = hmNode.get(s);
|
|
if (ni.isExit() && ni.isFavourite()) {
|
|
nooffavs++;
|
|
sf.writeFile(ni.getIPAddress(), 1);
|
|
}
|
|
}
|
|
sf.closeFile();
|
|
}
|
|
|
|
/**
|
|
* Save exit node blacklist
|
|
*
|
|
* @return number of active guards
|
|
*/
|
|
public int saveGuardWhitelist() {
|
|
int activeguards = 0;
|
|
if (guardFavouritesFile == null) {
|
|
return activeguards;
|
|
}
|
|
SimpleFile sf = new SimpleFile(filepath + guardFavouritesFile);
|
|
sf.openBufferedWrite();
|
|
NodeItem ni;
|
|
for (String s : hmNode.keySet()) {
|
|
ni = hmNode.get(s);
|
|
if (ni.isGuard() && ni.isGuardEnabled()) {
|
|
sf.writeFile(ni.getFingerprint(), 1);
|
|
activeguards++;
|
|
}
|
|
}
|
|
sf.closeFile();
|
|
return activeguards;
|
|
}
|
|
|
|
}
|