From 6a9b209bc04fc43eae8a23bf6c845e9f7310bba0 Mon Sep 17 00:00:00 2001 From: cyBerta <cyberta@riseup.net> Date: Wed, 18 May 2022 16:07:29 +0200 Subject: [PATCH] pach commit containing all tor-android customizations on top of upstream main branch --- .../jni/ClientTransportPluginInterface.java | 7 + .../java/org/torproject/jni/TorService.java | 204 +++++++++++++++--- 2 files changed, 187 insertions(+), 24 deletions(-) create mode 100644 tor-android-binary/src/main/java/org/torproject/jni/ClientTransportPluginInterface.java diff --git a/tor-android-binary/src/main/java/org/torproject/jni/ClientTransportPluginInterface.java b/tor-android-binary/src/main/java/org/torproject/jni/ClientTransportPluginInterface.java new file mode 100644 index 00000000..ae0cadcf --- /dev/null +++ b/tor-android-binary/src/main/java/org/torproject/jni/ClientTransportPluginInterface.java @@ -0,0 +1,7 @@ +package org.torproject.jni; + +public interface ClientTransportPluginInterface { + void start(); + void stop(); + String getTorrc(); +} diff --git a/tor-android-binary/src/main/java/org/torproject/jni/TorService.java b/tor-android-binary/src/main/java/org/torproject/jni/TorService.java index cf977115..5af50f6c 100644 --- a/tor-android-binary/src/main/java/org/torproject/jni/TorService.java +++ b/tor-android-binary/src/main/java/org/torproject/jni/TorService.java @@ -1,11 +1,21 @@ package org.torproject.jni; +import static net.freehaven.tor.control.TorControlCommands.EVENT_CELL_STATS; +import static net.freehaven.tor.control.TorControlCommands.EVENT_CIRCUIT_STATUS; +import static net.freehaven.tor.control.TorControlCommands.EVENT_CIRCUIT_STATUS_MINOR; +import static net.freehaven.tor.control.TorControlCommands.EVENT_ERR_MSG; +import static net.freehaven.tor.control.TorControlCommands.EVENT_NETWORK_LIVENESS; +import static net.freehaven.tor.control.TorControlCommands.EVENT_NOTICE_MSG; +import static net.freehaven.tor.control.TorControlCommands.EVENT_WARN_MSG; + import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.FileObserver; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Process; import android.util.Log; @@ -13,10 +23,12 @@ import net.freehaven.tor.control.RawEventListener; import net.freehaven.tor.control.TorControlCommands; import net.freehaven.tor.control.TorControlConnection; +import java.io.BufferedWriter; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -26,6 +38,8 @@ import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import androidx.annotation.Nullable; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -44,7 +58,7 @@ public class TorService extends Service { public static final String TAG = "TorService"; - public static final String VERSION_NAME = org.torproject.jni.BuildConfig.VERSION_NAME; + //public static final String VERSION_NAME = org.torproject.jni.BuildConfig.VERSION_NAME; /** * Request to transparently start Tor services. @@ -54,7 +68,7 @@ public class TorService extends Service { /** * Internal request to stop Tor services. */ - private static final String ACTION_STOP = "org.torproject.android.intent.action.STOP"; + public static final String ACTION_STOP = "org.torproject.android.intent.action.STOP"; /** * {@link Intent} sent by this app with {@code ON/OFF/STARTING/STOPPING} status @@ -90,6 +104,16 @@ public class TorService extends Service { */ public final static String EXTRA_SERVICE_PACKAGE_NAME = "org.torproject.android.intent.extra.SERVICE_PACKAGE_NAME"; + /** + * {@code int} that represents the bootstrapping progress in percent + */ + public static final String EXTRA_STATUS_DETAIL_BOOTSTRAP = "EXTRA_BOOTSTRAP"; + + /** + * {@code String} that contains a status log description + */ + public static final String EXTRA_STATUS_DETAIL_LOGKEY = "EXTRA_LOG"; + /** * All tor-related services and daemons are stopped */ @@ -101,6 +125,24 @@ public class TorService extends Service { public static final String STATUS_STARTING = "STARTING"; public static final String STATUS_STOPPING = "STOPPING"; + private static final Object LOCK = new Object(); + private static ClientTransportPluginInterface clientTransportPlugin; + private static final Pattern BOOTSTRAP_PATTERN = Pattern.compile("(Bootstrapped )(\\d+)(% \\()(\\w*)(\\): )([\\S|\\s]*)"); + private static final int GROUP_PERCENT = 2; + private static final int GROUP_REASON_KEYWORD = 4; + + public static void setClientTransportPlugin(ClientTransportPluginInterface client) { + synchronized (LOCK) { + TorService.clientTransportPlugin = client; + } + } + + public static boolean hasClientTransportPlugin() { + synchronized (LOCK) { + return clientTransportPlugin != null; + } + } + /** * @return a {@link File} pointing to the location of the optional * {@code torrc} file. @@ -239,21 +281,62 @@ public class TorService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { sendBroadcastStatusIntent(this); + if (intent != null && ACTION_STOP.equals(intent.getAction())) { + Log.d(TAG, "stop tor service ACTION_STOP"); + stopTorService(); + return START_NOT_STICKY; + } return super.onStartCommand(intent, flags, startId); } + public void stopTorService() { + final Handler handler = new Handler(Looper.getMainLooper()); + new Thread(new Runnable() { + @Override + public void run() { + synchronized (LOCK) { + Log.d(TAG, "stopTorService - stopClientTransportPlugin"); + if (clientTransportPlugin != null) { + clientTransportPlugin.stop(); + clientTransportPlugin = null; + } + } + handler.post(new Runnable() { + @Override + public void run() { + stopSelf(); + } + }); + } + }).start(); + } + /** * Announce Tor is available for connections once the first circuit is complete */ private final RawEventListener startedEventListener = new RawEventListener() { @Override public void onEvent(String keyword, String data) { + Log.d("[Tor]", keyword + " " + data); if (TorService.STATUS_STARTING.equals(TorService.currentStatus) - && TorControlCommands.EVENT_CIRCUIT_STATUS.equals(keyword) + && EVENT_NOTICE_MSG.equals(keyword) && data != null && data.length() > 0) { - String[] tokenArray = data.split(" "); - if (tokenArray.length > 1 && TorControlCommands.CIRC_EVENT_BUILT.equals(tokenArray[1])) { - TorService.broadcastStatus(TorService.this, TorService.STATUS_ON); + Matcher m = BOOTSTRAP_PATTERN.matcher(data); + if (m.matches()) { + String reason = m.group(GROUP_REASON_KEYWORD); + + Integer percent = -1; + String percentString = m.group(GROUP_PERCENT); + try { + percent = Integer.valueOf(percentString); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + + broadcastStatus(TorService.this, currentStatus, percent, reason); + if (percent == 100) { + broadcastStatus(TorService.this, TorService.STATUS_ON); + } } } } @@ -290,23 +373,43 @@ public class TorService extends Service { throw new IOException("cannot read " + controlSocket); } - FileDescriptor controlSocketFd = prepareFileDescriptor(getControlSocket(TorService.this).getAbsolutePath()); + int attempts = 0; + FileDescriptor controlSocketFd = null; + while (controlSocketFd == null && attempts <= 5) { + try { + controlSocketFd = prepareFileDescriptor(getControlSocket(TorService.this).getAbsolutePath()); + } catch (Exception e) { + e.printStackTrace(); + if (attempts == 5) { + throw new IOException("Failed to prepare control socket file descriptor."); + } + Thread.sleep(200); + attempts++; + } + } InputStream is = new FileInputStream(controlSocketFd); OutputStream os = new FileOutputStream(controlSocketFd); torControlConnection = new TorControlConnection(is, os); torControlConnection.launchThread(true); torControlConnection.authenticate(new byte[0]); torControlConnection.addRawEventListener(startedEventListener); - torControlConnection.setEvents(Arrays.asList(TorControlCommands.EVENT_CIRCUIT_STATUS)); + torControlConnection.setEvents(Arrays.asList(EVENT_CIRCUIT_STATUS, + EVENT_CIRCUIT_STATUS_MINOR, + EVENT_NOTICE_MSG, + EVENT_WARN_MSG, + EVENT_ERR_MSG, + EVENT_NETWORK_LIVENESS, + EVENT_CELL_STATS + )); socksPort = getPortFromGetInfo("net/listeners/socks"); httpTunnelPort = getPortFromGetInfo("net/listeners/httptunnel"); - } catch (IOException | ArrayIndexOutOfBoundsException | InterruptedException e) { + } catch (IOException | ArrayIndexOutOfBoundsException | InterruptedException | NullPointerException e) { e.printStackTrace(); broadcastError(TorService.this, e); - broadcastStatus(TorService.this, STATUS_STOPPING); - stopSelf(); + Log.d(TAG, "tor finishing ControlThread"); + stopTorService(); } } }; @@ -318,6 +421,13 @@ public class TorService extends Service { public void run() { final Context context = getApplicationContext(); try { + synchronized (LOCK) { + if (clientTransportPlugin != null) { + clientTransportPlugin.start(); + initTorrc(); + } + } + String socksPort = "auto"; if (isPortAvailable(9050)) { socksPort = Integer.toString(9050); @@ -326,7 +436,7 @@ public class TorService extends Service { if (isPortAvailable(8118)) { httpTunnelPort = Integer.toString(8118); } - + runLock.lock(); createTorConfiguration(); ArrayList<String> lines = new ArrayList<>(Arrays.asList("tor", "--verify-config", // must always be here "--RunAsDaemon", "0", @@ -376,15 +486,15 @@ public class TorService extends Service { } catch (IllegalStateException | IllegalArgumentException | InterruptedException e) { e.printStackTrace(); broadcastError(context, e); + stopTorService(); } finally { - broadcastStatus(context, STATUS_STOPPING); mainConfigurationFree(); - TorService.this.stopSelf(); + runLock.unlock(); } } }; - private int getPortFromGetInfo(String key) { + private int getPortFromGetInfo(String key) throws NullPointerException { final String value = getInfo(key); return Integer.parseInt(value.substring(value.lastIndexOf(':') + 1, value.length() - 1)); } @@ -406,25 +516,24 @@ public class TorService extends Service { * @see <a href="https://github.com/torproject/tor/blob/40be20d542a83359ea480bbaa28380b4137c88b2/src/app/config/config.c#L4730">options that must be on the command line</a> */ private void startTorServiceThread() { - if (runLock.isLocked()) { - Log.i(TAG, "Waiting for lock"); - } - runLock.lock(); - Log.i(TAG, "Acquired lock"); torThread.start(); } @Override public void onDestroy() { super.onDestroy(); + Log.d(TAG, "onDestroy is called"); if (torControlConnection != null) { torControlConnection.removeRawEventListener(startedEventListener); } - if (runLock.isLocked()) { - Log.i(TAG, "Releasing lock"); - runLock.unlock(); - } + broadcastStatus(TorService.this, STATUS_STOPPING); shutdownTor(TorControlCommands.SIGNAL_SHUTDOWN); + synchronized (LOCK) { + if (clientTransportPlugin != null) { + clientTransportPlugin.stop(); + clientTransportPlugin = null; + } + } broadcastStatus(TorService.this, STATUS_OFF); } @@ -494,6 +603,17 @@ public class TorService extends Service { context.sendBroadcast(intent); } + /** + * Sends the current status both internally and for any apps that need to + * follow the status of TorService. + */ + static void broadcastStatus(Context context, String currentStatus, int percent, @Nullable String logkey) { + TorService.currentStatus = currentStatus; + Intent intent = getBroadcastIntent(ACTION_STATUS, currentStatus, percent, logkey); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + context.sendBroadcast(intent); + } + /** * This might be better handled by {@link android.content.ServiceConnection} * but there is no way to write tests for {@code ServiceConnection}. @@ -517,6 +637,15 @@ public class TorService extends Service { return intent; } + private static Intent getBroadcastIntent(String action, String currentStatus, int bootstapping, @Nullable String logkey) { + Intent intent = new Intent(action); + intent.putExtra(EXTRA_STATUS, currentStatus); + intent.putExtra(EXTRA_STATUS_DETAIL_BOOTSTRAP, bootstapping); + intent.putExtra(EXTRA_STATUS_DETAIL_LOGKEY, logkey); + return intent; + } + + private static boolean isPortAvailable(int port) { try { (new ServerSocket(port)).close(); @@ -526,4 +655,31 @@ public class TorService extends Service { return false; } } + + public void initTorrc() { + if (clientTransportPlugin == null) { + return; + } + File torrc = TorService.getTorrc(this); + BufferedWriter fileWriter = null; + + String config = clientTransportPlugin.getTorrc(); + + try { + fileWriter = new BufferedWriter(new FileWriter(torrc)); + fileWriter.write(config); + fileWriter.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fileWriter != null) { + try { + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } -- GitLab