NaTo1000's picture
download
raw
12.5 kB
package com.termux.shared.shell.am;
import android.Manifest;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.termux.am.Am;
import com.termux.shared.R;
import com.termux.shared.android.PackageUtils;
import com.termux.shared.android.PermissionUtils;
import com.termux.shared.errors.Error;
import com.termux.shared.logger.Logger;
import com.termux.shared.net.socket.local.ILocalSocketManager;
import com.termux.shared.net.socket.local.LocalClientSocket;
import com.termux.shared.net.socket.local.LocalServerSocket;
import com.termux.shared.net.socket.local.LocalSocketManager;
import com.termux.shared.net.socket.local.LocalSocketManagerClientBase;
import com.termux.shared.net.socket.local.LocalSocketRunConfig;
import com.termux.shared.shell.ArgumentTokenizer;
import com.termux.shared.shell.command.ExecutionCommand;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A AF_UNIX/SOCK_STREAM local server managed with {@link LocalSocketManager} whose
* {@link LocalServerSocket} receives android activity manager (am) commands from {@link LocalClientSocket}
* and runs them with termux-am-library. It would normally only allow processes belonging to the
* server app's user and root user to connect to it.
*
* The client must send the am command as a string without the initial "am" arg on its output stream
* and then wait for the result on its input stream. The result of the execution or error is sent
* back in the format `exit_code\0stdout\0stderr\0` where `\0` represents a null character.
* Check termux/termux-am-socket for implementation of a native c client.
*
* Usage:
* 1. Optionally extend {@link AmSocketServerClient}, the implementation for
* {@link ILocalSocketManager} that will receive call backs from the server including
* when client connects via {@link ILocalSocketManager#onClientAccepted(LocalSocketManager, LocalClientSocket)}.
* 2. Create a {@link AmSocketServerRunConfig} instance which extends from {@link LocalSocketRunConfig}
* with the run config of the am server. It would be better to use a filesystem socket instead
* of abstract namespace socket for security reasons.
* 3. Call {@link #start(Context, LocalSocketRunConfig)} to start the server and store the {@link LocalSocketManager}
* instance returned.
* 4. Stop server if needed with a call to {@link LocalSocketManager#stop()} on the
* {@link LocalSocketManager} instance returned by start call.
*
* https://github.com/termux/termux-am-library/blob/main/termux-am-library/src/main/java/com/termux/am/Am.java
* https://github.com/termux/termux-am-socket
* https://developer.android.com/studio/command-line/adb#am
* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
*/
public class AmSocketServer {
public static final String LOG_TAG = "AmSocketServer";
/**
* Create the {@link AmSocketServer} {@link LocalServerSocket} and start listening for new {@link LocalClientSocket}.
*
* @param context The {@link Context} for {@link LocalSocketManager}.
* @param localSocketRunConfig The {@link LocalSocketRunConfig} for {@link LocalSocketManager}.
*/
public static synchronized LocalSocketManager start(@NonNull Context context,
@NonNull LocalSocketRunConfig localSocketRunConfig) {
LocalSocketManager localSocketManager = new LocalSocketManager(context, localSocketRunConfig);
Error error = localSocketManager.start();
if (error != null) {
localSocketManager.onError(error);
return null;
}
return localSocketManager;
}
public static void processAmClient(@NonNull LocalSocketManager localSocketManager,
@NonNull LocalClientSocket clientSocket) {
Error error;
// Read amCommandString client sent and close input stream
StringBuilder data = new StringBuilder();
error = clientSocket.readDataOnInputStream(data, true);
if (error != null) {
sendResultToClient(localSocketManager, clientSocket, 1, null, error.toString());
return;
}
String amCommandString = data.toString();
Logger.logVerbose(LOG_TAG, "am command received from peer " + clientSocket.getPeerCred().getMinimalString() +
"\nam command: `" + amCommandString + "`");
// Parse am command string and convert it to a list of arguments
List<String> amCommandList = new ArrayList<>();
error = parseAmCommand(amCommandString, amCommandList);
if (error != null) {
sendResultToClient(localSocketManager, clientSocket, 1, null, error.toString());
return;
}
String[] amCommandArray = amCommandList.toArray(new String[0]);
Logger.logDebug(LOG_TAG, "am command received from peer " + clientSocket.getPeerCred().getMinimalString() +
"\n" + ExecutionCommand.getArgumentsLogString("am command", amCommandArray));
AmSocketServerRunConfig amSocketServerRunConfig = (AmSocketServerRunConfig) localSocketManager.getLocalSocketRunConfig();
// Run am command and send its result to the client
StringBuilder stdout = new StringBuilder();
StringBuilder stderr = new StringBuilder();
error = runAmCommand(localSocketManager.getContext(), amCommandArray, stdout, stderr,
amSocketServerRunConfig.shouldCheckDisplayOverAppsPermission());
if (error != null) {
sendResultToClient(localSocketManager, clientSocket, 1, stdout.toString(),
!stderr.toString().isEmpty() ? stderr + "\n\n" + error : error.toString());
}
sendResultToClient(localSocketManager, clientSocket, 0, stdout.toString(), stderr.toString());
}
/**
* Send result to {@link LocalClientSocket} that requested the am command to be run.
*
* @param localSocketManager The {@link LocalSocketManager} instance for the local socket.
* @param clientSocket The {@link LocalClientSocket} to which the result is to be sent.
* @param exitCode The exit code value to send.
* @param stdout The stdout value to send.
* @param stderr The stderr value to send.
*/
public static void sendResultToClient(@NonNull LocalSocketManager localSocketManager,
@NonNull LocalClientSocket clientSocket,
int exitCode,
@Nullable String stdout, @Nullable String stderr) {
StringBuilder result = new StringBuilder();
result.append(sanitizeExitCode(clientSocket, exitCode));
result.append('\0');
result.append(stdout != null ? stdout : "");
result.append('\0');
result.append(stderr != null ? stderr : "");
// Send result to client and close output stream
Error error = clientSocket.sendDataToOutputStream(result.toString(), true);
if (error != null) {
localSocketManager.onError(clientSocket, error);
}
}
/**
* Sanitize exitCode to between 0-255, otherwise it may be considered invalid.
* Out of bound exit codes would return with exit code `44` `Channel number out of range` in shell.
*
* @param clientSocket The {@link LocalClientSocket} to which the exit code will be sent.
* @param exitCode The current exit code.
* @return Returns the sanitized exit code.
*/
public static int sanitizeExitCode(@NonNull LocalClientSocket clientSocket, int exitCode) {
if (exitCode < 0 || exitCode > 255) {
Logger.logWarn(LOG_TAG, "Ignoring invalid peer " + clientSocket.getPeerCred().getMinimalString() + " result value \"" + exitCode + "\" and force setting it to \"" + 1 + "\"");
exitCode = 1;
}
return exitCode;
}
/**
* Parse amCommandString into a list of arguments like normally done on shells like bourne shell.
* Arguments are split on whitespaces unless quoted with single or double quotes.
* Double quotes and backslashes can be escaped with backslashes in arguments surrounded.
* Double quotes and backslashes can be escaped with backslashes in arguments surrounded with
* double quotes.
*
* @param amCommandString The am command {@link String}.
* @param amCommandList The {@link List<String>} to set list of arguments in.
* @return Returns the {@code error} if parsing am command failed, otherwise {@code null}.
*/
public static Error parseAmCommand(String amCommandString, List<String> amCommandList) {
if (amCommandString == null || amCommandString.isEmpty()) {
return null;
}
try {
amCommandList.addAll(ArgumentTokenizer.tokenize(amCommandString));
} catch (Exception e) {
return AmSocketServerErrno.ERRNO_PARSE_AM_COMMAND_FAILED_WITH_EXCEPTION.getError(e, amCommandString, e.getMessage());
}
return null;
}
/**
* Call termux-am-library to run the am command.
*
* @param context The {@link Context} to run am command with.
* @param amCommandArray The am command array.
* @param stdout The {@link StringBuilder} to set stdout in that is returned by the am command.
* @param stderr The {@link StringBuilder} to set stderr in that is returned by the am command.
* @param checkDisplayOverAppsPermission Check if {@link Manifest.permission#SYSTEM_ALERT_WINDOW}
* has been granted if running on Android `>= 10` and
* starting activity or service.
* @return Returns the {@code error} if am command failed, otherwise {@code null}.
*/
public static Error runAmCommand(@NonNull Context context,
String[] amCommandArray,
@NonNull StringBuilder stdout, @NonNull StringBuilder stderr,
boolean checkDisplayOverAppsPermission) {
try (ByteArrayOutputStream stdoutByteStream = new ByteArrayOutputStream();
PrintStream stdoutPrintStream = new PrintStream(stdoutByteStream);
ByteArrayOutputStream stderrByteStream = new ByteArrayOutputStream();
PrintStream stderrPrintStream = new PrintStream(stderrByteStream)) {
if (checkDisplayOverAppsPermission && amCommandArray.length >= 1 &&
(amCommandArray[0].equals("start") || amCommandArray[0].equals("startservice")) &&
!PermissionUtils.validateDisplayOverOtherAppsPermissionForPostAndroid10(context, true)) {
throw new IllegalStateException(context.getString(R.string.error_display_over_other_apps_permission_not_granted,
PackageUtils.getAppNameForPackage(context)));
}
new Am(stdoutPrintStream, stderrPrintStream, (Application) context.getApplicationContext()).run(amCommandArray);
// Set stdout to value set by am command in stdoutPrintStream
stdoutPrintStream.flush();
stdout.append(stdoutByteStream.toString(StandardCharsets.UTF_8.name()));
// Set stderr to value set by am command in stderrPrintStream
stderrPrintStream.flush();
stderr.append(stderrByteStream.toString(StandardCharsets.UTF_8.name()));
} catch (Exception e) {
return AmSocketServerErrno.ERRNO_RUN_AM_COMMAND_FAILED_WITH_EXCEPTION.getError(e, Arrays.toString(amCommandArray), e.getMessage());
}
return null;
}
/** Implementation for {@link ILocalSocketManager} for {@link AmSocketServer}. */
public abstract static class AmSocketServerClient extends LocalSocketManagerClientBase {
@Override
public void onClientAccepted(@NonNull LocalSocketManager localSocketManager,
@NonNull LocalClientSocket clientSocket) {
AmSocketServer.processAmClient(localSocketManager, clientSocket);
super.onClientAccepted(localSocketManager, clientSocket);
}
}
}

Xet Storage Details

Size:
12.5 kB
·
Xet hash:
ed58ea8149a26d19650ac04d4814c9dcd3a4b6f83f7da2c63db0f30e979d360e

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.