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
58int smbrun(char *cmd, int *outfd)
59{
60	pid_t pid;
61	uid_t uid = current_user.uid;
62	gid_t gid = current_user.gid;
63
64	/*
65	 * Lose any kernel oplock capabilities we may have.
66	 */
67	oplock_set_capability(False, False);
68
69	/* point our stdout at the file we want output to go into */
70
71	if (outfd && ((*outfd = setup_out_fd()) == -1)) {
72		return -1;
73	}
74
75	/* in this method we will exec /bin/sh with the correct
76	   arguments, after first setting stdout to point at the file */
77
78	/*
79	 * We need to temporarily stop CatchChild from eating
80	 * SIGCLD signals as it also eats the exit status code. JRA.
81	 */
82
83	CatchChildLeaveStatus();
84
85	if ((pid=sys_fork()) < 0) {
86		DEBUG(0,("smbrun: fork failed with error %s\n", strerror(errno) ));
87		CatchChild();
88		if (outfd) {
89			close(*outfd);
90			*outfd = -1;
91		}
92		return errno;
93	}
94
95	if (pid) {
96		/*
97		 * Parent.
98		 */
99		int status=0;
100		pid_t wpid;
101
102
103		/* the parent just waits for the child to exit */
104		while((wpid = sys_waitpid(pid,&status,0)) < 0) {
105			if(errno == EINTR) {
106				errno = 0;
107				continue;
108			}
109			break;
110		}
111
112		CatchChild();
113
114		if (wpid != pid) {
115			DEBUG(2,("waitpid(%d) : %s\n",(int)pid,strerror(errno)));
116			if (outfd) {
117				close(*outfd);
118				*outfd = -1;
119			}
120			return -1;
121		}
122
123		/* Reset the seek pointer. */
124		if (outfd) {
125			sys_lseek(*outfd, 0, SEEK_SET);
126		}
127
128#if defined(WIFEXITED) && defined(WEXITSTATUS)
129		if (WIFEXITED(status)) {
130			return WEXITSTATUS(status);
131		}
132#endif
133
134		return status;
135	}
136
137	CatchChild();
138
139	/* we are in the child. we exec /bin/sh to do the work for us. we
140	   don't directly exec the command we want because it may be a
141	   pipeline or anything else the config file specifies */
142
143	/* point our stdout at the file we want output to go into */
144	if (outfd) {
145		close(1);
146		if (sys_dup2(*outfd,1) != 1) {
147			DEBUG(2,("Failed to create stdout file descriptor\n"));
148			close(*outfd);
149			exit(80);
150		}
151	}
152
153	/* now completely lose our privileges. This is a fairly paranoid
154	   way of doing it, but it does work on all systems that I know of */
155
156	become_user_permanently(uid, gid);
157
158	if (getuid() != uid || geteuid() != uid ||
159	    getgid() != gid || getegid() != gid) {
160		/* we failed to lose our privileges - do not execute
161                   the command */
162		exit(81); /* we can't print stuff at this stage,
163			     instead use exit codes for debugging */
164	}
165
166#ifndef __INSURE__
167	/* close all other file descriptors, leaving only 0, 1 and 2. 0 and
168	   2 point to /dev/null from the startup code */
169	{
170	int fd;
171	for (fd=3;fd<256;fd++) close(fd);
172	}
173#endif
174
175	execl("/bin/sh","sh","-c",cmd,NULL);
176
177	/* not reached */
178	exit(82);
179	return 1;
180}
181
182
183/****************************************************************************
184run a command being careful about uid/gid handling and putting the output in
185outfd (or discard it if outfd is NULL).
186sends the provided secret to the child stdin.
187****************************************************************************/
188
189int smbrunsecret(char *cmd, char *secret)
190{
191	pid_t pid;
192	uid_t uid = current_user.uid;
193	gid_t gid = current_user.gid;
194	int ifd[2];
195
196	/*
197	 * Lose any kernel oplock capabilities we may have.
198	 */
199	oplock_set_capability(False, False);
200
201	/* build up an input pipe */
202	if(pipe(ifd)) {
203		return -1;
204	}
205
206	/* in this method we will exec /bin/sh with the correct
207	   arguments, after first setting stdout to point at the file */
208
209	/*
210	 * We need to temporarily stop CatchChild from eating
211	 * SIGCLD signals as it also eats the exit status code. JRA.
212	 */
213
214	CatchChildLeaveStatus();
215
216	if ((pid=sys_fork()) < 0) {
217		DEBUG(0, ("smbrunsecret: fork failed with error %s\n", strerror(errno)));
218		CatchChild();
219		return errno;
220    	}
221
222	if (pid) {
223		/*
224		 * Parent.
225		 */
226		int status = 0;
227		pid_t wpid;
228
229		close(ifd[0]);
230		/* send the secret */
231		write(ifd[1], secret, strlen(secret));
232		fsync(ifd[1]);
233		close(ifd[1]);
234
235		/* the parent just waits for the child to exit */
236		while((wpid = sys_waitpid(pid, &status, 0)) < 0) {
237			if(errno == EINTR) {
238				errno = 0;
239				continue;
240			}
241			break;
242		}
243
244		CatchChild();
245
246		if (wpid != pid) {
247			DEBUG(2, ("waitpid(%d) : %s\n", (int)pid, strerror(errno)));
248			return -1;
249		}
250
251#if defined(WIFEXITED) && defined(WEXITSTATUS)
252		if (WIFEXITED(status)) {
253			return WEXITSTATUS(status);
254		}
255#endif
256
257		return status;
258	}
259
260	CatchChild();
261
262	/* we are in the child. we exec /bin/sh to do the work for us. we
263	   don't directly exec the command we want because it may be a
264	   pipeline or anything else the config file specifies */
265
266	close(ifd[1]);
267	close(0);
268	if (sys_dup2(ifd[0], 0) != 0) {
269		DEBUG(2,("Failed to create stdin file descriptor\n"));
270		close(ifd[0]);
271		exit(80);
272	}
273
274	/* now completely lose our privileges. This is a fairly paranoid
275	   way of doing it, but it does work on all systems that I know of */
276
277	become_user_permanently(uid, gid);
278
279	if (getuid() != uid || geteuid() != uid ||
280	    getgid() != gid || getegid() != gid) {
281		/* we failed to lose our privileges - do not execute
282                   the command */
283		exit(81); /* we can't print stuff at this stage,
284			     instead use exit codes for debugging */
285	}
286
287#ifndef __INSURE__
288	/* close all other file descriptors, leaving only 0, 1 and 2. 0 and
289	   2 point to /dev/null from the startup code */
290	{
291		int fd;
292		for (fd = 3; fd < 256; fd++) close(fd);
293	}
294#endif
295
296	execl("/bin/sh", "sh", "-c", cmd, NULL);
297
298	/* not reached */
299	exit(82);
300	return 1;
301}
302