Pier Angelo Vendrame pushed to branch tor-browser-115.5.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
5ad20208
by Pier Angelo Vendrame at 2023-12-06T18:31:01+01:00
-
8c95910c
by Pier Angelo Vendrame at 2023-12-06T18:31:02+01:00
-
fb609914
by Pier Angelo Vendrame at 2023-12-07T19:35:29+01:00
-
1b7630ec
by Pier Angelo Vendrame at 2023-12-07T19:35:34+01:00
-
d0ae1f7e
by Pier Angelo Vendrame at 2023-12-07T19:35:35+01:00
-
51d44491
by Pier Angelo Vendrame at 2023-12-07T19:35:35+01:00
-
be3afbb8
by Pier Angelo Vendrame at 2023-12-07T19:35:55+01:00
-
766abfe6
by Pier Angelo Vendrame at 2023-12-07T19:35:58+01:00
13 changed files:
- mobile/android/components/geckoview/GeckoViewStartup.jsm
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
- + mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
- mobile/android/modules/geckoview/GeckoViewContent.sys.mjs
- + toolkit/components/tor-launcher/TorProcessAndroid.sys.mjs
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/components/tor-launcher/moz.build
- toolkit/modules/Moat.sys.mjs
- + toolkit/modules/TorAndroidIntegration.sys.mjs
- toolkit/modules/TorConnect.sys.mjs
- toolkit/modules/moz.build
Changes:
| ... | ... | @@ -5,6 +5,10 @@ |
| 5 | 5 | |
| 6 | 6 | var EXPORTED_SYMBOLS = ["GeckoViewStartup"];
|
| 7 | 7 | |
| 8 | +const { AppConstants } = ChromeUtils.importESModule(
|
|
| 9 | + "resource://gre/modules/AppConstants.sys.mjs"
|
|
| 10 | +);
|
|
| 11 | + |
|
| 8 | 12 | const { GeckoViewUtils } = ChromeUtils.importESModule(
|
| 9 | 13 | "resource://gre/modules/GeckoViewUtils.sys.mjs"
|
| 10 | 14 | );
|
| ... | ... | @@ -17,6 +21,7 @@ ChromeUtils.defineESModuleGetters(lazy, { |
| 17 | 21 | PdfJs: "resource://pdf.js/PdfJs.sys.mjs",
|
| 18 | 22 | Preferences: "resource://gre/modules/Preferences.sys.mjs",
|
| 19 | 23 | RFPHelper: "resource://gre/modules/RFPHelper.sys.mjs",
|
| 24 | + TorAndroidIntegration: "resource://gre/modules/TorAndroidIntegration.sys.mjs",
|
|
| 20 | 25 | TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
|
| 21 | 26 | });
|
| 22 | 27 | |
| ... | ... | @@ -259,6 +264,7 @@ class GeckoViewStartup { |
| 259 | 264 | "GeckoView:SetLocale",
|
| 260 | 265 | ]);
|
| 261 | 266 | |
| 267 | + lazy.TorAndroidIntegration.init();
|
|
| 262 | 268 | lazy.TorDomainIsolator.init();
|
| 263 | 269 | |
| 264 | 270 | Services.obs.addObserver(this, "browser-idle-startup-tasks-finished");
|
| ... | ... | @@ -167,7 +167,7 @@ public final class GeckoRuntime implements Parcelable { |
| 167 | 167 | if (!BuildConfig.TOR_BROWSER) {
|
| 168 | 168 | GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext());
|
| 169 | 169 | } else {
|
| 170 | - Log.d(LOGTAG, "Tor Browser: skip GeckoNetworkManager startup");
|
|
| 170 | + Log.d(LOGTAG, "Tor Browser: skip GeckoNetworkManager startup");
|
|
| 171 | 171 | }
|
| 172 | 172 | |
| 173 | 173 | // Set settings that may have changed between last app opening
|
| ... | ... | @@ -230,6 +230,8 @@ public final class GeckoRuntime implements Parcelable { |
| 230 | 230 | private final ProfilerController mProfilerController;
|
| 231 | 231 | private final GeckoScreenChangeListener mScreenChangeListener;
|
| 232 | 232 | |
| 233 | + private TorIntegrationAndroid mTorIntegration;
|
|
| 234 | + |
|
| 233 | 235 | private GeckoRuntime() {
|
| 234 | 236 | mWebExtensionController = new WebExtensionController(this);
|
| 235 | 237 | mContentBlockingController = new ContentBlockingController();
|
| ... | ... | @@ -484,6 +486,8 @@ public final class GeckoRuntime implements Parcelable { |
| 484 | 486 | mScreenChangeListener.enable();
|
| 485 | 487 | }
|
| 486 | 488 | |
| 489 | + mTorIntegration = new TorIntegrationAndroid(context);
|
|
| 490 | + |
|
| 487 | 491 | mProfilerController.addMarker(
|
| 488 | 492 | "GeckoView Initialization START", mProfilerController.getProfilerTime());
|
| 489 | 493 | return true;
|
| ... | ... | @@ -600,6 +604,10 @@ public final class GeckoRuntime implements Parcelable { |
| 600 | 604 | mScreenChangeListener.disable();
|
| 601 | 605 | }
|
| 602 | 606 | |
| 607 | + if (mTorIntegration != null) {
|
|
| 608 | + mTorIntegration.shutdown();
|
|
| 609 | + }
|
|
| 610 | + |
|
| 603 | 611 | GeckoThread.forceQuit();
|
| 604 | 612 | }
|
| 605 | 613 |
| ... | ... | @@ -487,6 +487,11 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { |
| 487 | 487 | getSettings().mPrioritizeOnions.set(flag);
|
| 488 | 488 | return this;
|
| 489 | 489 | }
|
| 490 | + |
|
| 491 | + public @NonNull Builder useNewBootstrap(final boolean flag) {
|
|
| 492 | + getSettings().mUseNewBootstrap.set(flag);
|
|
| 493 | + return this;
|
|
| 494 | + }
|
|
| 490 | 495 | }
|
| 491 | 496 | |
| 492 | 497 | private GeckoRuntime mRuntime;
|
| ... | ... | @@ -540,6 +545,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { |
| 540 | 545 | new Pref<>("browser.security_level.security_slider", 4);
|
| 541 | 546 | /* package */ final Pref<Boolean> mPrioritizeOnions =
|
| 542 | 547 | new Pref<>("privacy.prioritizeonions.enabled", false);
|
| 548 | + /* package */ final Pref<Boolean> mUseNewBootstrap =
|
|
| 549 | + new Pref<>("browser.tor_android.use_new_bootstrap", false);
|
|
| 543 | 550 | |
| 544 | 551 | /* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
|
| 545 | 552 | |
| ... | ... | @@ -1352,6 +1359,15 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { |
| 1352 | 1359 | return this;
|
| 1353 | 1360 | }
|
| 1354 | 1361 | |
| 1362 | + public boolean getUseNewBootstrap() {
|
|
| 1363 | + return mUseNewBootstrap.get();
|
|
| 1364 | + }
|
|
| 1365 | + |
|
| 1366 | + public @NonNull GeckoRuntimeSettings setUseNewBootstrap(final boolean flag) {
|
|
| 1367 | + mUseNewBootstrap.commit(flag);
|
|
| 1368 | + return this;
|
|
| 1369 | + }
|
|
| 1370 | + |
|
| 1355 | 1371 | @Override // Parcelable
|
| 1356 | 1372 | public void writeToParcel(final Parcel out, final int flags) {
|
| 1357 | 1373 | super.writeToParcel(out, flags);
|
| ... | ... | @@ -2493,6 +2493,16 @@ public class GeckoSession { |
| 2493 | 2493 | return mEventDispatcher.queryBoolean("GeckoView:IsPdfJs");
|
| 2494 | 2494 | }
|
| 2495 | 2495 | |
| 2496 | + /**
|
|
| 2497 | + * Try to get last circuit used in this session, if possible.
|
|
| 2498 | + *
|
|
| 2499 | + * @return The circuit information as a {@link GeckoResult} object.
|
|
| 2500 | + */
|
|
| 2501 | + @AnyThread
|
|
| 2502 | + public @NonNull GeckoResult<GeckoBundle> getTorCircuit() {
|
|
| 2503 | + return mEventDispatcher.queryBundle("GeckoView:GetTorCircuit");
|
|
| 2504 | + }
|
|
| 2505 | + |
|
| 2496 | 2506 | /**
|
| 2497 | 2507 | * Set this GeckoSession as active or inactive, which represents if the session is currently
|
| 2498 | 2508 | * visible or not. Setting a GeckoSession to inactive will significantly reduce its memory
|
| 1 | +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
| 2 | + * vim: ts=4 sw=4 expandtab:
|
|
| 3 | + * This Source Code Form is subject to the terms of the Mozilla Public
|
|
| 4 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
| 5 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
| 6 | + |
|
| 7 | +package org.mozilla.geckoview;
|
|
| 8 | + |
|
| 9 | +import android.content.Context;
|
|
| 10 | +import android.util.Log;
|
|
| 11 | + |
|
| 12 | +import java.io.BufferedReader;
|
|
| 13 | +import java.io.File;
|
|
| 14 | +import java.io.IOException;
|
|
| 15 | +import java.io.InputStream;
|
|
| 16 | +import java.io.InputStreamReader;
|
|
| 17 | +import java.nio.file.Files;
|
|
| 18 | +import java.nio.file.Path;
|
|
| 19 | +import java.nio.file.Paths;
|
|
| 20 | +import java.nio.file.StandardCopyOption;
|
|
| 21 | +import java.nio.file.attribute.PosixFilePermission;
|
|
| 22 | +import java.nio.file.attribute.PosixFilePermissions;
|
|
| 23 | +import java.util.ArrayList;
|
|
| 24 | +import java.util.HashMap;
|
|
| 25 | +import java.util.Map;
|
|
| 26 | +import java.util.Set;
|
|
| 27 | +import java.util.UUID;
|
|
| 28 | + |
|
| 29 | +import org.mozilla.gecko.EventDispatcher;
|
|
| 30 | +import org.mozilla.gecko.GeckoAppShell;
|
|
| 31 | +import org.mozilla.gecko.util.BundleEventListener;
|
|
| 32 | +import org.mozilla.gecko.util.EventCallback;
|
|
| 33 | +import org.mozilla.gecko.util.GeckoBundle;
|
|
| 34 | + |
|
| 35 | +/* package */ class TorIntegrationAndroid implements BundleEventListener {
|
|
| 36 | + private static final String TAG = "TorIntegrationAndroid";
|
|
| 37 | + |
|
| 38 | + private static final String TOR_EVENT_START = "GeckoView:Tor:StartTor";
|
|
| 39 | + private static final String TOR_EVENT_STOP = "GeckoView:Tor:StopTor";
|
|
| 40 | + private static final String MEEK_EVENT_START = "GeckoView:Tor:StartMeek";
|
|
| 41 | + private static final String MEEK_EVENT_STOP = "GeckoView:Tor:StopMeek";
|
|
| 42 | + |
|
| 43 | + private static final String CONTROL_PORT_FILE = "/control-ipc";
|
|
| 44 | + private static final String SOCKS_FILE = "/socks-ipc";
|
|
| 45 | + private static final String COOKIE_AUTH_FILE = "/auth-file";
|
|
| 46 | + |
|
| 47 | + private final String mLibraryDir;
|
|
| 48 | + private final Path mCacheDir;
|
|
| 49 | + private final String mIpcDirectory;
|
|
| 50 | + private final String mDataDir;
|
|
| 51 | + |
|
| 52 | + private TorProcess mTorProcess = null;
|
|
| 53 | + /**
|
|
| 54 | + * The first time we run a Tor process in this session, we copy some configuration files to be
|
|
| 55 | + * sure we always have the latest version, but if we re-launch a tor process we do not need to
|
|
| 56 | + * copy them again.
|
|
| 57 | + */
|
|
| 58 | + private boolean mCopiedConfigFiles = false;
|
|
| 59 | + /**
|
|
| 60 | + * Allow multiple proxies to be started, even though it might not actually happen.
|
|
| 61 | + * The key should be positive (also 0 is not allowed).
|
|
| 62 | + */
|
|
| 63 | + private final HashMap<Integer, MeekTransport> mMeeks = new HashMap<>();
|
|
| 64 | + private int mMeekCounter;
|
|
| 65 | + |
|
| 66 | + public TorIntegrationAndroid(Context context) {
|
|
| 67 | + mLibraryDir = context.getApplicationInfo().nativeLibraryDir;
|
|
| 68 | + mCacheDir = context.getCacheDir().toPath();
|
|
| 69 | + mIpcDirectory = mCacheDir + "/tor-private";
|
|
| 70 | + mDataDir = context.getDataDir().getAbsolutePath() + "/tor";
|
|
| 71 | + registerListener();
|
|
| 72 | + }
|
|
| 73 | + |
|
| 74 | + public synchronized void shutdown() {
|
|
| 75 | + // FIXME: It seems this never gets called
|
|
| 76 | + if (mTorProcess != null) {
|
|
| 77 | + mTorProcess.shutdown();
|
|
| 78 | + mTorProcess = null;
|
|
| 79 | + }
|
|
| 80 | + }
|
|
| 81 | + |
|
| 82 | + private void registerListener() {
|
|
| 83 | + EventDispatcher.getInstance()
|
|
| 84 | + .registerUiThreadListener(
|
|
| 85 | + this,
|
|
| 86 | + TOR_EVENT_START,
|
|
| 87 | + MEEK_EVENT_START,
|
|
| 88 | + MEEK_EVENT_STOP);
|
|
| 89 | + }
|
|
| 90 | + |
|
| 91 | + @Override // BundleEventListener
|
|
| 92 | + public synchronized void handleMessage(
|
|
| 93 | + final String event, final GeckoBundle message, final EventCallback callback) {
|
|
| 94 | + if (TOR_EVENT_START.equals(event)) {
|
|
| 95 | + startDaemon(message, callback);
|
|
| 96 | + } else if (TOR_EVENT_STOP.equals(event)) {
|
|
| 97 | + stopDaemon(message, callback);
|
|
| 98 | + } else if (MEEK_EVENT_START.equals(event)) {
|
|
| 99 | + startMeek(message, callback);
|
|
| 100 | + } else if (MEEK_EVENT_STOP.equals(event)) {
|
|
| 101 | + stopMeek(message, callback);
|
|
| 102 | + }
|
|
| 103 | + }
|
|
| 104 | + |
|
| 105 | + private synchronized void startDaemon(final GeckoBundle message, final EventCallback callback) {
|
|
| 106 | + // Let JS generate this to possibly reduce the chance of race conditions.
|
|
| 107 | + String handle = message.getString("handle", "");
|
|
| 108 | + if (handle.isEmpty()) {
|
|
| 109 | + Log.e(TAG, "Requested to start a tor process without a handle.");
|
|
| 110 | + callback.sendError("Expected a handle for the new process.");
|
|
| 111 | + return;
|
|
| 112 | + }
|
|
| 113 | + Log.d(TAG, "Starting the a tor process with handle " + handle);
|
|
| 114 | + |
|
| 115 | + TorProcess previousProcess = mTorProcess;
|
|
| 116 | + if (previousProcess != null) {
|
|
| 117 | + Log.w(TAG, "We still have a running process: " + previousProcess.getHandle());
|
|
| 118 | + }
|
|
| 119 | + mTorProcess = new TorProcess(handle);
|
|
| 120 | + |
|
| 121 | + GeckoBundle bundle = new GeckoBundle(3);
|
|
| 122 | + bundle.putString("controlPortPath", mIpcDirectory + CONTROL_PORT_FILE);
|
|
| 123 | + bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE);
|
|
| 124 | + bundle.putString("cookieFilePath", mIpcDirectory + COOKIE_AUTH_FILE);
|
|
| 125 | + callback.sendSuccess(bundle);
|
|
| 126 | + }
|
|
| 127 | + |
|
| 128 | + private synchronized void stopDaemon(final GeckoBundle message, final EventCallback callback) {
|
|
| 129 | + if (mTorProcess == null) {
|
|
| 130 | + if (callback != null) {
|
|
| 131 | + callback.sendSuccess(null);
|
|
| 132 | + }
|
|
| 133 | + return;
|
|
| 134 | + }
|
|
| 135 | + String handle = message.getString("handle", "");
|
|
| 136 | + if (!mTorProcess.getHandle().equals(handle)) {
|
|
| 137 | + GeckoBundle bundle = new GeckoBundle(1);
|
|
| 138 | + bundle.putString("error", "The requested process has not been found. It might have already been stopped.");
|
|
| 139 | + callback.sendError(bundle);
|
|
| 140 | + return;
|
|
| 141 | + }
|
|
| 142 | + mTorProcess.shutdown();
|
|
| 143 | + mTorProcess = null;
|
|
| 144 | + callback.sendSuccess(null);
|
|
| 145 | + }
|
|
| 146 | + |
|
| 147 | + class TorProcess extends Thread {
|
|
| 148 | + private static final String TOR_EVENT_STARTED = "GeckoView:Tor:TorStarted";
|
|
| 149 | + private static final String TOR_EVENT_START_FAILED = "GeckoView:Tor:TorStartFailed";
|
|
| 150 | + private static final String TOR_EVENT_EXITED = "GeckoView:Tor:TorExited";
|
|
| 151 | + private final String mHandle;
|
|
| 152 | + private Process mProcess = null;
|
|
| 153 | + |
|
| 154 | + TorProcess(String handle) {
|
|
| 155 | + mHandle = handle;
|
|
| 156 | + setName("tor-process-" + handle);
|
|
| 157 | + start();
|
|
| 158 | + }
|
|
| 159 | + |
|
| 160 | + @Override
|
|
| 161 | + public void run() {
|
|
| 162 | + cleanIpcDirectory();
|
|
| 163 | + |
|
| 164 | + final String ipcDir = TorIntegrationAndroid.this.mIpcDirectory;
|
|
| 165 | + final ArrayList<String> args = new ArrayList<>();
|
|
| 166 | + args.add(mLibraryDir + "/libTor.so");
|
|
| 167 | + args.add("DisableNetwork");
|
|
| 168 | + args.add("1");
|
|
| 169 | + args.add("+__ControlPort");
|
|
| 170 | + args.add("unix:" + ipcDir + CONTROL_PORT_FILE);
|
|
| 171 | + args.add("+__SocksPort");
|
|
| 172 | + args.add("unix:" + ipcDir + SOCKS_FILE + " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth");
|
|
| 173 | + args.add("CookieAuthentication");
|
|
| 174 | + args.add("1");
|
|
| 175 | + args.add("CookieAuthFile");
|
|
| 176 | + args.add(ipcDir + COOKIE_AUTH_FILE);
|
|
| 177 | + args.add("DataDirectory");
|
|
| 178 | + args.add(mDataDir);
|
|
| 179 | + boolean copied = true;
|
|
| 180 | + try {
|
|
| 181 | + copyAndUseConfigFile("--defaults-torrc", "torrc-defaults", args);
|
|
| 182 | + } catch (IOException e) {
|
|
| 183 | + Log.w(TAG, "torrc-default cannot be created, pluggable transports will not be available", e);
|
|
| 184 | + copied = false;
|
|
| 185 | + }
|
|
| 186 | + try {
|
|
| 187 | + copyAndUseConfigFile("GeoIPFile", "geoip", args);
|
|
| 188 | + copyAndUseConfigFile("GeoIPv6File", "geoip6", args);
|
|
| 189 | + } catch (IOException e) {
|
|
| 190 | + Log.w(TAG, "GeoIP files cannot be created, this feature will not be available.", e);
|
|
| 191 | + copied = false;
|
|
| 192 | + }
|
|
| 193 | + mCopiedConfigFiles = copied;
|
|
| 194 | + |
|
| 195 | + Log.d(TAG, "Starting tor with the follwing args: " + args.toString());
|
|
| 196 | + final ProcessBuilder builder = new ProcessBuilder(args);
|
|
| 197 | + builder.directory(new File(mLibraryDir));
|
|
| 198 | + try {
|
|
| 199 | + mProcess = builder.start();
|
|
| 200 | + } catch (IOException e) {
|
|
| 201 | + Log.e(TAG, "Cannot start tor " + mHandle, e);
|
|
| 202 | + final GeckoBundle data = new GeckoBundle(2);
|
|
| 203 | + data.putString("handle", mHandle);
|
|
| 204 | + data.putString("error", e.getMessage());
|
|
| 205 | + EventDispatcher.getInstance().dispatch(TOR_EVENT_START_FAILED, data);
|
|
| 206 | + return;
|
|
| 207 | + }
|
|
| 208 | + Log.i(TAG, "Tor process " + mHandle + " started.");
|
|
| 209 | + {
|
|
| 210 | + final GeckoBundle data = new GeckoBundle(1);
|
|
| 211 | + data.putString("handle", mHandle);
|
|
| 212 | + EventDispatcher.getInstance().dispatch(TOR_EVENT_STARTED, data);
|
|
| 213 | + }
|
|
| 214 | + try {
|
|
| 215 | + BufferedReader reader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
|
|
| 216 | + String line;
|
|
| 217 | + while ((line = reader.readLine()) != null) {
|
|
| 218 | + Log.i(TAG, "[tor-" + mHandle + "] " + line);
|
|
| 219 | + }
|
|
| 220 | + } catch (IOException e) {
|
|
| 221 | + Log.e(TAG, "Failed to read stdout of the tor process " + mHandle, e);
|
|
| 222 | + }
|
|
| 223 | + Log.d(TAG, "Exiting the stdout loop for process " + mHandle);
|
|
| 224 | + final GeckoBundle data = new GeckoBundle(2);
|
|
| 225 | + data.putString("handle", mHandle);
|
|
| 226 | + try {
|
|
| 227 | + data.putInt("status", mProcess.waitFor());
|
|
| 228 | + } catch (InterruptedException e) {
|
|
| 229 | + Log.e(TAG, "Failed to wait for the tor process " + mHandle, e);
|
|
| 230 | + data.putInt("status", 0xdeadbeef);
|
|
| 231 | + }
|
|
| 232 | + // FIXME: We usually don't reach this when the application is killed!
|
|
| 233 | + // So, we don't do our cleanup.
|
|
| 234 | + Log.i(TAG, "Tor process " + mHandle + " has exited.");
|
|
| 235 | + EventDispatcher.getInstance().dispatch(TOR_EVENT_EXITED, data);
|
|
| 236 | + }
|
|
| 237 | + |
|
| 238 | + private void cleanIpcDirectory() {
|
|
| 239 | + File directory = new File(TorIntegrationAndroid.this.mIpcDirectory);
|
|
| 240 | + if (!Files.isDirectory(directory.toPath())) {
|
|
| 241 | + if (!directory.mkdirs()) {
|
|
| 242 | + Log.e(TAG, "Failed to create the IPC directory.");
|
|
| 243 | + return;
|
|
| 244 | + }
|
|
| 245 | + try {
|
|
| 246 | + Set<PosixFilePermission> chmod = PosixFilePermissions.fromString("rwx------");
|
|
| 247 | + Files.setPosixFilePermissions(directory.toPath(), chmod);
|
|
| 248 | + } catch (IOException e) {
|
|
| 249 | + Log.e(TAG, "Could not set the permissions to the IPC directory.", e);
|
|
| 250 | + }
|
|
| 251 | + return;
|
|
| 252 | + }
|
|
| 253 | + // We assume we do not have child directories, only files
|
|
| 254 | + File[] maybeFiles = directory.listFiles();
|
|
| 255 | + if (maybeFiles != null) {
|
|
| 256 | + for (File file : maybeFiles) {
|
|
| 257 | + if (!file.delete()) {
|
|
| 258 | + Log.d(TAG, "Could not delete " + file);
|
|
| 259 | + }
|
|
| 260 | + }
|
|
| 261 | + }
|
|
| 262 | + }
|
|
| 263 | + |
|
| 264 | + private void copyAndUseConfigFile(String option, String name, ArrayList<String> args) throws IOException {
|
|
| 265 | + final Path path = Paths.get(mCacheDir.toFile().getAbsolutePath(), name);
|
|
| 266 | + if (!mCopiedConfigFiles || !path.toFile().exists()) {
|
|
| 267 | + final Context context = GeckoAppShell.getApplicationContext();
|
|
| 268 | + final InputStream in = context.getAssets().open("common/" + name);
|
|
| 269 | + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
|
|
| 270 | + in.close();
|
|
| 271 | + }
|
|
| 272 | + args.add(option);
|
|
| 273 | + args.add(path.toString());
|
|
| 274 | + }
|
|
| 275 | + |
|
| 276 | + public void shutdown() {
|
|
| 277 | + if (mProcess != null && mProcess.isAlive()) {
|
|
| 278 | + mProcess.destroy();
|
|
| 279 | + }
|
|
| 280 | + if (isAlive()) {
|
|
| 281 | + try {
|
|
| 282 | + join();
|
|
| 283 | + } catch (InterruptedException e) {
|
|
| 284 | + Log.e(TAG, "Cannot join the thread for tor process " + mHandle + ", possibly already terminated", e);
|
|
| 285 | + }
|
|
| 286 | + }
|
|
| 287 | + }
|
|
| 288 | + |
|
| 289 | + public String getHandle() {
|
|
| 290 | + return mHandle;
|
|
| 291 | + }
|
|
| 292 | + }
|
|
| 293 | + |
|
| 294 | + private synchronized void startMeek(final GeckoBundle message, final EventCallback callback) {
|
|
| 295 | + if (callback == null) {
|
|
| 296 | + Log.e(TAG, "Tried to start Meek without a callback.");
|
|
| 297 | + return;
|
|
| 298 | + }
|
|
| 299 | + mMeekCounter++;
|
|
| 300 | + mMeeks.put(new Integer(mMeekCounter), new MeekTransport(callback, mMeekCounter));
|
|
| 301 | + }
|
|
| 302 | + |
|
| 303 | + private synchronized void stopMeek(final GeckoBundle message, final EventCallback callback) {
|
|
| 304 | + final Integer key = message.getInteger("id");
|
|
| 305 | + final MeekTransport meek = mMeeks.remove(key);
|
|
| 306 | + if (meek != null) {
|
|
| 307 | + meek.shutdown();
|
|
| 308 | + }
|
|
| 309 | + if (callback != null) {
|
|
| 310 | + callback.sendSuccess(null);
|
|
| 311 | + }
|
|
| 312 | + }
|
|
| 313 | + |
|
| 314 | + private class MeekTransport extends Thread {
|
|
| 315 | + private static final String TRANSPORT = "meek_lite";
|
|
| 316 | + private Process mProcess;
|
|
| 317 | + private final EventCallback mCallback;
|
|
| 318 | + private final int mId;
|
|
| 319 | + |
|
| 320 | + MeekTransport(final EventCallback callback, int id) {
|
|
| 321 | + setName("meek-" + id);
|
|
| 322 | + final ProcessBuilder builder = new ProcessBuilder(mLibraryDir + "/libObfs4proxy.so");
|
|
| 323 | + {
|
|
| 324 | + final Map<String, String> env = builder.environment();
|
|
| 325 | + env.put("TOR_PT_MANAGED_TRANSPORT_VER", "1");
|
|
| 326 | + env.put("TOR_PT_STATE_LOCATION", mDataDir + "/pt_state");
|
|
| 327 | + env.put("TOR_PT_EXIT_ON_STDIN_CLOSE", "1");
|
|
| 328 | + env.put("TOR_PT_CLIENT_TRANSPORTS", TRANSPORT);
|
|
| 329 | + }
|
|
| 330 | + mCallback = callback;
|
|
| 331 | + mId = id;
|
|
| 332 | + try {
|
|
| 333 | + // We expect this process to be short-lived, therefore we do not bother with
|
|
| 334 | + // implementing this as a service.
|
|
| 335 | + mProcess = builder.start();
|
|
| 336 | + } catch (IOException e) {
|
|
| 337 | + Log.e(TAG, "Cannot start the PT", e);
|
|
| 338 | + callback.sendError(e.getMessage());
|
|
| 339 | + return;
|
|
| 340 | + }
|
|
| 341 | + start();
|
|
| 342 | + }
|
|
| 343 | + |
|
| 344 | + /**
|
|
| 345 | + * Parse the standard output of the pluggable transport to find the hostname and port it is
|
|
| 346 | + * listening on.
|
|
| 347 | + * <p>
|
|
| 348 | + * See also the specs for the IPC protocol at https://spec.torproject.org/pt-spec/ipc.html.
|
|
| 349 | + */
|
|
| 350 | + @Override
|
|
| 351 | + public void run() {
|
|
| 352 | + final String PROTOCOL_VERSION = "1";
|
|
| 353 | + String hostname = "";
|
|
| 354 | + boolean valid = false;
|
|
| 355 | + int port = 0;
|
|
| 356 | + String error = "Did not see a CMETHOD";
|
|
| 357 | + try {
|
|
| 358 | + InputStreamReader isr = new InputStreamReader(mProcess.getInputStream());
|
|
| 359 | + BufferedReader reader = new BufferedReader(isr);
|
|
| 360 | + String line;
|
|
| 361 | + while ((line = reader.readLine()) != null) {
|
|
| 362 | + line = line.trim();
|
|
| 363 | + Log.d(TAG, "Meek line: " + line);
|
|
| 364 | + // Split produces always at least one item
|
|
| 365 | + String[] tokens = line.split(" ");
|
|
| 366 | + if ("VERSION".equals(tokens[0]) && (tokens.length != 2 || !PROTOCOL_VERSION.equals(tokens[1]))) {
|
|
| 367 | + error = "Bad version: " + line;
|
|
| 368 | + break;
|
|
| 369 | + }
|
|
| 370 | + if ("CMETHOD".equals(tokens[0])) {
|
|
| 371 | + if (tokens.length != 4) {
|
|
| 372 | + error = "Bad number of tokens in CMETHOD: " + line;
|
|
| 373 | + break;
|
|
| 374 | + }
|
|
| 375 | + if (!tokens[1].equals(TRANSPORT)) {
|
|
| 376 | + error = "Unexpected transport: " + tokens[1];
|
|
| 377 | + break;
|
|
| 378 | + }
|
|
| 379 | + if (!"socks5".equals(tokens[2])) {
|
|
| 380 | + error = "Unexpected proxy type: " + tokens[2];
|
|
| 381 | + break;
|
|
| 382 | + }
|
|
| 383 | + String[] addr = tokens[3].split(":");
|
|
| 384 | + if (addr.length != 2) {
|
|
| 385 | + error = "Invalid address";
|
|
| 386 | + break;
|
|
| 387 | + }
|
|
| 388 | + hostname = addr[0];
|
|
| 389 | + try {
|
|
| 390 | + port = Integer.parseInt(addr[1]);
|
|
| 391 | + } catch (NumberFormatException e) {
|
|
| 392 | + error = "Invalid port: " + e.getMessage();
|
|
| 393 | + break;
|
|
| 394 | + }
|
|
| 395 | + if (port < 1 || port > 65535) {
|
|
| 396 | + error = "Invalid port: out of bounds";
|
|
| 397 | + break;
|
|
| 398 | + }
|
|
| 399 | + valid = true;
|
|
| 400 | + break;
|
|
| 401 | + }
|
|
| 402 | + if (tokens[0].endsWith("-ERROR")) {
|
|
| 403 | + error = "Seen an error: " + line;
|
|
| 404 | + break;
|
|
| 405 | + }
|
|
| 406 | + }
|
|
| 407 | + } catch (Exception e) {
|
|
| 408 | + error = e.getMessage();
|
|
| 409 | + }
|
|
| 410 | + if (valid) {
|
|
| 411 | + Log.d(TAG, "Setup a meek transport " + mId + ": " + hostname + ":" + port);
|
|
| 412 | + final GeckoBundle bundle = new GeckoBundle(3);
|
|
| 413 | + bundle.putInt("id", mId);
|
|
| 414 | + bundle.putString("address", hostname);
|
|
| 415 | + bundle.putInt("port", port);
|
|
| 416 | + mCallback.sendSuccess(bundle);
|
|
| 417 | + } else {
|
|
| 418 | + Log.e(TAG, "Failed to get a usable config from the PT: " + error);
|
|
| 419 | + mCallback.sendError(error);
|
|
| 420 | + }
|
|
| 421 | + }
|
|
| 422 | + |
|
| 423 | + void shutdown() {
|
|
| 424 | + if (mProcess != null) {
|
|
| 425 | + mProcess.destroy();
|
|
| 426 | + mProcess = null;
|
|
| 427 | + }
|
|
| 428 | + try {
|
|
| 429 | + join();
|
|
| 430 | + } catch (InterruptedException e) {
|
|
| 431 | + Log.e(TAG, "Could not join the meek thread", e);
|
|
| 432 | + }
|
|
| 433 | + }
|
|
| 434 | + }
|
|
| 435 | +} |
| ... | ... | @@ -4,6 +4,12 @@ |
| 4 | 4 | |
| 5 | 5 | import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
|
| 6 | 6 | |
| 7 | +const lazy = {};
|
|
| 8 | + |
|
| 9 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
| 10 | + TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
|
|
| 11 | +});
|
|
| 12 | + |
|
| 7 | 13 | export class GeckoViewContent extends GeckoViewModule {
|
| 8 | 14 | onInit() {
|
| 9 | 15 | this.registerListener([
|
| ... | ... | @@ -22,6 +28,7 @@ export class GeckoViewContent extends GeckoViewModule { |
| 22 | 28 | "GeckoView:UpdateInitData",
|
| 23 | 29 | "GeckoView:ZoomToInput",
|
| 24 | 30 | "GeckoView:IsPdfJs",
|
| 31 | + "GeckoView:GetTorCircuit",
|
|
| 25 | 32 | ]);
|
| 26 | 33 | }
|
| 27 | 34 | |
| ... | ... | @@ -190,6 +197,21 @@ export class GeckoViewContent extends GeckoViewModule { |
| 190 | 197 | case "GeckoView:HasCookieBannerRuleForBrowsingContextTree":
|
| 191 | 198 | this._hasCookieBannerRuleForBrowsingContextTree(aCallback);
|
| 192 | 199 | break;
|
| 200 | + case "GeckoView:GetTorCircuit":
|
|
| 201 | + if (this.browser && aCallback) {
|
|
| 202 | + const domain = lazy.TorDomainIsolator.getDomainForBrowser(
|
|
| 203 | + this.browser
|
|
| 204 | + );
|
|
| 205 | + const nodes = lazy.TorDomainIsolator.getCircuit(
|
|
| 206 | + this.browser,
|
|
| 207 | + domain,
|
|
| 208 | + this.browser.contentPrincipal.originAttributes.userContextId
|
|
| 209 | + );
|
|
| 210 | + aCallback?.onSuccess({ domain, nodes });
|
|
| 211 | + } else {
|
|
| 212 | + aCallback?.onSuccess(null);
|
|
| 213 | + }
|
|
| 214 | + break;
|
|
| 193 | 215 | }
|
| 194 | 216 | }
|
| 195 | 217 |
| 1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
| 2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
| 3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
| 4 | + |
|
| 5 | +import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
|
|
| 6 | + |
|
| 7 | +const lazy = {};
|
|
| 8 | + |
|
| 9 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
| 10 | + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
| 11 | +});
|
|
| 12 | + |
|
| 13 | +const logger = new ConsoleAPI({
|
|
| 14 | + maxLogLevel: "info",
|
|
| 15 | + prefix: "TorProcessAndroid",
|
|
| 16 | +});
|
|
| 17 | + |
|
| 18 | +const TorOutgoingEvents = Object.freeze({
|
|
| 19 | + start: "GeckoView:Tor:StartTor",
|
|
| 20 | + stop: "GeckoView:Tor:StopTor",
|
|
| 21 | +});
|
|
| 22 | + |
|
| 23 | +// The events we will listen to
|
|
| 24 | +const TorIncomingEvents = Object.freeze({
|
|
| 25 | + started: "GeckoView:Tor:TorStarted",
|
|
| 26 | + startFailed: "GeckoView:Tor:TorStartFailed",
|
|
| 27 | + exited: "GeckoView:Tor:TorExited",
|
|
| 28 | +});
|
|
| 29 | + |
|
| 30 | +export class TorProcessAndroid {
|
|
| 31 | + /**
|
|
| 32 | + * The handle the Java counterpart uses to refer to the process we started.
|
|
| 33 | + * We use it to filter the exit events and make sure they refer to the daemon
|
|
| 34 | + * we are interested in.
|
|
| 35 | + */
|
|
| 36 | + #processHandle = null;
|
|
| 37 | + /**
|
|
| 38 | + * The promise resolver we call when the Java counterpart sends the event that
|
|
| 39 | + * tor has started.
|
|
| 40 | + */
|
|
| 41 | + #startResolve = null;
|
|
| 42 | + /**
|
|
| 43 | + * The promise resolver we call when the Java counterpart sends the event that
|
|
| 44 | + * it failed to start tor.
|
|
| 45 | + */
|
|
| 46 | + #startReject = null;
|
|
| 47 | + |
|
| 48 | + onExit = () => {};
|
|
| 49 | + |
|
| 50 | + get isRunning() {
|
|
| 51 | + return !!this.#processHandle;
|
|
| 52 | + }
|
|
| 53 | + |
|
| 54 | + async start() {
|
|
| 55 | + // Generate the handle on the JS side so that it's ready in case it takes
|
|
| 56 | + // less to start the process than to propagate the success.
|
|
| 57 | + this.#processHandle = crypto.randomUUID();
|
|
| 58 | + logger.info(`Starting new process with handle ${this.#processHandle}`);
|
|
| 59 | + // Let's declare it immediately, so that the Java side can do its stuff in
|
|
| 60 | + // an async manner and we avoid possible race conditions (at most we await
|
|
| 61 | + // an already resolved/rejected promise.
|
|
| 62 | + const startEventPromise = new Promise((resolve, reject) => {
|
|
| 63 | + this.#startResolve = resolve;
|
|
| 64 | + this.#startReject = reject;
|
|
| 65 | + });
|
|
| 66 | + lazy.EventDispatcher.instance.registerListener(
|
|
| 67 | + this,
|
|
| 68 | + Object.values(TorIncomingEvents)
|
|
| 69 | + );
|
|
| 70 | + let config;
|
|
| 71 | + try {
|
|
| 72 | + config = await lazy.EventDispatcher.instance.sendRequestForResult({
|
|
| 73 | + type: TorOutgoingEvents.start,
|
|
| 74 | + handle: this.#processHandle,
|
|
| 75 | + });
|
|
| 76 | + logger.debug("Sent the start event.");
|
|
| 77 | + } catch (e) {
|
|
| 78 | + this.forget();
|
|
| 79 | + throw e;
|
|
| 80 | + }
|
|
| 81 | + await startEventPromise;
|
|
| 82 | + return config;
|
|
| 83 | + }
|
|
| 84 | + |
|
| 85 | + forget() {
|
|
| 86 | + // Processes usually exit when we close the control port connection to them.
|
|
| 87 | + logger.trace(`Forgetting process ${this.#processHandle}`);
|
|
| 88 | + lazy.EventDispatcher.instance.sendRequestForResult({
|
|
| 89 | + type: TorOutgoingEvents.stop,
|
|
| 90 | + handle: this.#processHandle,
|
|
| 91 | + });
|
|
| 92 | + logger.debug("Sent the start event.");
|
|
| 93 | + this.#processHandle = null;
|
|
| 94 | + lazy.EventDispatcher.instance.unregisterListener(
|
|
| 95 | + this,
|
|
| 96 | + Object.values(TorIncomingEvents)
|
|
| 97 | + );
|
|
| 98 | + }
|
|
| 99 | + |
|
| 100 | + onEvent(event, data, callback) {
|
|
| 101 | + if (data?.handle !== this.#processHandle) {
|
|
| 102 | + logger.debug(`Ignoring event ${event} with another handle`, data);
|
|
| 103 | + return;
|
|
| 104 | + }
|
|
| 105 | + logger.info(`Received an event ${event}`, data);
|
|
| 106 | + switch (event) {
|
|
| 107 | + case TorIncomingEvents.started:
|
|
| 108 | + this.#startResolve();
|
|
| 109 | + break;
|
|
| 110 | + case TorIncomingEvents.startFailed:
|
|
| 111 | + this.#startReject(new Error(data.error));
|
|
| 112 | + break;
|
|
| 113 | + case TorIncomingEvents.exited:
|
|
| 114 | + this.forget();
|
|
| 115 | + if (this.#startReject !== null) {
|
|
| 116 | + this.#startReject();
|
|
| 117 | + }
|
|
| 118 | + this.onExit(data.status);
|
|
| 119 | + break;
|
|
| 120 | + }
|
|
| 121 | + }
|
|
| 122 | +} |
| ... | ... | @@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, { |
| 14 | 14 | FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
|
| 15 | 15 | TorController: "resource://gre/modules/TorControlPort.sys.mjs",
|
| 16 | 16 | TorProcess: "resource://gre/modules/TorProcess.sys.mjs",
|
| 17 | + TorProcessAndroid: "resource://gre/modules/TorProcessAndroid.sys.mjs",
|
|
| 17 | 18 | });
|
| 18 | 19 | |
| 19 | 20 | const logger = new ConsoleAPI({
|
| ... | ... | @@ -182,8 +183,12 @@ export class TorProvider { |
| 182 | 183 | logger.debug("Initializing the Tor provider.");
|
| 183 | 184 | |
| 184 | 185 | // These settings might be customized in the following steps.
|
| 185 | - this.#socksSettings = TorLauncherUtil.getPreferredSocksConfiguration();
|
|
| 186 | - logger.debug("Requested SOCKS configuration", this.#socksSettings);
|
|
| 186 | + if (TorLauncherUtil.isAndroid) {
|
|
| 187 | + this.#socksSettings = { transproxy: false };
|
|
| 188 | + } else {
|
|
| 189 | + this.#socksSettings = TorLauncherUtil.getPreferredSocksConfiguration();
|
|
| 190 | + logger.debug("Requested SOCKS configuration", this.#socksSettings);
|
|
| 191 | + }
|
|
| 187 | 192 | |
| 188 | 193 | try {
|
| 189 | 194 | await this.#setControlPortConfiguration();
|
| ... | ... | @@ -490,10 +495,14 @@ export class TorProvider { |
| 490 | 495 | return;
|
| 491 | 496 | }
|
| 492 | 497 | |
| 493 | - this.#torProcess = new lazy.TorProcess(
|
|
| 494 | - this.#controlPortSettings,
|
|
| 495 | - this.#socksSettings
|
|
| 496 | - );
|
|
| 498 | + if (TorLauncherUtil.isAndroid) {
|
|
| 499 | + this.#torProcess = new lazy.TorProcessAndroid();
|
|
| 500 | + } else {
|
|
| 501 | + this.#torProcess = new lazy.TorProcess(
|
|
| 502 | + this.#controlPortSettings,
|
|
| 503 | + this.#socksSettings
|
|
| 504 | + );
|
|
| 505 | + }
|
|
| 497 | 506 | // Use a closure instead of bind because we reassign #cancelConnection.
|
| 498 | 507 | // Also, we now assign an exit handler that cancels the first connection,
|
| 499 | 508 | // so that a sudden exit before the first connection is completed might
|
| ... | ... | @@ -507,7 +516,17 @@ export class TorProvider { |
| 507 | 516 | };
|
| 508 | 517 | |
| 509 | 518 | logger.debug("Trying to start the tor process.");
|
| 510 | - await this.#torProcess.start();
|
|
| 519 | + const res = await this.#torProcess.start();
|
|
| 520 | + if (TorLauncherUtil.isAndroid) {
|
|
| 521 | + this.#controlPortSettings = {
|
|
| 522 | + ipcFile: new lazy.FileUtils.File(res.controlPortPath),
|
|
| 523 | + cookieFilePath: res.cookieFilePath,
|
|
| 524 | + };
|
|
| 525 | + this.#socksSettings = {
|
|
| 526 | + transproxy: false,
|
|
| 527 | + ipcFile: new lazy.FileUtils.File(res.socksPath),
|
|
| 528 | + };
|
|
| 529 | + }
|
|
| 511 | 530 | logger.info("Started a tor process");
|
| 512 | 531 | }
|
| 513 | 532 | |
| ... | ... | @@ -521,6 +540,11 @@ export class TorProvider { |
| 521 | 540 | logger.debug("Reading the control port configuration");
|
| 522 | 541 | const settings = {};
|
| 523 | 542 | |
| 543 | + if (TorLauncherUtil.isAndroid) {
|
|
| 544 | + // We will populate the settings after having started the daemon.
|
|
| 545 | + return;
|
|
| 546 | + }
|
|
| 547 | + |
|
| 524 | 548 | const isWindows = Services.appinfo.OS === "WINNT";
|
| 525 | 549 | // Determine how Tor Launcher will connect to the Tor control port.
|
| 526 | 550 | // Environment variables get top priority followed by preferences.
|
| ... | ... | @@ -5,6 +5,7 @@ EXTRA_JS_MODULES += [ |
| 5 | 5 | "TorLauncherUtil.sys.mjs",
|
| 6 | 6 | "TorParsers.sys.mjs",
|
| 7 | 7 | "TorProcess.sys.mjs",
|
| 8 | + "TorProcessAndroid.sys.mjs",
|
|
| 8 | 9 | "TorProvider.sys.mjs",
|
| 9 | 10 | "TorProviderBuilder.sys.mjs",
|
| 10 | 11 | "TorStartupService.sys.mjs",
|
| ... | ... | @@ -10,6 +10,7 @@ import { |
| 10 | 10 | const lazy = {};
|
| 11 | 11 | |
| 12 | 12 | ChromeUtils.defineESModuleGetters(lazy, {
|
| 13 | + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
| 13 | 14 | Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
|
| 14 | 15 | TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
| 15 | 16 | TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
| ... | ... | @@ -290,6 +291,48 @@ class MeekTransport { |
| 290 | 291 | }
|
| 291 | 292 | }
|
| 292 | 293 | |
| 294 | +class MeekTransportAndroid {
|
|
| 295 | + // These members are used by consumers to setup the proxy to do requests over
|
|
| 296 | + // meek. They are passed to newProxyInfoWithAuth.
|
|
| 297 | + proxyType = null;
|
|
| 298 | + proxyAddress = null;
|
|
| 299 | + proxyPort = 0;
|
|
| 300 | + proxyUsername = null;
|
|
| 301 | + proxyPassword = null;
|
|
| 302 | + |
|
| 303 | + #id = 0;
|
|
| 304 | + |
|
| 305 | + async init() {
|
|
| 306 | + // ensure we haven't already init'd
|
|
| 307 | + if (this.#id) {
|
|
| 308 | + throw new Error("MeekTransport: Already initialized");
|
|
| 309 | + }
|
|
| 310 | + const details = await lazy.EventDispatcher.instance.sendRequestForResult({
|
|
| 311 | + type: "GeckoView:Tor:StartMeek",
|
|
| 312 | + });
|
|
| 313 | + this.#id = details.id;
|
|
| 314 | + this.proxyType = "socks";
|
|
| 315 | + this.proxyAddress = details.address;
|
|
| 316 | + this.proxyPort = details.port;
|
|
| 317 | + [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
|
|
| 318 | + this.proxyType
|
|
| 319 | + );
|
|
| 320 | + }
|
|
| 321 | + |
|
| 322 | + async uninit() {
|
|
| 323 | + lazy.EventDispatcher.instance.sendRequest({
|
|
| 324 | + type: "GeckoView:Tor:StopMeek",
|
|
| 325 | + id: this.#id,
|
|
| 326 | + });
|
|
| 327 | + this.#id = 0;
|
|
| 328 | + this.proxyType = null;
|
|
| 329 | + this.proxyAddress = null;
|
|
| 330 | + this.proxyPort = 0;
|
|
| 331 | + this.proxyUsername = null;
|
|
| 332 | + this.proxyPassword = null;
|
|
| 333 | + }
|
|
| 334 | +}
|
|
| 335 | + |
|
| 293 | 336 | //
|
| 294 | 337 | // Callback object with a cached promise for the returned Moat data
|
| 295 | 338 | //
|
| ... | ... | @@ -407,7 +450,10 @@ export class MoatRPC { |
| 407 | 450 | throw new Error("MoatRPC: Already initialized");
|
| 408 | 451 | }
|
| 409 | 452 | |
| 410 | - const meekTransport = new MeekTransport();
|
|
| 453 | + const meekTransport =
|
|
| 454 | + Services.appinfo.OS === "Android"
|
|
| 455 | + ? new MeekTransportAndroid()
|
|
| 456 | + : new MeekTransport();
|
|
| 411 | 457 | await meekTransport.init();
|
| 412 | 458 | this.#meekTransport = meekTransport;
|
| 413 | 459 | this.#inited = true;
|
| 1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
| 2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
| 3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
| 4 | + |
|
| 5 | +import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
|
|
| 6 | + |
|
| 7 | +const lazy = {};
|
|
| 8 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
| 9 | + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
| 10 | + TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
|
|
| 11 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
| 12 | + TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
|
|
| 13 | +});
|
|
| 14 | + |
|
| 15 | +const Prefs = Object.freeze({
|
|
| 16 | + useNewBootstrap: "browser.tor_android.use_new_bootstrap",
|
|
| 17 | + logLevel: "browser.tor_android.log_level",
|
|
| 18 | +});
|
|
| 19 | + |
|
| 20 | +const logger = new ConsoleAPI({
|
|
| 21 | + maxLogLevel: "info",
|
|
| 22 | + maxLogLevelPref: Prefs.logLevel,
|
|
| 23 | + prefix: "TorAndroidIntegration",
|
|
| 24 | +});
|
|
| 25 | + |
|
| 26 | +const ListenedEvents = Object.freeze({
|
|
| 27 | + settingsGet: "GeckoView:Tor:SettingsGet",
|
|
| 28 | + settingsSet: "GeckoView:Tor:SettingsSet",
|
|
| 29 | + settingsApply: "GeckoView:Tor:SettingsApply",
|
|
| 30 | + settingsSave: "GeckoView:Tor:SettingsSave",
|
|
| 31 | +});
|
|
| 32 | + |
|
| 33 | +class TorAndroidIntegrationImpl {
|
|
| 34 | + #initialized = false;
|
|
| 35 | + |
|
| 36 | + init() {
|
|
| 37 | + lazy.EventDispatcher.instance.registerListener(
|
|
| 38 | + this,
|
|
| 39 | + Object.values(ListenedEvents)
|
|
| 40 | + );
|
|
| 41 | + |
|
| 42 | + this.#bootstrapMethodReset();
|
|
| 43 | + Services.prefs.addObserver(Prefs.useNewBootstrap, this);
|
|
| 44 | + }
|
|
| 45 | + |
|
| 46 | + async #initNewBootstrap() {
|
|
| 47 | + if (this.#initialized) {
|
|
| 48 | + return;
|
|
| 49 | + }
|
|
| 50 | + this.#initialized = true;
|
|
| 51 | + |
|
| 52 | + lazy.TorProviderBuilder.init().finally(() => {
|
|
| 53 | + lazy.TorProviderBuilder.firstWindowLoaded();
|
|
| 54 | + });
|
|
| 55 | + try {
|
|
| 56 | + await lazy.TorSettings.init();
|
|
| 57 | + await lazy.TorConnect.init();
|
|
| 58 | + } catch (e) {
|
|
| 59 | + logger.error("Cannot initialize TorSettings or TorConnect", e);
|
|
| 60 | + }
|
|
| 61 | + }
|
|
| 62 | + |
|
| 63 | + observe(subj, topic, data) {
|
|
| 64 | + switch (topic) {
|
|
| 65 | + case "nsPref:changed":
|
|
| 66 | + if (data === Prefs.useNewBootstrap) {
|
|
| 67 | + this.#bootstrapMethodReset();
|
|
| 68 | + }
|
|
| 69 | + break;
|
|
| 70 | + }
|
|
| 71 | + }
|
|
| 72 | + |
|
| 73 | + async onEvent(event, data, callback) {
|
|
| 74 | + logger.debug(`Received event ${event}`, data);
|
|
| 75 | + try {
|
|
| 76 | + switch (event) {
|
|
| 77 | + case settingsGet:
|
|
| 78 | + callback?.onSuccess(lazy.TorSettings.getSettings());
|
|
| 79 | + return;
|
|
| 80 | + case settingsSet:
|
|
| 81 | + // This does not throw, so we do not have any way to report the error!
|
|
| 82 | + lazy.TorSettings.setSettings(data);
|
|
| 83 | + break;
|
|
| 84 | + case settingsApply:
|
|
| 85 | + await lazy.TorSettings.applySettings();
|
|
| 86 | + break;
|
|
| 87 | + case settingsSave:
|
|
| 88 | + await lazy.TorSettings.saveSettings();
|
|
| 89 | + break;
|
|
| 90 | + }
|
|
| 91 | + callback?.onSuccess();
|
|
| 92 | + } catch (e) {
|
|
| 93 | + logger.error();
|
|
| 94 | + callback?.sendError(e);
|
|
| 95 | + }
|
|
| 96 | + }
|
|
| 97 | + |
|
| 98 | + #bootstrapMethodReset() {
|
|
| 99 | + if (Services.prefs.getBoolPref(Prefs.useNewBootstrap, false)) {
|
|
| 100 | + this.#initNewBootstrap();
|
|
| 101 | + } else {
|
|
| 102 | + Services.prefs.clearUserPref("network.proxy.socks");
|
|
| 103 | + Services.prefs.clearUserPref("network.proxy.socks_port");
|
|
| 104 | + }
|
|
| 105 | + }
|
|
| 106 | +}
|
|
| 107 | + |
|
| 108 | +export const TorAndroidIntegration = new TorAndroidIntegrationImpl(); |
| ... | ... | @@ -793,6 +793,9 @@ export const TorConnect = (() => { |
| 793 | 793 | |
| 794 | 794 | TorConnect._errorMessage = errorMessage;
|
| 795 | 795 | TorConnect._errorDetails = errorDetails;
|
| 796 | + console.error(
|
|
| 797 | + `[TorConnect] Entering error state (${errorMessage}, ${errorDetails})`
|
|
| 798 | + );
|
|
| 796 | 799 | |
| 797 | 800 | Services.obs.notifyObservers(
|
| 798 | 801 | { message: errorMessage, details: errorDetails },
|
| ... | ... | @@ -215,6 +215,7 @@ EXTRA_JS_MODULES += [ |
| 215 | 215 | "Sqlite.sys.mjs",
|
| 216 | 216 | "SubDialog.sys.mjs",
|
| 217 | 217 | "Timer.sys.mjs",
|
| 218 | + "TorAndroidIntegration.sys.mjs",
|
|
| 218 | 219 | "TorConnect.sys.mjs",
|
| 219 | 220 | "TorSettings.sys.mjs",
|
| 220 | 221 | "TorStrings.sys.mjs",
|