/src/samba/source3/lib/smbrun.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Unix SMB/CIFS implementation. |
3 | | run a command as a specified user |
4 | | Copyright (C) Andrew Tridgell 1992-1998 |
5 | | |
6 | | This program is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 3 of the License, or |
9 | | (at your option) any later version. |
10 | | |
11 | | This program is distributed in the hope that it will be useful, |
12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | GNU General Public License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License |
17 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include "includes.h" |
21 | | #include "system/filesys.h" |
22 | | |
23 | | /* need to move this from here!! need some sleep ... */ |
24 | | struct current_user current_user; |
25 | | |
26 | | /**************************************************************************** |
27 | | This is a utility function of smbrun(). |
28 | | ****************************************************************************/ |
29 | | |
30 | | static int setup_out_fd(void) |
31 | 0 | { |
32 | 0 | int fd; |
33 | 0 | TALLOC_CTX *ctx = talloc_stackframe(); |
34 | 0 | char *path = NULL; |
35 | 0 | mode_t mask; |
36 | |
|
37 | 0 | path = talloc_asprintf(ctx, |
38 | 0 | "%s/smb.XXXXXX", |
39 | 0 | tmpdir()); |
40 | 0 | if (!path) { |
41 | 0 | TALLOC_FREE(ctx); |
42 | 0 | errno = ENOMEM; |
43 | 0 | return -1; |
44 | 0 | } |
45 | | |
46 | | /* now create the file */ |
47 | 0 | mask = umask(S_IRWXO | S_IRWXG); |
48 | 0 | fd = mkstemp(path); |
49 | 0 | umask(mask); |
50 | |
|
51 | 0 | if (fd == -1) { |
52 | 0 | DEBUG(0,("setup_out_fd: Failed to create file %s. (%s)\n", |
53 | 0 | path, strerror(errno) )); |
54 | 0 | TALLOC_FREE(ctx); |
55 | 0 | return -1; |
56 | 0 | } |
57 | | |
58 | 0 | DEBUG(10,("setup_out_fd: Created tmp file %s\n", path )); |
59 | | |
60 | | /* Ensure file only kept around by open fd. */ |
61 | 0 | unlink(path); |
62 | 0 | TALLOC_FREE(ctx); |
63 | 0 | return fd; |
64 | 0 | } |
65 | | |
66 | | /**************************************************************************** |
67 | | run a command being careful about uid/gid handling and putting the output in |
68 | | outfd (or discard it if outfd is NULL). |
69 | | ****************************************************************************/ |
70 | | |
71 | | static int smbrun_internal(const char *cmd, int *outfd, bool sanitize, |
72 | | char * const *env) |
73 | 0 | { |
74 | 0 | pid_t pid; |
75 | 0 | uid_t uid = current_user.ut.uid; |
76 | 0 | gid_t gid = current_user.ut.gid; |
77 | 0 | void (*saved_handler)(int); |
78 | | |
79 | | /* |
80 | | * Lose any elevated privileges. |
81 | | */ |
82 | 0 | drop_effective_capability(KERNEL_OPLOCK_CAPABILITY); |
83 | 0 | drop_effective_capability(DMAPI_ACCESS_CAPABILITY); |
84 | | |
85 | | /* point our stdout at the file we want output to go into */ |
86 | |
|
87 | 0 | if (outfd && ((*outfd = setup_out_fd()) == -1)) { |
88 | 0 | return -1; |
89 | 0 | } |
90 | | |
91 | | /* in this method we will exec /bin/sh with the correct |
92 | | arguments, after first setting stdout to point at the file */ |
93 | | |
94 | | /* |
95 | | * We need to temporarily stop CatchChild from eating |
96 | | * SIGCLD signals as it also eats the exit status code. JRA. |
97 | | */ |
98 | | |
99 | 0 | saved_handler = CatchChildLeaveStatus(); |
100 | | |
101 | 0 | if ((pid=fork()) < 0) { |
102 | 0 | DEBUG(0,("smbrun: fork failed with error %s\n", strerror(errno) )); |
103 | 0 | (void)CatchSignal(SIGCLD, saved_handler); |
104 | 0 | if (outfd) { |
105 | 0 | close(*outfd); |
106 | 0 | *outfd = -1; |
107 | 0 | } |
108 | 0 | return errno; |
109 | 0 | } |
110 | | |
111 | 0 | if (pid) { |
112 | | /* |
113 | | * Parent. |
114 | | */ |
115 | 0 | int status=0; |
116 | 0 | pid_t wpid; |
117 | | |
118 | | |
119 | | /* the parent just waits for the child to exit */ |
120 | 0 | while((wpid = waitpid(pid,&status,0)) < 0) { |
121 | 0 | if(errno == EINTR) { |
122 | 0 | errno = 0; |
123 | 0 | continue; |
124 | 0 | } |
125 | 0 | break; |
126 | 0 | } |
127 | |
|
128 | 0 | (void)CatchSignal(SIGCLD, saved_handler); |
129 | |
|
130 | 0 | if (wpid != pid) { |
131 | 0 | DEBUG(2,("waitpid(%d) : %s\n",(int)pid,strerror(errno))); |
132 | 0 | if (outfd) { |
133 | 0 | close(*outfd); |
134 | 0 | *outfd = -1; |
135 | 0 | } |
136 | 0 | return -1; |
137 | 0 | } |
138 | | |
139 | | /* Reset the seek pointer. */ |
140 | 0 | if (outfd) { |
141 | 0 | lseek(*outfd, 0, SEEK_SET); |
142 | 0 | } |
143 | |
|
144 | 0 | #if defined(WIFEXITED) && defined(WEXITSTATUS) |
145 | 0 | if (WIFEXITED(status)) { |
146 | 0 | return WEXITSTATUS(status); |
147 | 0 | } |
148 | 0 | #endif |
149 | | |
150 | 0 | return status; |
151 | 0 | } |
152 | | |
153 | 0 | (void)CatchChild(); |
154 | | |
155 | | /* we are in the child. we exec /bin/sh to do the work for us. we |
156 | | don't directly exec the command we want because it may be a |
157 | | pipeline or anything else the config file specifies */ |
158 | | |
159 | | /* point our stdout at the file we want output to go into */ |
160 | 0 | if (outfd) { |
161 | 0 | close(1); |
162 | 0 | if (dup2(*outfd,1) != 1) { |
163 | 0 | DEBUG(2,("Failed to create stdout file descriptor\n")); |
164 | 0 | close(*outfd); |
165 | 0 | exit(80); |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | /* now completely lose our privileges. This is a fairly paranoid |
170 | | way of doing it, but it does work on all systems that I know of */ |
171 | | |
172 | 0 | become_user_permanently(uid, gid); |
173 | |
|
174 | 0 | if (!non_root_mode()) { |
175 | 0 | if (getuid() != uid || geteuid() != uid || |
176 | 0 | getgid() != gid || getegid() != gid) { |
177 | | /* we failed to lose our privileges - do not execute |
178 | | the command */ |
179 | 0 | exit(81); /* we can't print stuff at this stage, |
180 | | instead use exit codes for debugging */ |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | | /* close all other file descriptors, leaving only 0, 1 and 2. 0 and |
185 | | 2 point to /dev/null from the startup code */ |
186 | 0 | closefrom(3); |
187 | |
|
188 | 0 | { |
189 | 0 | char *newcmd = NULL; |
190 | 0 | if (sanitize) { |
191 | 0 | newcmd = escape_shell_string(cmd); |
192 | 0 | if (!newcmd) |
193 | 0 | exit(82); |
194 | 0 | } |
195 | | |
196 | 0 | if (env != NULL) { |
197 | 0 | execle("/bin/sh","sh","-c", |
198 | 0 | newcmd ? (const char *)newcmd : cmd, NULL, |
199 | 0 | env); |
200 | 0 | } else { |
201 | 0 | execl("/bin/sh","sh","-c", |
202 | 0 | newcmd ? (const char *)newcmd : cmd, NULL); |
203 | 0 | } |
204 | |
|
205 | 0 | SAFE_FREE(newcmd); |
206 | 0 | } |
207 | | |
208 | | /* not reached */ |
209 | 0 | exit(83); |
210 | 0 | return 1; |
211 | 0 | } |
212 | | |
213 | | /**************************************************************************** |
214 | | Use only in known safe shell calls (printing). |
215 | | ****************************************************************************/ |
216 | | |
217 | | int smbrun_no_sanitize(const char *cmd, int *outfd, char * const *env) |
218 | 0 | { |
219 | 0 | return smbrun_internal(cmd, outfd, false, env); |
220 | 0 | } |
221 | | |
222 | | /**************************************************************************** |
223 | | By default this now sanitizes shell expansion. |
224 | | ****************************************************************************/ |
225 | | |
226 | | int smbrun(const char *cmd, int *outfd, char * const *env) |
227 | 0 | { |
228 | 0 | return smbrun_internal(cmd, outfd, true, env); |
229 | 0 | } |
230 | | |
231 | | /**************************************************************************** |
232 | | run a command being careful about uid/gid handling and putting the output in |
233 | | outfd (or discard it if outfd is NULL). |
234 | | sends the provided secret to the child stdin. |
235 | | ****************************************************************************/ |
236 | | |
237 | | int smbrunsecret(const char *cmd, const char *secret) |
238 | 0 | { |
239 | 0 | pid_t pid; |
240 | 0 | uid_t uid = current_user.ut.uid; |
241 | 0 | gid_t gid = current_user.ut.gid; |
242 | 0 | int ifd[2]; |
243 | 0 | void (*saved_handler)(int); |
244 | | |
245 | | /* |
246 | | * Lose any elevated privileges. |
247 | | */ |
248 | 0 | drop_effective_capability(KERNEL_OPLOCK_CAPABILITY); |
249 | 0 | drop_effective_capability(DMAPI_ACCESS_CAPABILITY); |
250 | | |
251 | | /* build up an input pipe */ |
252 | 0 | if(pipe(ifd)) { |
253 | 0 | return -1; |
254 | 0 | } |
255 | | |
256 | | /* in this method we will exec /bin/sh with the correct |
257 | | arguments, after first setting stdout to point at the file */ |
258 | | |
259 | | /* |
260 | | * We need to temporarily stop CatchChild from eating |
261 | | * SIGCLD signals as it also eats the exit status code. JRA. |
262 | | */ |
263 | | |
264 | 0 | saved_handler = CatchChildLeaveStatus(); |
265 | | |
266 | 0 | if ((pid=fork()) < 0) { |
267 | 0 | DEBUG(0, ("smbrunsecret: fork failed with error %s\n", strerror(errno))); |
268 | 0 | (void)CatchSignal(SIGCLD, saved_handler); |
269 | 0 | return errno; |
270 | 0 | } |
271 | | |
272 | 0 | if (pid) { |
273 | | /* |
274 | | * Parent. |
275 | | */ |
276 | 0 | int status = 0; |
277 | 0 | pid_t wpid; |
278 | 0 | size_t towrite; |
279 | 0 | ssize_t wrote; |
280 | | |
281 | 0 | close(ifd[0]); |
282 | | /* send the secret */ |
283 | 0 | towrite = strlen(secret); |
284 | 0 | wrote = write(ifd[1], secret, towrite); |
285 | 0 | if ( wrote != towrite ) { |
286 | 0 | DEBUG(0,("smbrunsecret: wrote %ld of %lu bytes\n",(long)wrote,(unsigned long)towrite)); |
287 | 0 | } |
288 | 0 | fsync(ifd[1]); |
289 | 0 | close(ifd[1]); |
290 | | |
291 | | /* the parent just waits for the child to exit */ |
292 | 0 | while((wpid = waitpid(pid, &status, 0)) < 0) { |
293 | 0 | if(errno == EINTR) { |
294 | 0 | errno = 0; |
295 | 0 | continue; |
296 | 0 | } |
297 | 0 | break; |
298 | 0 | } |
299 | |
|
300 | 0 | (void)CatchSignal(SIGCLD, saved_handler); |
301 | |
|
302 | 0 | if (wpid != pid) { |
303 | 0 | DEBUG(2, ("waitpid(%d) : %s\n", (int)pid, strerror(errno))); |
304 | 0 | return -1; |
305 | 0 | } |
306 | | |
307 | 0 | #if defined(WIFEXITED) && defined(WEXITSTATUS) |
308 | 0 | if (WIFEXITED(status)) { |
309 | 0 | return WEXITSTATUS(status); |
310 | 0 | } |
311 | 0 | #endif |
312 | | |
313 | 0 | return status; |
314 | 0 | } |
315 | | |
316 | 0 | (void)CatchChild(); |
317 | | |
318 | | /* we are in the child. we exec /bin/sh to do the work for us. we |
319 | | don't directly exec the command we want because it may be a |
320 | | pipeline or anything else the config file specifies */ |
321 | | |
322 | 0 | close(ifd[1]); |
323 | 0 | close(0); |
324 | 0 | if (dup2(ifd[0], 0) != 0) { |
325 | 0 | DEBUG(2,("Failed to create stdin file descriptor\n")); |
326 | 0 | close(ifd[0]); |
327 | 0 | exit(80); |
328 | 0 | } |
329 | | |
330 | | /* now completely lose our privileges. This is a fairly paranoid |
331 | | way of doing it, but it does work on all systems that I know of */ |
332 | | |
333 | 0 | become_user_permanently(uid, gid); |
334 | |
|
335 | 0 | if (!non_root_mode()) { |
336 | 0 | if (getuid() != uid || geteuid() != uid || |
337 | 0 | getgid() != gid || getegid() != gid) { |
338 | | /* we failed to lose our privileges - do not execute |
339 | | the command */ |
340 | 0 | exit(81); /* we can't print stuff at this stage, |
341 | | instead use exit codes for debugging */ |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | | /* close all other file descriptors, leaving only 0, 1 and 2. 0 and |
346 | | 2 point to /dev/null from the startup code */ |
347 | 0 | closefrom(3); |
348 | |
|
349 | 0 | execl("/bin/sh", "sh", "-c", cmd, NULL); |
350 | | |
351 | | /* not reached */ |
352 | 0 | exit(82); |
353 | 0 | return 1; |
354 | 0 | } |