forked from termux/termux-api-package
-
Notifications
You must be signed in to change notification settings - Fork 0
/
termux-api.c
186 lines (161 loc) · 7.31 KB
/
termux-api.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// termux-api.c - helper binary for calling termux api classes
// Usage: termux-api ${API_METHOD} ${ADDITIONAL_FLAGS}
// This executes
// am broadcast com.termux.api/.TermuxApiReceiver --es socket_input ${INPUT_SOCKET}
// --es socket_output ${OUTPUT_SOCKET}
// --es api_method ${API_METHOD}
// ${ADDITIONAL_FLAGS}
// where ${INPUT_SOCKET} and ${OUTPUT_SOCKET} are addresses to linux abstract namespace sockets,
// used to pass on stdin to the java implementation and pass back output from java to stdout.
#define _POSIX_SOURCE
#define _GNU_SOURCE
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
// Function which execs "am broadcast ..".
_Noreturn void exec_am_broadcast(int argc, char** argv, char* input_address_string, char* output_address_string)
{
// Redirect stdout to /dev/null (but leave stderr open):
close(STDOUT_FILENO);
open("/dev/null", O_RDONLY);
// Close stdin:
close(STDIN_FILENO);
int const extra_args = 15; // Including ending NULL.
char** child_argv = malloc((sizeof(char*)) * (argc + extra_args));
child_argv[0] = "am";
child_argv[1] = "broadcast";
child_argv[2] = "--user";
child_argv[3] = "0";
child_argv[4] = "-n";
child_argv[5] = "com.termux.api/.TermuxApiReceiver";
child_argv[6] = "--es";
// Input/output are reversed for the java process (our output is its input):
child_argv[7] = "socket_input";
child_argv[8] = output_address_string;
child_argv[9] = "--es";
child_argv[10] = "socket_output";
child_argv[11] = input_address_string;
child_argv[12] = "--es";
child_argv[13] = "api_method";
child_argv[14] = argv[1];
// Copy the remaining arguments -2 for first binary and second api name:
memcpy(child_argv + extra_args, argv + 2, (argc-1) * sizeof(char*));
// End with NULL:
child_argv[argc + extra_args] = NULL;
// Use an a executable taking care of PATH and LD_LIBRARY_PATH:
execv(PREFIX "/bin/am", child_argv);
perror("execv(\"" PREFIX "/bin/am\")");
exit(1);
}
_Noreturn void exec_callback(int fd)
{
char *fds;
if(asprintf(&fds, "%d", fd) == -1) { perror("asprintf"); }
execl(PREFIX "/libexec/termux-callback", "termux-callback", fds, NULL);
perror("execl(\"" PREFIX "/libexec/termux-callback\")");
exit(1);
}
void generate_uuid(char* str) {
sprintf(str, "%x%x-%x-%x-%x-%x%x%x",
arc4random(), arc4random(), // Generates a 64-bit Hex number
(uint32_t) getpid(), // Generates a 32-bit Hex number
((arc4random() & 0x0fff) | 0x4000), // Generates a 32-bit Hex number of the form 4xxx (4 indicates the UUID version)
arc4random() % 0x3fff + 0x8000, // Generates a 32-bit Hex number in the range [0x8000, 0xbfff]
arc4random(), arc4random(), arc4random()); // Generates a 96-bit Hex number
}
// Thread function which reads from stdin and writes to socket.
void* transmit_stdin_to_socket(void* arg) {
int output_server_socket = *((int*) arg);
struct sockaddr_un remote_addr;
socklen_t addrlen = sizeof(remote_addr);
int output_client_socket = accept(output_server_socket, (struct sockaddr*) &remote_addr, &addrlen);
ssize_t len;
char buffer[1024];
while (len = read(STDIN_FILENO, &buffer, sizeof(buffer)), len > 0) {
if (write(output_client_socket, buffer, len) < 0) break;
}
// Close output socket on end of input:
close(output_client_socket);
return NULL;
}
// Main thread function which reads from input socket and writes to stdout.
int transmit_socket_to_stdout(int input_socket_fd) {
ssize_t len;
char buffer[1024];
char cbuf[256];
struct iovec io = { .iov_base = buffer, .iov_len = sizeof(buffer) };
struct msghdr msg = { 0 };
int fd = -1; // An optional file descriptor received through the socket
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = cbuf;
msg.msg_controllen = sizeof(cbuf);
while ((len = recvmsg(input_socket_fd, &msg, 0)) > 0) {
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
if (cmsg->cmsg_type == SCM_RIGHTS) {
fd = *((int *) CMSG_DATA(cmsg));
}
}
// A file descriptor must be accompanied by a non-empty message,
// so we use "@" when we don't want any output.
if (fd != -1 && len == 1 && buffer[0] == '@') { len = 0; }
write(STDOUT_FILENO, buffer, len);
msg.msg_controllen = sizeof(cbuf);
}
if (len < 0) perror("recvmsg()");
return fd;
}
int main(int argc, char** argv) {
// Do not transform children into zombies when they terminate:
struct sigaction sigchld_action = { .sa_handler = SIG_DFL, .sa_flags = SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT };
sigaction(SIGCHLD, &sigchld_action, NULL);
char input_address_string[100]; // This program reads from it.
char output_address_string[100]; // This program writes to it.
generate_uuid(input_address_string);
generate_uuid(output_address_string);
struct sockaddr_un input_address = { .sun_family = AF_UNIX };
struct sockaddr_un output_address = { .sun_family = AF_UNIX };
// Leave struct sockaddr_un.sun_path[0] as 0 and use the UUID string as abstract linux namespace:
strncpy(&input_address.sun_path[1], input_address_string, strlen(input_address_string));
strncpy(&output_address.sun_path[1], output_address_string, strlen(output_address_string));
int input_server_socket = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (input_server_socket == -1) { perror("socket()"); return 1; }
int output_server_socket = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (output_server_socket == -1) { perror("socket()"); return 1; }
if (bind(input_server_socket, (struct sockaddr*) &input_address, sizeof(sa_family_t) + strlen(input_address_string) + 1) == -1) {
perror("bind(input)");
return 1;
}
if (bind(output_server_socket, (struct sockaddr*) &output_address, sizeof(sa_family_t) + strlen(output_address_string) + 1) == -1) {
perror("bind(output)");
return 1;
}
if (listen(input_server_socket, 1) == -1) { perror("listen()"); return 1; }
if (listen(output_server_socket, 1) == -1) { perror("listen()"); return 1; }
pid_t fork_result = fork();
switch (fork_result) {
case -1: perror("fork()"); return 1;
case 0: exec_am_broadcast(argc, argv, input_address_string, output_address_string);
}
struct sockaddr_un remote_addr;
socklen_t addrlen = sizeof(remote_addr);
int input_client_socket = accept(input_server_socket, (struct sockaddr*) &remote_addr, &addrlen);
pthread_t transmit_thread;
pthread_create(&transmit_thread, NULL, transmit_stdin_to_socket, &output_server_socket);
int fd = transmit_socket_to_stdout(input_client_socket);
close(input_client_socket);
if (fd != -1) { exec_callback(fd); }
return 0;
}