aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/app/src/main/java/com/wireguard/android/util/RootShell.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/com/wireguard/android/util/RootShell.java')
-rw-r--r--app/src/main/java/com/wireguard/android/util/RootShell.java199
1 files changed, 0 insertions, 199 deletions
diff --git a/app/src/main/java/com/wireguard/android/util/RootShell.java b/app/src/main/java/com/wireguard/android/util/RootShell.java
deleted file mode 100644
index 1fe5667a..00000000
--- a/app/src/main/java/com/wireguard/android/util/RootShell.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import com.wireguard.android.BuildConfig;
-import com.wireguard.android.R;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.UUID;
-
-/**
- * Helper class for running commands as root.
- */
-
-public class RootShell {
- private static final String SU = "su";
- private static final String TAG = "WireGuard/" + RootShell.class.getSimpleName();
-
- private final Context context;
- private final String deviceNotRootedMessage;
- private final File localBinaryDir;
- private final File localTemporaryDir;
- private final Object lock = new Object();
- private final String preamble;
- @Nullable private Process process;
- @Nullable private BufferedReader stderr;
- @Nullable private OutputStreamWriter stdin;
- @Nullable private BufferedReader stdout;
-
- public RootShell(final Context context) {
- deviceNotRootedMessage = context.getString(R.string.error_root);
- localBinaryDir = new File(context.getCodeCacheDir(), "bin");
- localTemporaryDir = new File(context.getCacheDir(), "tmp");
- preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
- BuildConfig.APPLICATION_ID, localBinaryDir, localTemporaryDir);
- this.context = context;
- }
-
- private static boolean isExecutableInPath(final String name) {
- final String path = System.getenv("PATH");
- if (path == null)
- return false;
- for (final String dir : path.split(":"))
- if (new File(dir, name).canExecute())
- return true;
- return false;
- }
-
- private boolean isRunning() {
- synchronized (lock) {
- try {
- // Throws an exception if the process hasn't finished yet.
- if (process != null)
- process.exitValue();
- return false;
- } catch (final IllegalThreadStateException ignored) {
- // The existing process is still running.
- return true;
- }
- }
- }
-
- /**
- * Run a command in a root shell.
- *
- * @param output Lines read from stdout are appended to this list. Pass null if the
- * output from the shell is not important.
- * @param command Command to run as root.
- * @return The exit value of the command.
- */
- public int run(@Nullable final Collection<String> output, final String command)
- throws IOException, NoRootException {
- synchronized (lock) {
- /* Start inside synchronized block to prevent a concurrent call to stop(). */
- start();
- final String marker = UUID.randomUUID().toString();
- final String script = "echo " + marker + "; echo " + marker + " >&2; (" + command +
- "); ret=$?; echo " + marker + " $ret; echo " + marker + " $ret >&2\n";
- Log.v(TAG, "executing: " + command);
- stdin.write(script);
- stdin.flush();
- String line;
- int errnoStdout = Integer.MIN_VALUE;
- int errnoStderr = Integer.MAX_VALUE;
- int markersSeen = 0;
- while ((line = stdout.readLine()) != null) {
- if (line.startsWith(marker)) {
- ++markersSeen;
- if (line.length() > marker.length() + 1) {
- errnoStdout = Integer.valueOf(line.substring(marker.length() + 1));
- break;
- }
- } else if (markersSeen > 0) {
- if (output != null)
- output.add(line);
- Log.v(TAG, "stdout: " + line);
- }
- }
- while ((line = stderr.readLine()) != null) {
- if (line.startsWith(marker)) {
- ++markersSeen;
- if (line.length() > marker.length() + 1) {
- errnoStderr = Integer.valueOf(line.substring(marker.length() + 1));
- break;
- }
- } else if (markersSeen > 2) {
- Log.v(TAG, "stderr: " + line);
- }
- }
- if (markersSeen != 4)
- throw new IOException(context.getString(R.string.shell_marker_count_error, markersSeen));
- if (errnoStdout != errnoStderr)
- throw new IOException(context.getString(R.string.shell_exit_status_read_error));
- Log.v(TAG, "exit: " + errnoStdout);
- return errnoStdout;
- }
- }
-
- public void start() throws IOException, NoRootException {
- if (!isExecutableInPath(SU))
- throw new NoRootException(deviceNotRootedMessage);
- synchronized (lock) {
- if (isRunning())
- return;
- if (!localBinaryDir.isDirectory() && !localBinaryDir.mkdirs())
- throw new FileNotFoundException(context.getString(R.string.create_bin_dir_error));
- if (!localTemporaryDir.isDirectory() && !localTemporaryDir.mkdirs())
- throw new FileNotFoundException(context.getString(R.string.create_temp_dir_error));
- try {
- final ProcessBuilder builder = new ProcessBuilder().command(SU);
- builder.environment().put("LC_ALL", "C");
- try {
- process = builder.start();
- } catch (final IOException e) {
- // A failure at this stage means the device isn't rooted.
- throw new NoRootException(deviceNotRootedMessage, e);
- }
- stdin = new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8);
- stdout = new BufferedReader(new InputStreamReader(process.getInputStream(),
- StandardCharsets.UTF_8));
- stderr = new BufferedReader(new InputStreamReader(process.getErrorStream(),
- StandardCharsets.UTF_8));
- stdin.write(preamble);
- stdin.flush();
- // Check that the shell started successfully.
- final String uid = stdout.readLine();
- if (!"0".equals(uid)) {
- Log.w(TAG, "Root check did not return correct UID: " + uid);
- throw new NoRootException(deviceNotRootedMessage);
- }
- if (!isRunning()) {
- String line;
- while ((line = stderr.readLine()) != null) {
- Log.w(TAG, "Root check returned an error: " + line);
- if (line.contains("Permission denied"))
- throw new NoRootException(deviceNotRootedMessage);
- }
- throw new IOException(context.getString(R.string.shell_start_error, process.exitValue()));
- }
- } catch (final IOException | NoRootException e) {
- stop();
- throw e;
- }
- }
- }
-
- public void stop() {
- synchronized (lock) {
- if (process != null) {
- process.destroy();
- process = null;
- }
- }
- }
-
- public static class NoRootException extends Exception {
- public NoRootException(final String message, final Throwable cause) {
- super(message, cause);
- }
-
- public NoRootException(final String message) {
- super(message);
- }
- }
-}