187 lines
7.5 KiB
Java
187 lines
7.5 KiB
Java
/*
|
|
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
|
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package com.wireguard.android.util;
|
|
|
|
import android.content.Context;
|
|
import android.system.OsConstants;
|
|
import android.util.Log;
|
|
|
|
import com.wireguard.android.Application;
|
|
import com.wireguard.android.BuildConfig;
|
|
import com.wireguard.android.util.RootShell.NoRootException;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Helper to install WireGuard tools to the system partition.
|
|
*/
|
|
|
|
public final class ToolsInstaller {
|
|
public static final int ERROR = 0x0;
|
|
public static final int YES = 0x1;
|
|
public static final int NO = 0x2;
|
|
public static final int MAGISK = 0x4;
|
|
public static final int SYSTEM = 0x8;
|
|
|
|
private static final String[][] EXECUTABLES = {
|
|
{"libwg.so", "wg"},
|
|
{"libwg-quick.so", "wg-quick"},
|
|
};
|
|
private static final File[] INSTALL_DIRS = {
|
|
new File("/system/xbin"),
|
|
new File("/system/bin"),
|
|
};
|
|
private static final File INSTALL_DIR = getInstallDir();
|
|
private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName();
|
|
|
|
private final File localBinaryDir;
|
|
private final Object lock = new Object();
|
|
private final File nativeLibraryDir;
|
|
private Boolean areToolsAvailable;
|
|
private Boolean installAsMagiskModule;
|
|
|
|
public ToolsInstaller(final Context context) {
|
|
localBinaryDir = new File(context.getCacheDir(), "bin");
|
|
nativeLibraryDir = new File(context.getApplicationInfo().nativeLibraryDir);
|
|
}
|
|
|
|
private static File getInstallDir() {
|
|
final String path = System.getenv("PATH");
|
|
if (path == null)
|
|
return INSTALL_DIRS[0];
|
|
final List<String> paths = Arrays.asList(path.split(":"));
|
|
for (final File dir : INSTALL_DIRS) {
|
|
if (paths.contains(dir.getPath()) && dir.isDirectory())
|
|
return dir;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public int areInstalled() throws NoRootException {
|
|
if (INSTALL_DIR == null)
|
|
return ERROR;
|
|
final StringBuilder script = new StringBuilder();
|
|
for (final String[] names : EXECUTABLES) {
|
|
script.append(String.format("cmp -s '%s' '%s' && ",
|
|
new File(nativeLibraryDir, names[0]),
|
|
new File(INSTALL_DIR, names[1])));
|
|
}
|
|
script.append("exit ").append(OsConstants.EALREADY).append(';');
|
|
try {
|
|
final int ret = Application.getRootShell().run(null, script.toString());
|
|
if (ret == OsConstants.EALREADY)
|
|
return willInstallAsMagiskModule() ? YES | MAGISK : YES | SYSTEM;
|
|
else
|
|
return willInstallAsMagiskModule() ? NO | MAGISK : NO | SYSTEM;
|
|
} catch (final IOException ignored) {
|
|
return ERROR;
|
|
}
|
|
}
|
|
|
|
public void ensureToolsAvailable() throws FileNotFoundException, NoRootException {
|
|
synchronized (lock) {
|
|
if (areToolsAvailable == null) {
|
|
final int ret = symlink();
|
|
if (ret == OsConstants.EALREADY) {
|
|
Log.d(TAG, "Tools were already symlinked into our private binary dir");
|
|
areToolsAvailable = true;
|
|
} else if (ret == OsConstants.EXIT_SUCCESS) {
|
|
Log.d(TAG, "Tools are now symlinked into our private binary dir");
|
|
areToolsAvailable = true;
|
|
} else {
|
|
Log.e(TAG, "For some reason, wg and wg-quick are not available at all");
|
|
areToolsAvailable = false;
|
|
}
|
|
}
|
|
if (!areToolsAvailable)
|
|
throw new FileNotFoundException("Required tools unavailable");
|
|
}
|
|
}
|
|
|
|
private boolean willInstallAsMagiskModule() {
|
|
synchronized (lock) {
|
|
if (installAsMagiskModule == null) {
|
|
try {
|
|
installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.core/mirror -a -d /sbin/.core/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
|
|
} catch (final Exception ignored) {
|
|
installAsMagiskModule = false;
|
|
}
|
|
}
|
|
return installAsMagiskModule;
|
|
}
|
|
}
|
|
|
|
private int installSystem() throws NoRootException {
|
|
if (INSTALL_DIR == null)
|
|
return OsConstants.ENOENT;
|
|
final StringBuilder script = new StringBuilder("set -ex; ");
|
|
script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
|
|
for (final String[] names : EXECUTABLES) {
|
|
final File destination = new File(INSTALL_DIR, names[1]);
|
|
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
|
|
new File(nativeLibraryDir, names[0]), destination, destination, destination));
|
|
}
|
|
try {
|
|
return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR;
|
|
} catch (final IOException ignored) {
|
|
return ERROR;
|
|
}
|
|
}
|
|
|
|
private int installMagisk() throws NoRootException {
|
|
final StringBuilder script = new StringBuilder("set -ex; ");
|
|
|
|
script.append("trap 'rm -rf /sbin/.core/img/wireguard' INT TERM EXIT; ");
|
|
script.append(String.format("rm -rf /sbin/.core/img/wireguard/; mkdir -p /sbin/.core/img/wireguard%s; ", INSTALL_DIR));
|
|
script.append(String.format("printf 'name=WireGuard Command Line Tools\nversion=%s\nversionCode=%s\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /sbin/.core/img/wireguard/module.prop; ", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
|
script.append("touch /sbin/.core/img/wireguard/auto_mount; ");
|
|
for (final String[] names : EXECUTABLES) {
|
|
final File destination = new File("/sbin/.core/img/wireguard" + INSTALL_DIR, names[1]);
|
|
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
|
|
new File(nativeLibraryDir, names[0]), destination, destination, destination));
|
|
}
|
|
script.append("trap - INT TERM EXIT;");
|
|
|
|
try {
|
|
return Application.getRootShell().run(null, script.toString()) == 0 ? YES | MAGISK : ERROR;
|
|
} catch (final IOException ignored) {
|
|
return ERROR;
|
|
}
|
|
}
|
|
|
|
public int install() throws NoRootException {
|
|
return willInstallAsMagiskModule() ? installMagisk() : installSystem();
|
|
}
|
|
|
|
public int symlink() throws NoRootException {
|
|
final StringBuilder script = new StringBuilder("set -x; ");
|
|
for (final String[] names : EXECUTABLES) {
|
|
script.append(String.format("test '%s' -ef '%s' && ",
|
|
new File(nativeLibraryDir, names[0]),
|
|
new File(localBinaryDir, names[1])));
|
|
}
|
|
script.append("exit ").append(OsConstants.EALREADY).append("; set -e; ");
|
|
|
|
for (final String[] names : EXECUTABLES) {
|
|
script.append(String.format("ln -fns '%s' '%s'; ",
|
|
new File(nativeLibraryDir, names[0]),
|
|
new File(localBinaryDir, names[1])));
|
|
}
|
|
script.append("exit ").append(OsConstants.EXIT_SUCCESS).append(';');
|
|
|
|
try {
|
|
return Application.getRootShell().run(null, script.toString());
|
|
} catch (final IOException ignored) {
|
|
return OsConstants.EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|