2 * A simple gateway for set-uid scripts
4 * (c) 2013-2022 Martin Mares <mj@ucw.cz>
24 #define NONRET __attribute__((noreturn))
25 #define UNUSED __attribute__((unused))
28 #define DBG(...) printf(__VA_ARGS__)
30 #define DBG(...) do { } while (0)
33 static char program_name[PATH_MAX];
34 static char script_path[PATH_MAX];
38 static const char * const script_dirs[] = {
39 "/usr/local/lib/suidgw",
47 static char env_orig_uid[32], env_orig_gid[32];
49 static char *sanitized_env[] = {
50 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
56 static void NONRET die(const char *fmt, ...)
60 fprintf(stderr, "suidgw: ");
61 vfprintf(stderr, fmt, args);
66 static void sanitize_fds(void)
68 // Make sure that nobody is playing dirty games with our file descriptors
72 fd = open("/dev/null", O_RDWR);
74 die("Cannot open /dev/null: %m");
80 static void sanitize_env(void)
82 snprintf(env_orig_uid, sizeof(env_orig_uid), "ORIG_UID=%d", (int) getuid());
83 snprintf(env_orig_gid, sizeof(env_orig_gid), "ORIG_GID=%d", (int) getgid());
86 static void get_script_name(const char *arg0)
89 die("Program name must be given");
91 // If arg0 is a path, extract the last component
92 const char *p = strrchr(arg0, '/');
98 // Reject empty and oversized names
100 die("Script name is empty");
101 if (strlen(p) >= PATH_MAX)
102 die("Script name too long");
104 // Reject invalid characters
105 for (const char *q = p; *q; q++)
108 if (! (c >= 'a' && c <= 'z' ||
109 c >= 'A' && c <= 'Z' ||
110 c >= '0' && c <= '9' ||
114 die("Script name contains an invalid character 0x%02x", c);
117 // Reject "." and ".."
118 if (!strcmp(p, ".") || !strcmp(p, ".."))
119 die("Script name must not be `.' or `..'");
121 DBG("Program name: <%s>\n", p);
122 strcpy(program_name, p);
125 static void find_script(void)
127 for (int i = 0; script_dirs[i]; i++)
129 int len = snprintf(script_path, PATH_MAX, "%s/%s", script_dirs[i], program_name);
132 DBG("Trying <%s>\n", script_path);
133 if (access(script_path, X_OK) >= 0)
135 DBG("Found <%s>\n", script_path);
139 die("Cannot run %s: %m", script_path);
142 die("Unable to find script %s", program_name);
145 static void check_stat(void)
148 if (stat(script_path, &st) < 0)
149 die("Cannot stat %s: %m", script_path);
151 if (!S_ISREG(st.st_mode))
152 die("Not a regular file: %s", script_path);
153 if (!(st.st_mode & (S_ISUID | S_ISGID)))
154 die("Missing suid/sgid: %s", script_path);
156 use_uid = (st.st_mode & S_ISUID) ? st.st_uid : getuid();
157 use_gid = (st.st_mode & S_ISGID) ? st.st_gid : getgid();
158 DBG("Will use uid=%d gid=%d\n", (int) use_uid, (int) use_gid);
161 static void switch_ugid(void)
163 if (setresgid(use_gid, use_gid, use_gid) < 0)
164 die("Failed to set group id: %m");
166 if (setresuid(use_uid, use_uid, use_uid) < 0)
167 die("Failed to set user id: %m");
170 int main(int argc UNUSED, char **argv)
177 die("Must be run setuid");
179 struct passwd *pwd = getpwuid(getuid());
181 die("You don't exist");
183 openlog("suidgw", LOG_NDELAY | LOG_PID, LOG_AUTH);
185 get_script_name(argv[0]);
190 syslog(LOG_INFO, "User %s executes %s with uid=%d, gid=%d",
191 pwd->pw_name, script_path, (int) use_uid, (int) use_gid);
193 if (execve(script_path, argv, sanitized_env) < 0)
194 die("Cannot execute %s: %m", script_path);
196 die("This must never happen");