#include <errno.h>
#include <error.h>
+#include <getopt.h>
+#include <paths.h>
#include <pwd.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
free(clobberablegids);
}
-/*
- * SU can be given a specific command to exec. UID _must_ be
- * specified for this.
- *
- * Usage:
- * su 1000
- * su 1000 ls -l
- * or
- * su [uid[,gid[,group1]...] [cmd]]
- * E.g.
- * su 1000,shell,net_bw_acct,net_bw_stats id
- * will return
- * uid=1000(system) gid=2000(shell) groups=3006(net_bw_stats),3007(net_bw_acct)
- */
int main(int argc, char** argv) {
uid_t current_uid = getuid();
if (current_uid != AID_ROOT && current_uid != AID_SHELL) error(1, 0, "not allowed");
+ // Handle -h and --help.
+ ++argv;
+ if (*argv && (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0)) {
+ fprintf(stderr,
+ "usage: su [UID[,GID[,GID2]...]] [COMMAND [ARG...]]\n"
+ "\n"
+ "Switch to WHO (default 'root') and run the given command (default sh).\n"
+ "\n"
+ "where WHO is a comma-separated list of user, group,\n"
+ "and supplementary groups in that order.\n"
+ "\n");
+ return 0;
+ }
+
// The default user is root.
uid_t uid = 0;
gid_t gid = 0;
- // TODO: use getopt and support at least -- and --help.
-
// If there are any arguments, the first argument is the uid/gid/supplementary groups.
- if (argc >= 2) {
+ if (*argv) {
gid_t gids[10];
int gids_count = sizeof(gids)/sizeof(gids[0]);
- extract_uidgids(argv[1], &uid, &gid, gids, &gids_count);
+ extract_uidgids(*argv, &uid, &gid, gids, &gids_count);
if (gids_count) {
if (setgroups(gids_count, gids)) {
error(1, errno, "setgroups failed");
if (setgid(gid)) error(1, errno, "setgid failed");
if (setuid(uid)) error(1, errno, "setuid failed");
- // TODO: reset $PATH.
+ // Reset parts of the environment.
+ setenv("PATH", _PATH_DEFPATH, 1);
+ unsetenv("IFS");
+ struct passwd* pw = getpwuid(uid);
+ if (pw) {
+ setenv("LOGNAME", pw->pw_name, 1);
+ setenv("USER", pw->pw_name, 1);
+ } else {
+ unsetenv("LOGNAME");
+ unsetenv("USER");
+ }
// Set up the arguments for exec.
- char* exec_args[argc]; // Having too much space is fine.
- memset(exec_args, 0, sizeof(exec_args));
- // Skip "su" and copy any other args. We already skipped the optional uid above.
- ++argv;
- for (size_t i = 0; argv[i] != NULL; ++i) {
- exec_args[i] = argv[i];
+ char* exec_args[argc + 1]; // Having too much space is fine.
+ size_t i = 0;
+ for (; *argv != NULL; ++i) {
+ exec_args[i] = *argv++;
}
// Default to the standard shell.
- if (!exec_args[0]) exec_args[0] = "/system/bin/sh";
+ if (i == 0) exec_args[i++] = "/system/bin/sh";
+ exec_args[i] = NULL;
execvp(exec_args[0], exec_args);
error(1, errno, "failed to exec %s", exec_args[0]);