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",
 |