Convert indentation to spaces

The tab war is lost
This commit is contained in:
topjohnwu
2020-12-30 22:11:24 -08:00
parent 947a7d6a2f
commit f9bde347bc
78 changed files with 10945 additions and 10945 deletions

View File

@@ -10,9 +10,9 @@
using namespace std;
enum {
NAMED_ACTIVITY,
PKG_ACTIVITY,
CONTENT_PROVIDER
NAMED_ACTIVITY,
PKG_ACTIVITY,
CONTENT_PROVIDER
};
#define CALL_PROVIDER \
@@ -34,179 +34,179 @@ enum {
(to.command[0] ? to.command : to.shell[0] ? to.shell : DEFAULT_SHELL)
class Extra {
const char *key;
enum {
INT,
BOOL,
STRING
} type;
union {
int int_val;
bool bool_val;
const char * str_val;
};
char buf[32];
const char *key;
enum {
INT,
BOOL,
STRING
} type;
union {
int int_val;
bool bool_val;
const char * str_val;
};
char buf[32];
public:
Extra(const char *k, int v): key(k), type(INT), int_val(v) {}
Extra(const char *k, bool v): key(k), type(BOOL), bool_val(v) {}
Extra(const char *k, const char *v): key(k), type(STRING), str_val(v) {}
Extra(const char *k, int v): key(k), type(INT), int_val(v) {}
Extra(const char *k, bool v): key(k), type(BOOL), bool_val(v) {}
Extra(const char *k, const char *v): key(k), type(STRING), str_val(v) {}
void add_intent(vector<const char *> &vec) {
const char *val;
switch (type) {
case INT:
vec.push_back("--ei");
sprintf(buf, "%d", int_val);
val = buf;
break;
case BOOL:
vec.push_back("--ez");
val = bool_val ? "true" : "false";
break;
case STRING:
vec.push_back("--es");
val = str_val;
break;
}
vec.push_back(key);
vec.push_back(val);
}
void add_intent(vector<const char *> &vec) {
const char *val;
switch (type) {
case INT:
vec.push_back("--ei");
sprintf(buf, "%d", int_val);
val = buf;
break;
case BOOL:
vec.push_back("--ez");
val = bool_val ? "true" : "false";
break;
case STRING:
vec.push_back("--es");
val = str_val;
break;
}
vec.push_back(key);
vec.push_back(val);
}
void add_bind(vector<const char *> &vec) {
switch (type) {
case INT:
sprintf(buf, "%s:i:%d", key, int_val);
break;
case BOOL:
sprintf(buf, "%s:b:%s", key, bool_val ? "true" : "false");
break;
case STRING:
sprintf(buf, "%s:s:%s", key, str_val);
break;
}
vec.push_back("--extra");
vec.push_back(buf);
}
void add_bind(vector<const char *> &vec) {
switch (type) {
case INT:
sprintf(buf, "%s:i:%d", key, int_val);
break;
case BOOL:
sprintf(buf, "%s:b:%s", key, bool_val ? "true" : "false");
break;
case STRING:
sprintf(buf, "%s:s:%s", key, str_val);
break;
}
vec.push_back("--extra");
vec.push_back(buf);
}
};
static bool check_no_error(int fd) {
char buf[1024];
auto out = xopen_file(fd, "r");
while (fgets(buf, sizeof(buf), out.get())) {
if (strncmp(buf, "Error", 5) == 0)
return false;
}
return true;
char buf[1024];
auto out = xopen_file(fd, "r");
while (fgets(buf, sizeof(buf), out.get())) {
if (strncmp(buf, "Error", 5) == 0)
return false;
}
return true;
}
static void exec_cmd(const char *action, vector<Extra> &data,
const shared_ptr<su_info> &info, int mode = CONTENT_PROVIDER) {
char target[128];
char user[4];
sprintf(user, "%d", get_user(info));
const shared_ptr<su_info> &info, int mode = CONTENT_PROVIDER) {
char target[128];
char user[4];
sprintf(user, "%d", get_user(info));
// First try content provider call method
if (mode >= CONTENT_PROVIDER) {
sprintf(target, "content://%s.provider", info->str[SU_MANAGER].data());
vector<const char *> args{ CALL_PROVIDER };
for (auto &e : data) {
e.add_bind(args);
}
args.push_back(nullptr);
exec_t exec {
.err = true,
.fd = -1,
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/content.jar", 1); },
.argv = args.data()
};
exec_command_sync(exec);
if (check_no_error(exec.fd))
return;
}
// First try content provider call method
if (mode >= CONTENT_PROVIDER) {
sprintf(target, "content://%s.provider", info->str[SU_MANAGER].data());
vector<const char *> args{ CALL_PROVIDER };
for (auto &e : data) {
e.add_bind(args);
}
args.push_back(nullptr);
exec_t exec {
.err = true,
.fd = -1,
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/content.jar", 1); },
.argv = args.data()
};
exec_command_sync(exec);
if (check_no_error(exec.fd))
return;
}
vector<const char *> args{ START_ACTIVITY };
for (auto &e : data) {
e.add_intent(args);
}
args.push_back(nullptr);
exec_t exec {
.err = true,
.fd = -1,
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/am.jar", 1); },
.argv = args.data()
};
vector<const char *> args{ START_ACTIVITY };
for (auto &e : data) {
e.add_intent(args);
}
args.push_back(nullptr);
exec_t exec {
.err = true,
.fd = -1,
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/am.jar", 1); },
.argv = args.data()
};
if (mode >= PKG_ACTIVITY) {
// Then try start activity without component name
strcpy(target, info->str[SU_MANAGER].data());
exec_command_sync(exec);
if (check_no_error(exec.fd))
return;
}
if (mode >= PKG_ACTIVITY) {
// Then try start activity without component name
strcpy(target, info->str[SU_MANAGER].data());
exec_command_sync(exec);
if (check_no_error(exec.fd))
return;
}
// Finally, fallback to start activity with component name
args[4] = "-n";
sprintf(target, "%s/a.m", info->str[SU_MANAGER].data());
exec.fd = -2;
exec.fork = fork_dont_care;
exec_command(exec);
// Finally, fallback to start activity with component name
args[4] = "-n";
sprintf(target, "%s/a.m", info->str[SU_MANAGER].data());
exec.fd = -2;
exec.fork = fork_dont_care;
exec_command(exec);
}
void app_log(const su_context &ctx) {
if (fork_dont_care() == 0) {
vector<Extra> extras;
extras.reserve(6);
extras.emplace_back("from.uid", ctx.info->uid);
extras.emplace_back("to.uid", ctx.req.uid);
extras.emplace_back("pid", ctx.pid);
extras.emplace_back("policy", ctx.info->access.policy);
extras.emplace_back("command", get_cmd(ctx.req));
extras.emplace_back("notify", (bool) ctx.info->access.notify);
if (fork_dont_care() == 0) {
vector<Extra> extras;
extras.reserve(6);
extras.emplace_back("from.uid", ctx.info->uid);
extras.emplace_back("to.uid", ctx.req.uid);
extras.emplace_back("pid", ctx.pid);
extras.emplace_back("policy", ctx.info->access.policy);
extras.emplace_back("command", get_cmd(ctx.req));
extras.emplace_back("notify", (bool) ctx.info->access.notify);
exec_cmd("log", extras, ctx.info);
exit(0);
}
exec_cmd("log", extras, ctx.info);
exit(0);
}
}
void app_notify(const su_context &ctx) {
if (fork_dont_care() == 0) {
vector<Extra> extras;
extras.reserve(2);
extras.emplace_back("from.uid", ctx.info->uid);
extras.emplace_back("policy", ctx.info->access.policy);
if (fork_dont_care() == 0) {
vector<Extra> extras;
extras.reserve(2);
extras.emplace_back("from.uid", ctx.info->uid);
extras.emplace_back("policy", ctx.info->access.policy);
exec_cmd("notify", extras, ctx.info);
exit(0);
}
exec_cmd("notify", extras, ctx.info);
exit(0);
}
}
int app_request(const shared_ptr<su_info> &info) {
// Create FIFO
char fifo[64];
strcpy(fifo, "/dev/socket/");
gen_rand_str(fifo + 12, 32, true);
mkfifo(fifo, 0600);
chown(fifo, info->mgr_st.st_uid, info->mgr_st.st_gid);
setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0");
// Create FIFO
char fifo[64];
strcpy(fifo, "/dev/socket/");
gen_rand_str(fifo + 12, 32, true);
mkfifo(fifo, 0600);
chown(fifo, info->mgr_st.st_uid, info->mgr_st.st_gid);
setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0");
// Send request
vector<Extra> extras;
extras.reserve(2);
extras.emplace_back("fifo", fifo);
extras.emplace_back("uid", info->uid);
exec_cmd("request", extras, info, PKG_ACTIVITY);
// Send request
vector<Extra> extras;
extras.reserve(2);
extras.emplace_back("fifo", fifo);
extras.emplace_back("uid", info->uid);
exec_cmd("request", extras, info, PKG_ACTIVITY);
// Wait for data input for at most 70 seconds
int fd = xopen(fifo, O_RDONLY | O_CLOEXEC);
struct pollfd pfd = {
.fd = fd,
.events = POLL_IN
};
if (xpoll(&pfd, 1, 70 * 1000) <= 0) {
close(fd);
fd = -1;
}
// Wait for data input for at most 70 seconds
int fd = xopen(fifo, O_RDONLY | O_CLOEXEC);
struct pollfd pfd = {
.fd = fd,
.events = POLL_IN
};
if (xpoll(&pfd, 1, 70 * 1000) <= 0) {
close(fd);
fd = -1;
}
unlink(fifo);
return fd;
unlink(fifo);
return fd;
}

View File

@@ -26,16 +26,16 @@
*/
// Ensures all the data is written out
static int write_blocking(int fd, char *buf, ssize_t bufsz) {
ssize_t ret, written;
ssize_t ret, written;
written = 0;
do {
ret = write(fd, buf + written, bufsz - written);
if (ret == -1) return -1;
written += ret;
} while (written < bufsz);
written = 0;
do {
ret = write(fd, buf + written, bufsz - written);
if (ret == -1) return -1;
written += ret;
} while (written < bufsz);
return 0;
return 0;
}
/**
@@ -43,28 +43,28 @@ static int write_blocking(int fd, char *buf, ssize_t bufsz) {
* true, then close the output FD when we're done.
*/
static void pump(int input, int output, bool close_output = true) {
char buf[4096];
int len;
while ((len = read(input, buf, 4096)) > 0) {
if (write_blocking(output, buf, len) == -1) break;
}
close(input);
if (close_output) close(output);
char buf[4096];
int len;
while ((len = read(input, buf, 4096)) > 0) {
if (write_blocking(output, buf, len) == -1) break;
}
close(input);
if (close_output) close(output);
}
static void* pump_thread(void* data) {
int *fds = (int*) data;
pump(fds[0], fds[1]);
delete[] fds;
return nullptr;
int *fds = (int*) data;
pump(fds[0], fds[1]);
delete[] fds;
return nullptr;
}
static void pump_async(int input, int output) {
pthread_t writer;
int *fds = new int[2];
fds[0] = input;
fds[1] = output;
pthread_create(&writer, nullptr, pump_thread, fds);
pthread_t writer;
int *fds = new int[2];
fds[0] = input;
fds[1] = output;
pthread_create(&writer, nullptr, pump_thread, fds);
}
@@ -82,31 +82,31 @@ static void pump_async(int input, int output) {
* on success, the file descriptor of the master device is returned.
*/
int pts_open(char *slave_name, size_t slave_name_size) {
int fdm;
int fdm;
// Open master ptmx device
fdm = open("/dev/ptmx", O_RDWR);
if (fdm == -1)
goto error;
// Open master ptmx device
fdm = open("/dev/ptmx", O_RDWR);
if (fdm == -1)
goto error;
// Get the slave name
if (ptsname_r(fdm, slave_name, slave_name_size-1))
goto error;
// Get the slave name
if (ptsname_r(fdm, slave_name, slave_name_size-1))
goto error;
slave_name[slave_name_size - 1] = '\0';
slave_name[slave_name_size - 1] = '\0';
// Grant, then unlock
if (grantpt(fdm) == -1)
goto error;
// Grant, then unlock
if (grantpt(fdm) == -1)
goto error;
if (unlockpt(fdm) == -1)
goto error;
if (unlockpt(fdm) == -1)
goto error;
return fdm;
return fdm;
error:
close(fdm);
PLOGE("pts_open");
return -1;
close(fdm);
PLOGE("pts_open");
return -1;
}
// Stores the previous termios of stdin
@@ -124,31 +124,31 @@ static int stdin_is_raw = 0;
* on success 0
*/
int set_stdin_raw(void) {
struct termios new_termios;
struct termios new_termios;
// Save the current stdin termios
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
return -1;
}
// Save the current stdin termios
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
return -1;
}
// Start from the current settings
new_termios = old_stdin;
// Start from the current settings
new_termios = old_stdin;
// Make the terminal like an SSH or telnet client
new_termios.c_iflag |= IGNPAR;
new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
new_termios.c_oflag &= ~OPOST;
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
// Make the terminal like an SSH or telnet client
new_termios.c_iflag |= IGNPAR;
new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
new_termios.c_oflag &= ~OPOST;
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
return -1;
}
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
return -1;
}
stdin_is_raw = 1;
stdin_is_raw = 1;
return 0;
return 0;
}
/**
@@ -165,15 +165,15 @@ int set_stdin_raw(void) {
* on success, 0
*/
int restore_stdin(void) {
if (!stdin_is_raw) return 0;
if (!stdin_is_raw) return 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
return -1;
}
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
return -1;
}
stdin_is_raw = 0;
stdin_is_raw = 0;
return 0;
return 0;
}
// Flag indicating whether the sigwinch watcher should terminate.
@@ -184,30 +184,30 @@ static volatile bool close_sigwinch_watcher = false;
* the terminal size.
*/
static void *watch_sigwinch(void *data) {
sigset_t winch;
int *fds = (int *)data;
int sig;
sigset_t winch;
int *fds = (int *)data;
int sig;
sigemptyset(&winch);
sigaddset(&winch, SIGWINCH);
pthread_sigmask(SIG_UNBLOCK, &winch, nullptr);
sigemptyset(&winch);
sigaddset(&winch, SIGWINCH);
pthread_sigmask(SIG_UNBLOCK, &winch, nullptr);
do {
if (close_sigwinch_watcher)
break;
do {
if (close_sigwinch_watcher)
break;
// Get the new terminal size
struct winsize w;
if (ioctl(fds[0], TIOCGWINSZ, &w) == -1)
continue;
// Get the new terminal size
struct winsize w;
if (ioctl(fds[0], TIOCGWINSZ, &w) == -1)
continue;
// Set the new terminal size
ioctl(fds[1], TIOCSWINSZ, &w);
// Set the new terminal size
ioctl(fds[1], TIOCSWINSZ, &w);
} while (sigwait(&winch, &sig) == 0);
delete[] fds;
} while (sigwait(&winch, &sig) == 0);
delete[] fds;
return nullptr;
return nullptr;
}
/**
@@ -233,30 +233,30 @@ static void *watch_sigwinch(void *data) {
* on success, 0
*/
int watch_sigwinch_async(int master, int slave) {
pthread_t watcher;
int *fds = new int[2];
pthread_t watcher;
int *fds = new int[2];
// Block SIGWINCH so sigwait can later receive it
sigset_t winch;
sigemptyset(&winch);
sigaddset(&winch, SIGWINCH);
if (pthread_sigmask(SIG_BLOCK, &winch, nullptr) == -1) {
delete[] fds;
return -1;
}
// Block SIGWINCH so sigwait can later receive it
sigset_t winch;
sigemptyset(&winch);
sigaddset(&winch, SIGWINCH);
if (pthread_sigmask(SIG_BLOCK, &winch, nullptr) == -1) {
delete[] fds;
return -1;
}
// Initialize some variables, then start the thread
close_sigwinch_watcher = 0;
fds[0] = master;
fds[1] = slave;
int ret = pthread_create(&watcher, nullptr, &watch_sigwinch, fds);
if (ret != 0) {
delete[] fds;
errno = ret;
return -1;
}
// Initialize some variables, then start the thread
close_sigwinch_watcher = 0;
fds[0] = master;
fds[1] = slave;
int ret = pthread_create(&watcher, nullptr, &watch_sigwinch, fds);
if (ret != 0) {
delete[] fds;
errno = ret;
return -1;
}
return 0;
return 0;
}
/**
@@ -266,11 +266,11 @@ int watch_sigwinch_async(int master, int slave) {
* in a seperate thread
*/
void pump_stdin_async(int outfd) {
// Put stdin into raw mode
set_stdin_raw();
// Put stdin into raw mode
set_stdin_raw();
// Pump data from stdin to the PTY
pump_async(STDIN_FILENO, outfd);
// Pump data from stdin to the PTY
pump_async(STDIN_FILENO, outfd);
}
/**
@@ -282,11 +282,11 @@ void pump_stdin_async(int outfd) {
* Before returning, restores stdin settings.
*/
void pump_stdout_blocking(int infd) {
// Pump data from stdout to PTY
pump(infd, STDOUT_FILENO, false /* Don't close output when done */);
// Pump data from stdout to PTY
pump(infd, STDOUT_FILENO, false /* Don't close output when done */);
// Cleanup
restore_stdin();
close_sigwinch_watcher = true;
raise(SIGWINCH);
// Cleanup
restore_stdin();
close_sigwinch_watcher = true;
raise(SIGWINCH);
}

