mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-08 03:24:04 +08:00
274 lines
8.8 KiB
Java
274 lines
8.8 KiB
Java
package com.termux.shared.shell;
|
|
|
|
import android.content.Context;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import com.termux.shared.logger.Logger;
|
|
|
|
import java.io.Closeable;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
public class LocalFilesystemSocket
|
|
{
|
|
static {
|
|
System.loadLibrary("local-filesystem-socket");
|
|
}
|
|
|
|
/**
|
|
* Creates as UNIX server socket at {@code path}, with a backlog of {@code backlog}.
|
|
* @return The file descriptor of the server socket or -1 if an error occurred.
|
|
*/
|
|
private static native int createserversocket(@NonNull byte[] path, int backlog);
|
|
|
|
/**
|
|
* Accepts a connection on the supplied server socket.
|
|
* @return The file descriptor of the connection socket or -1 in case of an error.
|
|
*/
|
|
private static native int accept(int fd);
|
|
|
|
private static native void closefd(int fd);
|
|
|
|
|
|
/**
|
|
* Returns the UID of the socket peer, or -1 in case of an error.
|
|
*/
|
|
private static native int getpeeruid(int fd);
|
|
|
|
|
|
/**
|
|
* Sends {@code data} over the socket and decrements the timeout by the elapsed time. If the timeout hits or an error occurs, false is returned, true otherwise.
|
|
*/
|
|
private static native boolean send(@NonNull byte[] data, int fd, long deadline);
|
|
|
|
/**
|
|
* Receives data from the socket and decrements the timeout by the elapsed time. If the timeout hits or an error occurs, -1 is returned, otherwise the number of received bytes.
|
|
*/
|
|
private static native int recv(@NonNull byte[] data, int fd, long deadline);
|
|
|
|
/**
|
|
* Sets the send and receive timeout for the socket in milliseconds.
|
|
*/
|
|
private static native void settimeout(int fd, int timeout);
|
|
|
|
/**
|
|
* Gets the number of bytes available to read on the socket.
|
|
*/
|
|
private static native int available(int fd);
|
|
|
|
|
|
|
|
|
|
static class Socket implements Closeable {
|
|
private int fd;
|
|
private long deadline = 0;
|
|
private final SocketOutputStream out = new SocketOutputStream();
|
|
private final SocketInputStream in = new SocketInputStream();
|
|
|
|
class SocketInputStream extends InputStream {
|
|
private final byte[] readb = new byte[1];
|
|
|
|
@Override
|
|
public int read() throws IOException {
|
|
int ret = recv(readb);
|
|
if (ret == -1) {
|
|
throw new IOException("Could not read from socket");
|
|
}
|
|
if (ret == 0) {
|
|
return -1;
|
|
}
|
|
return readb[0];
|
|
}
|
|
|
|
@Override
|
|
public int read(byte[] b) throws IOException {
|
|
if (b == null) {
|
|
throw new NullPointerException("Read buffer can't be null");
|
|
}
|
|
int ret = recv(b);
|
|
if (ret == -1) {
|
|
throw new IOException("Could not read from socket");
|
|
}
|
|
if (ret == 0) {
|
|
return -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
@Override
|
|
public int available() {
|
|
return Socket.this.available();
|
|
}
|
|
}
|
|
|
|
class SocketOutputStream extends OutputStream {
|
|
private final byte[] writeb = new byte[1];
|
|
|
|
@Override
|
|
public void write(int b) throws IOException {
|
|
writeb[0] = (byte) b;
|
|
if (! send(writeb)) {
|
|
throw new IOException("Could not write to socket");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void write(byte[] b) throws IOException {
|
|
if (! send(b)) {
|
|
throw new IOException("Could not write to socket");
|
|
}
|
|
}
|
|
}
|
|
|
|
private Socket(int fd) {
|
|
this.fd = fd;
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the socket timeout, that makes a single send/recv error out if it triggers. For a deadline after which the socket should be finished see setDeadline()
|
|
*/
|
|
public void setTimeout(int timeout) {
|
|
if (fd != -1) {
|
|
settimeout(fd, timeout);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the deadline in unix milliseconds. When the deadline has elapsed and the socket timeout triggers, all reads and writes will error.
|
|
*/
|
|
public void setDeadline(long deadline) {
|
|
this.deadline = deadline;
|
|
}
|
|
|
|
public boolean send(@NonNull byte[] data) {
|
|
if (fd == -1) {
|
|
return false;
|
|
}
|
|
return LocalFilesystemSocket.send(data, fd, deadline);
|
|
}
|
|
|
|
public int recv(@NonNull byte[] data) {
|
|
if (fd == -1) {
|
|
return -1;
|
|
}
|
|
return LocalFilesystemSocket.recv(data, fd, deadline);
|
|
}
|
|
|
|
public int available() {
|
|
if (fd == -1 || System.currentTimeMillis() > deadline) {
|
|
return 0;
|
|
}
|
|
return LocalFilesystemSocket.available(fd);
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
if (fd != -1) {
|
|
closefd(fd);
|
|
fd = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the UID of the socket peer, or -1 in case of an error.
|
|
*/
|
|
public int getPeerUID() {
|
|
return getpeeruid(fd);
|
|
}
|
|
|
|
/**
|
|
* Returns an {@link OutputStream} for the socket. You don't need to close the stream, it's automatically closed when closing the socket.
|
|
*/
|
|
public OutputStream getOutputStream() {
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Returns an {@link InputStream} for the socket. You don't need to close the stream, it's automatically closed when closing the socket.
|
|
*/
|
|
public InputStream getInputStream() {
|
|
return in;
|
|
}
|
|
}
|
|
|
|
|
|
static class ServerSocket implements Closeable
|
|
{
|
|
private final String path;
|
|
private final Context app;
|
|
private int fd;
|
|
|
|
public ServerSocket(Context c, String path, int backlog) throws IOException {
|
|
app = c.getApplicationContext();
|
|
if (backlog <= 0) {
|
|
throw new IllegalArgumentException("Backlog has to be at least 1");
|
|
}
|
|
if (path == null || path.length() == 0) {
|
|
throw new IllegalArgumentException("path cannot be null or empty");
|
|
}
|
|
this.path = path;
|
|
if (path.getBytes(StandardCharsets.UTF_8)[0] != 0) {
|
|
// not a socket in the abstract linux namespace, make sure the path is accessible and clear
|
|
File f = new File(path);
|
|
File parent = f.getParentFile();
|
|
if (parent != null) {
|
|
parent.mkdirs();
|
|
}
|
|
f.delete();
|
|
}
|
|
|
|
fd = createserversocket(path.getBytes(StandardCharsets.UTF_8), backlog);
|
|
if (fd == -1) {
|
|
throw new IOException("Could not create UNIX server socket at \""+path+"\"");
|
|
}
|
|
}
|
|
|
|
public ServerSocket(Context c, String path) throws IOException {
|
|
this(c, path, 50); // 50 is the default value for the Android LocalSocket implementation, so this should be good here, too
|
|
}
|
|
|
|
public Socket accept() {
|
|
if (fd == -1) {
|
|
return null;
|
|
}
|
|
int c = -1;
|
|
while (true) {
|
|
while (c == -1) {
|
|
c = LocalFilesystemSocket.accept(fd);
|
|
}
|
|
int peeruid = getpeeruid(c);
|
|
if (peeruid == -1) {
|
|
Logger.logWarn("LocalFilesystemSocket.ServerSocket", "Could not verify peer uid, closing socket");
|
|
closefd(c);
|
|
c = -1;
|
|
continue;
|
|
}
|
|
|
|
// if the peer has the same uid or is root, allow the connection
|
|
if (peeruid == app.getApplicationInfo().uid || peeruid == 0) {
|
|
break;
|
|
} else {
|
|
Logger.logWarn("LocalFilesystemSocket.ServerSocket", "WARNING: An app with the uid of "+peeruid+" tried to connect to the socket at \""+path+"\", closing connection.");
|
|
closefd(c);
|
|
c = -1;
|
|
}
|
|
}
|
|
return new Socket(c);
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
if (fd != -1) {
|
|
closefd(fd);
|
|
fd = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|