]> mj.ucw.cz Git - suidgw.git/blob - suidgw.c
Initial version
[suidgw.git] / suidgw.c
1 /*
2  *  A simple gateway for set-uid scripts
3  *
4  *  (c) 2013 Martin Mares <mj@ucw.cz>
5  */
6
7 #undef DEBUG
8
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <pwd.h>
13 #include <stdarg.h>
14 #include <stdbool.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <syslog.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21
22 #define NONRET __attribute__((noreturn))
23 #define UNUSED __attribute__((unused))
24
25 #ifdef DEBUG
26 #define DBG(...) printf(__VA_ARGS__)
27 #else
28 #define DBG(...) do { } while (0)
29 #endif
30
31 static char program_name[PATH_MAX];
32 static char script_path[PATH_MAX];
33 static uid_t use_uid;
34 static gid_t use_gid;
35
36 static const char * const script_dirs[] = {
37   "/usr/local/lib/suidgw",
38   "/usr/lib/suidgw",
39 #ifdef DEBUG
40   "./scripts",
41 #endif
42   NULL
43 };
44
45 static char *sanitized_env[] = {
46   "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
47   NULL
48 };
49
50 static void NONRET die(const char *fmt, ...)
51 {
52   va_list args;
53   va_start(args, fmt);
54   fprintf(stderr, "suidgw: ");
55   vfprintf(stderr, fmt, args);
56   fputc('\n', stderr);
57   exit(1);
58 }
59
60 static void sanitize_fds(void)
61 {
62   // Make sure that nobody is playing dirty games with our file descriptors
63   int fd;
64   do
65     {
66       fd = open("/dev/null", O_RDWR);
67       if (fd < 0)
68         die("Cannot open /dev/null: %m");
69     }
70   while (fd <= 2);
71   close(fd);
72 }
73
74 static bool get_program_name(const char *arg0)
75 {
76   // If arg0 is a path, extract the last component
77   const char *p = strrchr(arg0, '/');
78   if (p)
79     p++;
80   else
81     p = arg0;
82
83   // Reject empty and oversized names
84   if (!p[0] || strlen(p) >= PATH_MAX)
85     return 0;
86
87   // Reject invalid characters
88   for (const char *q = p; *q; q++)
89     {
90       int c = *q;
91       if (! (c >= 'a' && c <= 'z' ||
92              c >= 'A' && c <= 'Z' ||
93              c >= '0' && c <= '9' ||
94              c == '-' ||
95              c == '_'))
96         return 0;
97     }
98
99   DBG("Program name: <%s>\n", p);
100   strcpy(program_name, p);
101   return 1;
102 }
103
104 static void find_script(void)
105 {
106   for (int i = 0; script_dirs[i]; i++)
107     {
108       int len = snprintf(script_path, PATH_MAX, "%s/%s", script_dirs[i], program_name);
109       if (len >= PATH_MAX)
110         continue;
111       DBG("Trying <%s>\n", script_path);
112       if (access(script_path, X_OK) >= 0)
113         {
114           DBG("Found <%s>\n", script_path);
115           return;
116         }
117       if (errno != ENOENT)
118         die("Cannot run %s: %m", script_path);
119     }
120
121   die("Unable to find script %s", program_name);
122 }
123
124 static void check_stat(void)
125 {
126   struct stat st;
127   if (stat(script_path, &st) < 0)
128     die("Cannot stat %s: %m", script_path);
129
130   if (!S_ISREG(st.st_mode))
131     die("Not a regular file: %s", script_path);
132   if (!(st.st_mode & (S_ISUID | S_ISGID)))
133     die("Missing suid/sgid: %s", script_path);
134
135   use_uid = (st.st_mode & S_ISUID) ? st.st_uid : getuid();
136   use_gid = (st.st_mode & S_ISGID) ? st.st_gid : getgid();
137   DBG("Will use uid=%d gid=%d\n", (int) use_uid, (int) use_gid);
138 }
139
140 static void switch_ugid(void)
141 {
142   if (setegid(use_gid) < 0)
143     die("Failed to set group id: %m");
144
145   if (seteuid(use_uid) < 0)
146     die("Failed to set user id: %m");
147 }
148
149 int main(int argc UNUSED, char **argv)
150 {
151   sanitize_fds();
152
153   if (geteuid())
154     die("Must be run setuid");
155
156   struct passwd *pwd = getpwuid(getuid());
157   if (!pwd)
158     die("You don't exist");
159
160   openlog("suidgw", LOG_NDELAY | LOG_PID, LOG_AUTH);
161
162   if (!get_program_name(argv[0]))
163     die("Unable to parse program name %s", argv[0]);
164
165   find_script();
166   check_stat();
167   switch_ugid();
168
169   syslog(LOG_INFO, "User %s executes %s with uid=%d, gid=%d",
170     pwd->pw_name, script_path, (int) use_uid, (int) use_gid);
171
172   if (execve(script_path, argv, sanitized_env) < 0)
173     die("Cannot execute %s: %m", script_path);
174
175   die("This must never happen");
176 }