View File

@@ -31,200 +31,200 @@
int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
static void usage(int status) {
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
fprintf(stream,
"MagiskSU\n\n"
"Usage: su [options] [-] [user [argument...]]\n\n"
"Options:\n"
" -c, --command COMMAND pass COMMAND to the invoked shell\n"
" -h, --help display this help message and exit\n"
" -, -l, --login pretend the shell to be a login shell\n"
" -m, -p,\n"
" --preserve-environment preserve the entire environment\n"
" -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n"
" -v, --version display version number and exit\n"
" -V display version code and exit\n"
" -mm, -M,\n"
" --mount-master force run in the global mount namespace\n");
exit(status);
fprintf(stream,
"MagiskSU\n\n"
"Usage: su [options] [-] [user [argument...]]\n\n"
"Options:\n"
" -c, --command COMMAND pass COMMAND to the invoked shell\n"
" -h, --help display this help message and exit\n"
" -, -l, --login pretend the shell to be a login shell\n"
" -m, -p,\n"
" --preserve-environment preserve the entire environment\n"
" -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n"
" -v, --version display version number and exit\n"
" -V display version code and exit\n"
" -mm, -M,\n"
" --mount-master force run in the global mount namespace\n");
exit(status);
}
static char *concat_commands(int argc, char *argv[]) {
char command[ARG_MAX];
command[0] = '\0';
for (int i = optind - 1; i < argc; ++i) {
if (command[0])
sprintf(command, "%s %s", command, argv[i]);
else
strcpy(command, argv[i]);
}
return strdup(command);
char command[ARG_MAX];
command[0] = '\0';
for (int i = optind - 1; i < argc; ++i) {
if (command[0])
sprintf(command, "%s %s", command, argv[i]);
else
strcpy(command, argv[i]);
}
return strdup(command);
}
static void sighandler(int sig) {
restore_stdin();
restore_stdin();
// Assume we'll only be called before death
// See note before sigaction() in set_stdin_raw()
//
// Now, close all standard I/O to cause the pumps
// to exit so we can continue and retrieve the exit
// code
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// Assume we'll only be called before death
// See note before sigaction() in set_stdin_raw()
//
// Now, close all standard I/O to cause the pumps
// to exit so we can continue and retrieve the exit
// code
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// Put back all the default handlers
struct sigaction act;
// Put back all the default handlers
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_DFL;
for (int i = 0; quit_signals[i]; ++i) {
sigaction(quit_signals[i], &act, nullptr);
}
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_DFL;
for (int i = 0; quit_signals[i]; ++i) {
sigaction(quit_signals[i], &act, nullptr);
}
}
static void setup_sighandlers(void (*handler)(int)) {
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = handler;
for (int i = 0; quit_signals[i]; ++i) {
sigaction(quit_signals[i], &act, nullptr);
}
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = handler;
for (int i = 0; quit_signals[i]; ++i) {
sigaction(quit_signals[i], &act, nullptr);
}
}
int su_client_main(int argc, char *argv[]) {
int c;
struct option long_opts[] = {
{ "command", required_argument, nullptr, 'c' },
{ "help", no_argument, nullptr, 'h' },
{ "login", no_argument, nullptr, 'l' },
{ "preserve-environment", no_argument, nullptr, 'p' },
{ "shell", required_argument, nullptr, 's' },
{ "version", no_argument, nullptr, 'v' },
{ "context", required_argument, nullptr, 'z' },
{ "mount-master", no_argument, nullptr, 'M' },
{ nullptr, 0, nullptr, 0 },
};
int c;
struct option long_opts[] = {
{ "command", required_argument, nullptr, 'c' },
{ "help", no_argument, nullptr, 'h' },
{ "login", no_argument, nullptr, 'l' },
{ "preserve-environment", no_argument, nullptr, 'p' },
{ "shell", required_argument, nullptr, 's' },
{ "version", no_argument, nullptr, 'v' },
{ "context", required_argument, nullptr, 'z' },
{ "mount-master", no_argument, nullptr, 'M' },
{ nullptr, 0, nullptr, 0 },
};
su_request su_req;
su_request su_req;
for (int i = 0; i < argc; i++) {
// Replace -cn with -z, -mm with -M for supporting getopt_long
if (strcmp(argv[i], "-cn") == 0)
strcpy(argv[i], "-z");
else if (strcmp(argv[i], "-mm") == 0)
strcpy(argv[i], "-M");
}
for (int i = 0; i < argc; i++) {
// Replace -cn with -z, -mm with -M for supporting getopt_long
if (strcmp(argv[i], "-cn") == 0)
strcpy(argv[i], "-z");
else if (strcmp(argv[i], "-mm") == 0)
strcpy(argv[i], "-M");
}
while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:M", long_opts, nullptr)) != -1) {
switch (c) {
case 'c':
su_req.command = concat_commands(argc, argv);
optind = argc;
break;
case 'h':
usage(EXIT_SUCCESS);
break;
case 'l':
su_req.login = true;
break;
case 'm':
case 'p':
su_req.keepenv = true;
break;
case 's':
su_req.shell = optarg;
break;
case 'V':
printf("%d\n", MAGISK_VER_CODE);
exit(EXIT_SUCCESS);
case 'v':
printf("%s\n", MAGISK_VERSION ":MAGISKSU");
exit(EXIT_SUCCESS);
case 'z':
// Do nothing, placed here for legacy support :)
break;
case 'M':
su_req.mount_master = true;
break;
default:
/* Bionic getopt_long doesn't terminate its error output by newline */
fprintf(stderr, "\n");
usage(2);
}
}
while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:M", long_opts, nullptr)) != -1) {
switch (c) {
case 'c':
su_req.command = concat_commands(argc, argv);
optind = argc;
break;
case 'h':
usage(EXIT_SUCCESS);
break;
case 'l':
su_req.login = true;
break;
case 'm':
case 'p':
su_req.keepenv = true;
break;
case 's':
su_req.shell = optarg;
break;
case 'V':
printf("%d\n", MAGISK_VER_CODE);
exit(EXIT_SUCCESS);
case 'v':
printf("%s\n", MAGISK_VERSION ":MAGISKSU");
exit(EXIT_SUCCESS);
case 'z':
// Do nothing, placed here for legacy support :)
break;
case 'M':
su_req.mount_master = true;
break;
default:
/* Bionic getopt_long doesn't terminate its error output by newline */
fprintf(stderr, "\n");
usage(2);
}
}
if (optind < argc && strcmp(argv[optind], "-") == 0) {
su_req.login = true;
optind++;
}
/* username or uid */
if (optind < argc) {
struct passwd *pw;
pw = getpwnam(argv[optind]);
if (pw)
su_req.uid = pw->pw_uid;
else
su_req.uid = parse_int(argv[optind]);
optind++;
}
if (optind < argc && strcmp(argv[optind], "-") == 0) {
su_req.login = true;
optind++;
}
/* username or uid */
if (optind < argc) {
struct passwd *pw;
pw = getpwnam(argv[optind]);
if (pw)
su_req.uid = pw->pw_uid;
else
su_req.uid = parse_int(argv[optind]);
optind++;
}
char pts_slave[PATH_MAX];
int ptmx, fd;
char pts_slave[PATH_MAX];
int ptmx, fd;
// Connect to client
fd = connect_daemon();
// Connect to client
fd = connect_daemon();
// Tell the daemon we are su
write_int(fd, SUPERUSER);
// Tell the daemon we are su
write_int(fd, SUPERUSER);
// Send su_request
xwrite(fd, &su_req, sizeof(su_req_base));
write_string(fd, su_req.shell);
write_string(fd, su_req.command);
// Send su_request
xwrite(fd, &su_req, sizeof(su_req_base));
write_string(fd, su_req.shell);
write_string(fd, su_req.command);
// Wait for ack from daemon
if (read_int(fd)) {
// Fast fail
fprintf(stderr, "%s\n", strerror(EACCES));
return EACCES;
}
// Wait for ack from daemon
if (read_int(fd)) {
// Fast fail
fprintf(stderr, "%s\n", strerror(EACCES));
return EACCES;
}
// Determine which one of our streams are attached to a TTY
int atty = 0;
if (isatty(STDIN_FILENO)) atty |= ATTY_IN;
if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT;
if (isatty(STDERR_FILENO)) atty |= ATTY_ERR;
// Determine which one of our streams are attached to a TTY
int atty = 0;
if (isatty(STDIN_FILENO)) atty |= ATTY_IN;
if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT;
if (isatty(STDERR_FILENO)) atty |= ATTY_ERR;
if (atty) {
// We need a PTY. Get one.
ptmx = pts_open(pts_slave, sizeof(pts_slave));
} else {
pts_slave[0] = '\0';
}
if (atty) {
// We need a PTY. Get one.
ptmx = pts_open(pts_slave, sizeof(pts_slave));
} else {
pts_slave[0] = '\0';
}
// Send pts_slave
write_string(fd, pts_slave);
// Send pts_slave
write_string(fd, pts_slave);
// Send stdin
send_fd(fd, (atty & ATTY_IN) ? -1 : STDIN_FILENO);
// Send stdout
send_fd(fd, (atty & ATTY_OUT) ? -1 : STDOUT_FILENO);
// Send stderr
send_fd(fd, (atty & ATTY_ERR) ? -1 : STDERR_FILENO);
// Send stdin
send_fd(fd, (atty & ATTY_IN) ? -1 : STDIN_FILENO);
// Send stdout
send_fd(fd, (atty & ATTY_OUT) ? -1 : STDOUT_FILENO);
// Send stderr
send_fd(fd, (atty & ATTY_ERR) ? -1 : STDERR_FILENO);
if (atty) {
setup_sighandlers(sighandler);
watch_sigwinch_async(STDOUT_FILENO, ptmx);
pump_stdin_async(ptmx);
pump_stdout_blocking(ptmx);
}
if (atty) {
setup_sighandlers(sighandler);
watch_sigwinch_async(STDOUT_FILENO, ptmx);
pump_stdin_async(ptmx);
pump_stdout_blocking(ptmx);
}
// Get the exit code
int code = read_int(fd);
close(fd);
// Get the exit code
int code = read_int(fd);
close(fd);
return code;
return code;
}

