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 2 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, write to the Free Software
18   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19*/
20
21#include "includes.h"
22
23/* need to move this from here!! need some sleep ... */
24struct current_user current_user;
25
26/****************************************************************************
27This is a utility function of smbrun().
28****************************************************************************/
29
30static int setup_out_fd(void)
31{
32	int fd;
33	pstring path;
34
35	slprintf(path, sizeof(path)-1, "%s/smb.XXXXXX", tmpdir());
36
37	/* now create the file */
38	fd = smb_mkstemp(path);
39
40	if (fd == -1) {
41		DEBUG(0,("setup_out_fd: Failed to create file %s. (%s)\n",
42			path, strerror(errno) ));
43		return -1;
44	}
45
46	DEBUG(10,("setup_out_fd: Created tmp file %s\n", path ));
47
48	/* Ensure file only kept around by open fd. */
49	unlink(path);
50	return fd;
51}
52
53/****************************************************************************
54run a command being careful about uid/gid handling and putting the output in
55outfd (or discard it if outfd is NULL).
56****************************************************************************/
57
58static int smbrun_internal(const char *cmd, int *outfd, BOOL sanitize)
59{
60	pid_t pid;
61	uid_t uid = current_user.ut.uid;
62	gid_t gid = current_user.ut.gid;
63
64	/*
65	 * Lose any elevated privileges.
66	 */
67	drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
68	drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
69
70	/* point our stdout at the file we want output to go into */
71
72	if (outfd && ((*outfd = setup_out_fd()) == -1)) {
73		return -1;
74	}
75
76	/* in this method we will exec /bin/sh with the correct
77	   arguments, after first setting stdout to point at the file */
78
79	/*
80	 * We need to temporarily stop CatchChild from eating
81	 * SIGCLD signals as it also eats the exit status code. JRA.
82	 */
83
84	CatchChildLeaveStatus();
85
86	if ((pid=sys_fork()) < 0) {
87		DEBUG(0,("smbrun: fork failed with error %s\n", strerror(errno) ));
88		CatchChild();
89		if (outfd) {
90			close(*outfd);
91			*outfd = -1;
92		}
93		return errno;
94	}
95
96	if (pid) {
97		/*
98		 * Parent.
99		 */
100		int status=0;
101		pid_t wpid;
102
103
104		/* the parent just waits for the child to exit */
105		while((wpid = sys_waitpid(pid,&status,0)) < 0) {
106			if(errno == EINTR) {
107				errno = 0;
108				continue;
109			}
110			break;
111		}
112
113		CatchChild();
114
115		if (wpid != pid) {
116			DEBUG(2,("waitpid(%d) : %s\n",(int)pid,strerror(errno)));
117			if (outfd) {
118				close(*outfd);
119				*outfd = -1;
120			}
121			return -1;
122		}
123
124		/* Reset the seek pointer. */
125		if (outfd) {
126			sys_lseek(*outfd, 0, SEEK_SET);
127		}
128
129#if defined(WIFEXITED) && defined(WEXITSTATUS)
130		if (WIFEXITED(status)) {
131			return WEXITSTATUS(status);
132		}
133#endif
134
135		return status;
136	}
137
138	CatchChild();
139
140	/* we are in the child. we exec /bin/sh to do the work for us. we
141	   don't directly exec the command we want because it may be a
142	   pipeline or anything else the config file specifies */
143
144	/* point our stdout at the file we want output to go into */
145	if (outfd) {
146		close(1);
147		if (sys_dup2(*outfd,1) != 1) {
148			DEBUG(2,("Failed to create stdout file descriptor\n"));
149			close(*outfd);
150			exit(80);
151		}
152	}
153
154	/* now completely lose our privileges. This is a fairly paranoid
155	   way of doing it, but it does work on all systems that I know of */
156
157	become_user_permanently(uid, gid);
158
159	if (getuid() != uid || geteuid() != uid ||
160	    getgid() != gid || getegid() != gid) {
161		/* we failed to lose our privileges - do not execute
162                   the command */
163		exit(81); /* we can't print stuff at this stage,
164			     instead use exit codes for debugging */
165	}
166
167#ifndef __INSURE__
168	/* close all other file descriptors, leaving only 0, 1 and 2. 0 and
169	   2 point to /dev/null from the startup code */
170	{
171	int fd;
172	for (fd=3;fd<256;fd++) close(fd);
173	}
174#endif
175
176	{
177		const char *newcmd = sanitize ? escape_shell_string(cmd) : cmd;
178		if (!newcmd) {
179			exit(82);
180		}
181		execl("/bin/sh","sh","-c",newcmd,NULL);
182	}
183
184	/* not reached */
185	exit(83);
186	return 1;
187}
188
189/****************************************************************************
190 Use only in known safe shell calls (printing).
191****************************************************************************/
192
193int smbrun_no_sanitize(const char *cmd, int *outfd)
194{
195	return smbrun_internal(cmd, outfd, False);
196}
197
198/****************************************************************************
199 By default this now sanitizes shell expansion.
200****************************************************************************/
201
202int smbrun(const char *cmd, int *outfd)
203{
204	return smbrun_internal(cmd, outfd, True);
205}
206
207/****************************************************************************
208run a command being careful about uid/gid handling and putting the output in
209outfd (or discard it if outfd is NULL).
210sends the provided secret to the child stdin.
211****************************************************************************/
212
213int smbrunsecret(const char *cmd, const char *secret)
214{
215	pid_t pid;
216	uid_t uid = current_user.ut.uid;
217	gid_t gid = current_user.ut.gid;
218	int ifd[2];
219
220	/*
221	 * Lose any elevated privileges.
222	 */
223	drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
224	drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
225
226	/* build up an input pipe */
227	if(pipe(ifd)) {
228		return -1;
229	}
230
231	/* in this method we will exec /bin/sh with the correct
232	   arguments, after first setting stdout to point at the file */
233
234	/*
235	 * We need to temporarily stop CatchChild from eating
236	 * SIGCLD signals as it also eats the exit status code. JRA.
237	 */
238
239	CatchChildLeaveStatus();
240
241	if ((pid=sys_fork()) < 0) {
242		DEBUG(0, ("smbrunsecret: fork failed with error %s\n", strerror(errno)));
243		CatchChild();
244		return errno;
245    	}
246
247	if (pid) {
248		/*
249		 * Parent.
250		 */
251		int status = 0;
252		pid_t wpid;
253		size_t towrite;
254		ssize_t wrote;
255
256		close(ifd[0]);
257		/* send the secret */
258		towrite = strlen(secret);
259		wrote = write(ifd[1], secret, towrite);
260		if ( wrote != towrite ) {
261		    DEBUG(0,("smbrunsecret: wrote %ld of %lu bytes\n",(long)wrote,(unsigned long)towrite));
262		}
263		fsync(ifd[1]);
264		close(ifd[1]);
265
266		/* the parent just waits for the child to exit */
267		while((wpid = sys_waitpid(pid, &status, 0)) < 0) {
268			if(errno == EINTR) {
269				errno = 0;
270				continue;
271			}
272			break;
273		}
274
275		CatchChild();
276
277		if (wpid != pid) {
278			DEBUG(2, ("waitpid(%d) : %s\n", (int)pid, strerror(errno)));
279			return -1;
280		}
281
282#if defined(WIFEXITED) && defined(WEXITSTATUS)
283		if (WIFEXITED(status)) {
284			return WEXITSTATUS(status);
285		}
286#endif
287
288		return status;
289	}
290
291	CatchChild();
292
293	/* we are in the child. we exec /bin/sh to do the work for us. we
294	   don't directly exec the command we want because it may be a
295	   pipeline or anything else the config file specifies */
296
297	close(ifd[1]);
298	close(0);
299	if (sys_dup2(ifd[0], 0) != 0) {
300		DEBUG(2,("Failed to create stdin file descriptor\n"));
301		close(ifd[0]);
302		exit(80);
303	}
304
305	/* now completely lose our privileges. This is a fairly paranoid
306	   way of doing it, but it does work on all systems that I know of */
307
308	become_user_permanently(uid, gid);
309
310	if (getuid() != uid || geteuid() != uid ||
311	    getgid() != gid || getegid() != gid) {
312		/* we failed to lose our privileges - do not execute
313                   the command */
314		exit(81); /* we can't print stuff at this stage,
315			     instead use exit codes for debugging */
316	}
317
318#ifndef __INSURE__
319	/* close all other file descriptors, leaving only 0, 1 and 2. 0 and
320	   2 point to /dev/null from the startup code */
321	{
322		int fd;
323		for (fd = 3; fd < 256; fd++) close(fd);
324	}
325#endif
326
327	execl("/bin/sh", "sh", "-c", cmd, NULL);
328
329	/* not reached */
330	exit(82);
331	return 1;
332}
333