diff options
Diffstat (limited to 'src/runas.c')
-rw-r--r-- | src/runas.c | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/src/runas.c b/src/runas.c new file mode 100644 index 0000000..9d2a6c1 --- /dev/null +++ b/src/runas.c | |||
@@ -0,0 +1,191 @@ | |||
1 | /* This file is part of genrc | ||
2 | Copyryght (C) 2018 Sergey Poznyakoff | ||
3 | License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> | ||
4 | This is free software: you are free to change and redistribute it. | ||
5 | There is NO WARRANTY, to the extent permitted by law. | ||
6 | */ | ||
7 | #include "genrc.h" | ||
8 | #include <pwd.h> | ||
9 | #include <grp.h> | ||
10 | |||
11 | static void | ||
12 | addgid(gid_t **pgv, size_t *pgc, size_t *pgi, gid_t gid) | ||
13 | { | ||
14 | gid_t *gv = *pgv; | ||
15 | size_t gc = *pgc; | ||
16 | size_t gi = *pgi; | ||
17 | |||
18 | if (gi == gc) | ||
19 | gv = x2nrealloc(gv, &gc, sizeof(gv[0])); | ||
20 | |||
21 | gv[gi++] = gid; | ||
22 | *pgv = gv; | ||
23 | *pgc = gc; | ||
24 | *pgi = gi; | ||
25 | } | ||
26 | |||
27 | static int | ||
28 | member(gid_t *gv, size_t gc, gid_t gid) | ||
29 | { | ||
30 | size_t i; | ||
31 | |||
32 | for (i = 0; i < gc; i++) | ||
33 | if (gv[i] == gid) | ||
34 | return 1; | ||
35 | return 0; | ||
36 | } | ||
37 | |||
38 | static size_t | ||
39 | get_user_groups(const char *user, gid_t **pgv, size_t *pgc) | ||
40 | { | ||
41 | struct group *gr; | ||
42 | size_t gi = 0; | ||
43 | |||
44 | setgrent(); | ||
45 | while ((gr = getgrent())) { | ||
46 | char **p; | ||
47 | for (p = gr->gr_mem; *p; p++) | ||
48 | if (strcmp(*p, user) == 0) | ||
49 | addgid(pgv, pgc, &gi, gr->gr_gid); | ||
50 | } | ||
51 | endgrent(); | ||
52 | return gi; | ||
53 | } | ||
54 | |||
55 | static gid_t | ||
56 | strtogid(char const *str) | ||
57 | { | ||
58 | struct group *gr; | ||
59 | |||
60 | if (str[0] == '+') { | ||
61 | char *end; | ||
62 | unsigned long n; | ||
63 | |||
64 | errno = 0; | ||
65 | n = strtoul(str, &end, 10); | ||
66 | if (errno || *end) { | ||
67 | genrc_error("invalid group name %s", str); | ||
68 | exit(1); | ||
69 | } | ||
70 | |||
71 | gr = getgrgid(n); | ||
72 | } else | ||
73 | gr = getgrnam(str); | ||
74 | |||
75 | if (!gr) { | ||
76 | genrc_error("%s: no such group", str); | ||
77 | exit(1); | ||
78 | } | ||
79 | |||
80 | return gr->gr_gid; | ||
81 | } | ||
82 | |||
83 | void | ||
84 | runas(void) | ||
85 | { | ||
86 | struct passwd *pw; | ||
87 | uid_t uid; | ||
88 | gid_t gid; | ||
89 | gid_t *gv; | ||
90 | size_t gc, gn; | ||
91 | char const *runas_user; | ||
92 | char const *runas_groups; | ||
93 | |||
94 | runas_user = getenv("GENRC_USER"); | ||
95 | runas_groups = getenv("GENRC_GROUP"); | ||
96 | |||
97 | if (!(runas_user || runas_groups)) | ||
98 | return; | ||
99 | if (getuid() != 0) { | ||
100 | genrc_error("not root: can't switch to user privileges"); | ||
101 | exit(1); | ||
102 | } | ||
103 | |||
104 | if (!runas_user) { | ||
105 | pw = getpwuid(0); | ||
106 | runas_user = "root"; | ||
107 | } else if (runas_user[0] == '+') { | ||
108 | char *end; | ||
109 | unsigned long n; | ||
110 | |||
111 | errno = 0; | ||
112 | n = strtoul(runas_user + 1, &end, 10); | ||
113 | if (errno || *end) { | ||
114 | genrc_error("invalid user name %s", runas_user); | ||
115 | exit(1); | ||
116 | } | ||
117 | |||
118 | pw = getpwuid(n); | ||
119 | } else | ||
120 | pw = getpwnam(runas_user); | ||
121 | |||
122 | if (!pw) { | ||
123 | genrc_error("%s: no such user", runas_user); | ||
124 | exit(1); | ||
125 | } | ||
126 | runas_user = pw->pw_name; | ||
127 | |||
128 | uid = pw->pw_uid; | ||
129 | gid = pw->pw_gid; | ||
130 | |||
131 | gv = NULL; | ||
132 | gc = 0; | ||
133 | |||
134 | if (runas_groups && runas_groups[0]) { | ||
135 | struct wordsplit ws; | ||
136 | size_t i; | ||
137 | |||
138 | ws.ws_delim = ","; | ||
139 | ws.ws_error = genrc_error; | ||
140 | if (wordsplit(runas_groups, &ws, | ||
141 | WRDSF_NOCMD | ||
142 | | WRDSF_NOVAR | ||
143 | | WRDSF_DELIM | ||
144 | | WRDSF_ENOMEMABRT | ||
145 | | WRDSF_SHOWERR | ||
146 | | WRDSF_ERROR)) | ||
147 | exit(1); | ||
148 | |||
149 | if (ws.ws_wordc == 0) { | ||
150 | genrc_error("bad group list: '%s'", runas_groups); | ||
151 | exit(1); | ||
152 | } | ||
153 | |||
154 | gid = strtogid(ws.ws_wordv[0]); | ||
155 | for (i = 1; i < ws.ws_wordc; i++) | ||
156 | addgid(&gv, &gc, &gn, strtogid(ws.ws_wordv[i])); | ||
157 | |||
158 | wordsplit_free(&ws); | ||
159 | } | ||
160 | |||
161 | if (gc == 0) { | ||
162 | gn = get_user_groups(runas_user, &gv, &gc); | ||
163 | if (!member(gv, gn, gid)) | ||
164 | addgid(&gv, &gc, &gn, gid); | ||
165 | } | ||
166 | |||
167 | /* Reset group permissions */ | ||
168 | if (setgroups(gn, gv)) { | ||
169 | system_error(errno, "setgroups"); | ||
170 | exit(1); | ||
171 | } | ||
172 | free(gv); | ||
173 | |||
174 | if (gid) { | ||
175 | /* Switch to the user's gid. */ | ||
176 | if (setgid(gid)) { | ||
177 | system_error(errno, "setgid(%lu) failed", | ||
178 | (unsigned long) gid); | ||
179 | exit(1); | ||
180 | } | ||
181 | } | ||
182 | |||
183 | /* Now reset uid */ | ||
184 | if (uid) { | ||
185 | if (setuid(uid)) { | ||
186 | system_error(errno, "setuid(%lu) failed: %s", | ||
187 | (unsigned long) uid); | ||
188 | exit(1); | ||
189 | } | ||
190 | } | ||
191 | } | ||