View File

@@ -16,54 +16,54 @@
class su_info {
public:
/* Unique key */
const int uid;
/* Unique key */
const int uid;
/* These should be guarded with internal lock */
db_settings cfg;
db_strings str;
su_access access;
struct stat mgr_st;
/* These should be guarded with internal lock */
db_settings cfg;
db_strings str;
su_access access;
struct stat mgr_st;
/* This should be guarded with global cache lock */
long timestamp;
/* This should be guarded with global cache lock */
long timestamp;
su_info(unsigned uid = 0);
~su_info();
mutex_guard lock();
bool is_fresh();
void refresh();
su_info(unsigned uid = 0);
~su_info();
mutex_guard lock();
bool is_fresh();
void refresh();
private:
pthread_mutex_t _lock; /* Internal lock */
pthread_mutex_t _lock; /* Internal lock */
};
struct su_req_base {
int uid = UID_ROOT;
bool login = false;
bool keepenv = false;
bool mount_master = false;
int uid = UID_ROOT;
bool login = false;
bool keepenv = false;
bool mount_master = false;
} __attribute__((packed));
struct su_request : public su_req_base {
const char *shell = DEFAULT_SHELL;
const char *command = "";
su_request(bool dyn = false) : dyn(dyn) {}
~su_request() {
if (dyn) {
free(const_cast<char*>(shell));
free(const_cast<char*>(command));
}
}
const char *shell = DEFAULT_SHELL;
const char *command = "";
su_request(bool dyn = false) : dyn(dyn) {}
~su_request() {
if (dyn) {
free(const_cast<char*>(shell));
free(const_cast<char*>(command));
}
}
private:
bool dyn;
bool dyn;
} __attribute__((packed));
struct su_context {
std::shared_ptr<su_info> info;
su_request req;
int pid;
std::shared_ptr<su_info> info;
su_request req;
int pid;
};
void app_log(const su_context &ctx);

View File

@@ -25,319 +25,319 @@ static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
static shared_ptr<su_info> cached;
su_info::su_info(unsigned uid) :
uid(uid), access(DEFAULT_SU_ACCESS), mgr_st({}),
timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {}
uid(uid), access(DEFAULT_SU_ACCESS), mgr_st({}),
timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {}
su_info::~su_info() {
pthread_mutex_destroy(&_lock);
pthread_mutex_destroy(&_lock);
}
mutex_guard su_info::lock() {
return mutex_guard(_lock);
return mutex_guard(_lock);
}
bool su_info::is_fresh() {
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
long current = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
return current - timestamp < 3000; /* 3 seconds */
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
long current = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
return current - timestamp < 3000; /* 3 seconds */
}
void su_info::refresh() {
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
timestamp = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
timestamp = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
}
static void database_check(const shared_ptr<su_info> &info) {
int uid = info->uid;
get_db_settings(info->cfg);
get_db_strings(info->str);
int uid = info->uid;
get_db_settings(info->cfg);
get_db_strings(info->str);
// Check multiuser settings
switch (info->cfg[SU_MULTIUSER_MODE]) {
case MULTIUSER_MODE_OWNER_ONLY:
if (info->uid / 100000) {
uid = -1;
info->access = NO_SU_ACCESS;
}
break;
case MULTIUSER_MODE_OWNER_MANAGED:
uid = info->uid % 100000;
break;
case MULTIUSER_MODE_USER:
default:
break;
}
// Check multiuser settings
switch (info->cfg[SU_MULTIUSER_MODE]) {
case MULTIUSER_MODE_OWNER_ONLY:
if (info->uid / 100000) {
uid = -1;
info->access = NO_SU_ACCESS;
}
break;
case MULTIUSER_MODE_OWNER_MANAGED:
uid = info->uid % 100000;
break;
case MULTIUSER_MODE_USER:
default:
break;
}
if (uid > 0)
get_uid_policy(info->access, uid);
if (uid > 0)
get_uid_policy(info->access, uid);
// We need to check our manager
if (info->access.log || info->access.notify)
validate_manager(info->str[SU_MANAGER], uid / 100000, &info->mgr_st);
// We need to check our manager
if (info->access.log || info->access.notify)
validate_manager(info->str[SU_MANAGER], uid / 100000, &info->mgr_st);
}
static shared_ptr<su_info> get_su_info(unsigned uid) {
LOGD("su: request from uid=[%d]\n", uid);
LOGD("su: request from uid=[%d]\n", uid);
shared_ptr<su_info> info;
shared_ptr<su_info> info;
{
mutex_guard lock(cache_lock);
if (!cached || cached->uid != uid || !cached->is_fresh())
cached = make_shared<su_info>(uid);
cached->refresh();
info = cached;
}
{
mutex_guard lock(cache_lock);
if (!cached || cached->uid != uid || !cached->is_fresh())
cached = make_shared<su_info>(uid);
cached->refresh();
info = cached;
}
mutex_guard lock = info->lock();
mutex_guard lock = info->lock();
if (info->access.policy == QUERY) {
// Not cached, get data from database
database_check(info);
if (info->access.policy == QUERY) {
// Not cached, get data from database
database_check(info);
// If it's root or the manager, allow it silently
if (info->uid == UID_ROOT || (info->uid % 100000) == (info->mgr_st.st_uid % 100000)) {
info->access = SILENT_SU_ACCESS;
return info;
}
// If it's root or the manager, allow it silently
if (info->uid == UID_ROOT || (info->uid % 100000) == (info->mgr_st.st_uid % 100000)) {
info->access = SILENT_SU_ACCESS;
return info;
}
// Check su access settings
switch (info->cfg[ROOT_ACCESS]) {
case ROOT_ACCESS_DISABLED:
LOGW("Root access is disabled!\n");
info->access = NO_SU_ACCESS;
break;
case ROOT_ACCESS_ADB_ONLY:
if (info->uid != UID_SHELL) {
LOGW("Root access limited to ADB only!\n");
info->access = NO_SU_ACCESS;
}
break;
case ROOT_ACCESS_APPS_ONLY:
if (info->uid == UID_SHELL) {
LOGW("Root access is disabled for ADB!\n");
info->access = NO_SU_ACCESS;
}
break;
case ROOT_ACCESS_APPS_AND_ADB:
default:
break;
}
// Check su access settings
switch (info->cfg[ROOT_ACCESS]) {
case ROOT_ACCESS_DISABLED:
LOGW("Root access is disabled!\n");
info->access = NO_SU_ACCESS;
break;
case ROOT_ACCESS_ADB_ONLY:
if (info->uid != UID_SHELL) {
LOGW("Root access limited to ADB only!\n");
info->access = NO_SU_ACCESS;
}
break;
case ROOT_ACCESS_APPS_ONLY:
if (info->uid == UID_SHELL) {
LOGW("Root access is disabled for ADB!\n");
info->access = NO_SU_ACCESS;
}
break;
case ROOT_ACCESS_APPS_AND_ADB:
default:
break;
}
if (info->access.policy != QUERY)
return info;
if (info->access.policy != QUERY)
return info;
// If still not determined, check if manager exists
if (info->str[SU_MANAGER].empty()) {
info->access = NO_SU_ACCESS;
return info;
}
} else {
return info;
}
// If still not determined, check if manager exists
if (info->str[SU_MANAGER].empty()) {
info->access = NO_SU_ACCESS;
return info;
}
} else {
return info;
}
// If still not determined, ask manager
int fd = app_request(info);
if (fd < 0) {
info->access.policy = DENY;
} else {
int ret = read_int_be(fd);
info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
close(fd);
}
// If still not determined, ask manager
int fd = app_request(info);
if (fd < 0) {
info->access.policy = DENY;
} else {
int ret = read_int_be(fd);
info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
close(fd);
}
return info;
return info;
}
// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root
static void set_identity(unsigned uid) {
if (seteuid(0)) {
PLOGE("seteuid (root)");
}
if (setresgid(uid, uid, uid)) {
PLOGE("setresgid (%u)", uid);
}
if (setresuid(uid, uid, uid)) {
PLOGE("setresuid (%u)", uid);
}
if (seteuid(0)) {
PLOGE("seteuid (root)");
}
if (setresgid(uid, uid, uid)) {
PLOGE("setresgid (%u)", uid);
}
if (setresuid(uid, uid, uid)) {
PLOGE("setresuid (%u)", uid);
}
}
void su_daemon_handler(int client, struct ucred *credential) {
LOGD("su: request from pid=[%d], client=[%d]\n", credential->pid, client);
LOGD("su: request from pid=[%d], client=[%d]\n", credential->pid, client);
su_context ctx = {
.info = get_su_info(credential->uid),
.req = su_request(true),
.pid = credential->pid
};
su_context ctx = {
.info = get_su_info(credential->uid),
.req = su_request(true),
.pid = credential->pid
};
// Read su_request
xxread(client, &ctx.req, sizeof(su_req_base));
ctx.req.shell = read_string(client);
ctx.req.command = read_string(client);
// Read su_request
xxread(client, &ctx.req, sizeof(su_req_base));
ctx.req.shell = read_string(client);
ctx.req.command = read_string(client);
if (ctx.info->access.log)
app_log(ctx);
else if (ctx.info->access.notify)
app_notify(ctx);
if (ctx.info->access.log)
app_log(ctx);
else if (ctx.info->access.notify)
app_notify(ctx);
// Fail fast
if (ctx.info->access.policy == DENY) {
LOGW("su: request rejected (%u)\n", ctx.info->uid);
ctx.info.reset();
write_int(client, DENY);
close(client);
return;
}
// Fail fast
if (ctx.info->access.policy == DENY) {
LOGW("su: request rejected (%u)\n", ctx.info->uid);
ctx.info.reset();
write_int(client, DENY);
close(client);
return;
}
// Fork a child root process
//
// The child process will need to setsid, open a pseudo-terminal
// if needed, and eventually exec shell.
// The parent process will wait for the result and
// send the return code back to our client.
// Fork a child root process
//
// The child process will need to setsid, open a pseudo-terminal
// if needed, and eventually exec shell.
// The parent process will wait for the result and
// send the return code back to our client.
if (int child = xfork(); child) {
ctx.info.reset();
if (int child = xfork(); child) {
ctx.info.reset();
// Wait result
LOGD("su: waiting child pid=[%d]\n", child);
int status, code;
// Wait result
LOGD("su: waiting child pid=[%d]\n", child);
int status, code;
if (waitpid(child, &status, 0) > 0)
code = WEXITSTATUS(status);
else
code = -1;
if (waitpid(child, &status, 0) > 0)
code = WEXITSTATUS(status);
else
code = -1;
LOGD("su: return code=[%d]\n", code);
write(client, &code, sizeof(code));
close(client);
return;
}
LOGD("su: return code=[%d]\n", code);
write(client, &code, sizeof(code));
close(client);
return;
}
LOGD("su: fork handler\n");
LOGD("su: fork handler\n");
// Abort upon any error occurred
log_cb.ex = exit;
// Abort upon any error occurred
log_cb.ex = exit;
// ack
write_int(client, 0);
// ack
write_int(client, 0);
// Become session leader
xsetsid();
// Become session leader
xsetsid();
// Get pts_slave
char *pts_slave = read_string(client);
// Get pts_slave
char *pts_slave = read_string(client);
// The FDs for each of the streams
int infd = recv_fd(client);
int outfd = recv_fd(client);
int errfd = recv_fd(client);
// The FDs for each of the streams
int infd = recv_fd(client);
int outfd = recv_fd(client);
int errfd = recv_fd(client);
if (pts_slave[0]) {
LOGD("su: pts_slave=[%s]\n", pts_slave);
// Check pts_slave file is owned by daemon_from_uid
struct stat st;
xstat(pts_slave, &st);
if (pts_slave[0]) {
LOGD("su: pts_slave=[%s]\n", pts_slave);
// Check pts_slave file is owned by daemon_from_uid
struct stat st;
xstat(pts_slave, &st);
// If caller is not root, ensure the owner of pts_slave is the caller
if(st.st_uid != ctx.info->uid && ctx.info->uid != 0)
LOGE("su: Wrong permission of pts_slave\n");
// If caller is not root, ensure the owner of pts_slave is the caller
if(st.st_uid != ctx.info->uid && ctx.info->uid != 0)
LOGE("su: Wrong permission of pts_slave\n");
// Opening the TTY has to occur after the
// fork() and setsid() so that it becomes
// our controlling TTY and not the daemon's
int ptsfd = xopen(pts_slave, O_RDWR);
// Opening the TTY has to occur after the
// fork() and setsid() so that it becomes
// our controlling TTY and not the daemon's
int ptsfd = xopen(pts_slave, O_RDWR);
if (infd < 0)
infd = ptsfd;
if (outfd < 0)
outfd = ptsfd;
if (errfd < 0)
errfd = ptsfd;
}
if (infd < 0)
infd = ptsfd;
if (outfd < 0)
outfd = ptsfd;
if (errfd < 0)
errfd = ptsfd;
}
free(pts_slave);
free(pts_slave);
// Swap out stdin, stdout, stderr
xdup2(infd, STDIN_FILENO);
xdup2(outfd, STDOUT_FILENO);
xdup2(errfd, STDERR_FILENO);
// Swap out stdin, stdout, stderr
xdup2(infd, STDIN_FILENO);
xdup2(outfd, STDOUT_FILENO);
xdup2(errfd, STDERR_FILENO);
// Unleash all streams from SELinux hell
setfilecon("/proc/self/fd/0", "u:object_r:" SEPOL_FILE_TYPE ":s0");
setfilecon("/proc/self/fd/1", "u:object_r:" SEPOL_FILE_TYPE ":s0");
setfilecon("/proc/self/fd/2", "u:object_r:" SEPOL_FILE_TYPE ":s0");
// Unleash all streams from SELinux hell
setfilecon("/proc/self/fd/0", "u:object_r:" SEPOL_FILE_TYPE ":s0");
setfilecon("/proc/self/fd/1", "u:object_r:" SEPOL_FILE_TYPE ":s0");
setfilecon("/proc/self/fd/2", "u:object_r:" SEPOL_FILE_TYPE ":s0");
close(infd);
close(outfd);
close(errfd);
close(client);
close(infd);
close(outfd);
close(errfd);
close(client);
// Handle namespaces
if (ctx.req.mount_master)
ctx.info->cfg[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL;
switch (ctx.info->cfg[SU_MNT_NS]) {
case NAMESPACE_MODE_GLOBAL:
LOGD("su: use global namespace\n");
break;
case NAMESPACE_MODE_REQUESTER:
LOGD("su: use namespace of pid=[%d]\n", ctx.pid);
if (switch_mnt_ns(ctx.pid))
LOGD("su: setns failed, fallback to global\n");
break;
case NAMESPACE_MODE_ISOLATE:
LOGD("su: use new isolated namespace\n");
switch_mnt_ns(ctx.pid);
xunshare(CLONE_NEWNS);
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
break;
}
// Handle namespaces
if (ctx.req.mount_master)
ctx.info->cfg[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL;
switch (ctx.info->cfg[SU_MNT_NS]) {
case NAMESPACE_MODE_GLOBAL:
LOGD("su: use global namespace\n");
break;
case NAMESPACE_MODE_REQUESTER:
LOGD("su: use namespace of pid=[%d]\n", ctx.pid);
if (switch_mnt_ns(ctx.pid))
LOGD("su: setns failed, fallback to global\n");
break;
case NAMESPACE_MODE_ISOLATE:
LOGD("su: use new isolated namespace\n");
switch_mnt_ns(ctx.pid);
xunshare(CLONE_NEWNS);
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
break;
}
const char *argv[] = { nullptr, nullptr, nullptr, nullptr };
const char *argv[] = { nullptr, nullptr, nullptr, nullptr };
argv[0] = ctx.req.login ? "-" : ctx.req.shell;
argv[0] = ctx.req.login ? "-" : ctx.req.shell;
if (ctx.req.command[0]) {
argv[1] = "-c";
argv[2] = ctx.req.command;
}
if (ctx.req.command[0]) {
argv[1] = "-c";
argv[2] = ctx.req.command;
}
// Setup environment
umask(022);
char path[32];
snprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid);
chdir(path);
snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
char buf[4096] = { 0 };
int fd = xopen(path, O_RDONLY);
read(fd, buf, sizeof(buf));
close(fd);
clearenv();
for (size_t pos = 0; buf[pos];) {
putenv(buf + pos);
pos += strlen(buf + pos) + 1;
}
if (!ctx.req.keepenv) {
struct passwd *pw;
pw = getpwuid(ctx.req.uid);
if (pw) {
setenv("HOME", pw->pw_dir, 1);
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
setenv("SHELL", ctx.req.shell, 1);
}
}
// Setup environment
umask(022);
char path[32];
snprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid);
chdir(path);
snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
char buf[4096] = { 0 };
int fd = xopen(path, O_RDONLY);
read(fd, buf, sizeof(buf));
close(fd);
clearenv();
for (size_t pos = 0; buf[pos];) {
putenv(buf + pos);
pos += strlen(buf + pos) + 1;
}
if (!ctx.req.keepenv) {
struct passwd *pw;
pw = getpwuid(ctx.req.uid);
if (pw) {
setenv("HOME", pw->pw_dir, 1);
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
setenv("SHELL", ctx.req.shell, 1);
}
}
// Unblock all signals
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
set_identity(ctx.req.uid);
execvp(ctx.req.shell, (char **) argv);
fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell, strerror(errno));
PLOGE("exec");
exit(EXIT_FAILURE);
// Unblock all signals
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
set_identity(ctx.req.uid);
execvp(ctx.req.shell, (char **) argv);
fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell, strerror(errno));
PLOGE("exec");
exit(EXIT_FAILURE);
}