1/*
2 * Copyright (c) 2002-2006, 2013 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * Modification History
26 *
27 * October 12, 2001		Allan Nathanson <ajn@apple.com>
28 * - initial revision
29 */
30
31#include <fcntl.h>
32#include <paths.h>
33#include <pwd.h>
34#include <pthread.h>
35#include <unistd.h>
36#include <sysexits.h>
37#include <sys/types.h>
38#include <sys/ioctl.h>
39#include <sys/socket.h>
40#include <sys/wait.h>
41#include <mach/mach.h>
42#include <mach/mach_error.h>
43
44#include <CoreFoundation/CoreFoundation.h>
45#include <SystemConfiguration/SCDPlugin.h>
46#include <SystemConfiguration/SCPrivate.h>
47
48
49
50typedef struct childInfo *childInfoRef;
51
52struct childInfo {
53	pid_t			pid;
54	SCDPluginExecCallBack	callout;
55	void			*context;
56	int			status;
57	struct rusage		rusage;
58	childInfoRef		next;
59};
60
61
62/*
63 * Mach port used to notify runloop when a child process
64 * has been reaped.
65 */
66static CFMachPortRef	childReaped	= NULL;
67
68/*
69 * The following dictionaries contain information about child
70 * processes, reaped processes, and any associated callback
71 * information.
72 *
73 * Important: Access to these dictionaries should only be
74 *            made when in a SIGCHLD handler (or when the
75 *            childLock mutex is held *AND* the signal
76 *            has been blocked).
77 */
78static childInfoRef	activeChildren	= NULL;
79static pthread_mutex_t	lock		= PTHREAD_MUTEX_INITIALIZER;
80
81
82static __inline__ void
83blockSignal()
84{
85	sigset_t	mask	= sigmask(SIGCHLD);
86
87	// block SIGCHLD
88	if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
89		perror("sigprocmask(SIG_BLOCK)");
90	}
91
92	return;
93}
94
95
96static __inline__ void
97unblockSignal()
98{
99	sigset_t	mask	= sigmask(SIGCHLD);
100
101	// unblock SIGCHLD
102	if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1) {
103		perror("sigprocmask(SIG_UNBLOCK)");
104	}
105
106	return;
107}
108
109
110static void
111reaper(int sigraised)
112{
113	/*
114	 * block additional SIGCHLD's until current children have
115	 * been reaped.
116	 */
117	blockSignal();
118
119	/*
120	 * send message to indicate that at least one child is ready
121	 * to be reaped.
122	 */
123	_SC_sendMachMessage(CFMachPortGetPort(childReaped), 0);
124
125	return;
126}
127
128
129static void
130childrenReaped(CFMachPortRef port, void *msg, CFIndex size, void *info)
131{
132	pid_t		pid		= 0;
133	childInfoRef	reapedChildren	= NULL;
134
135	do {
136		struct rusage	rusage;
137		int		status;
138
139		pid = wait4(-1, &status, WNOHANG, &rusage);
140		switch (pid) {
141			case -1 :	// if error
142				if (errno != ECHILD) {
143					perror("wait4");
144				}
145				break;
146
147			case  0 :	// if no more children
148				break;
149
150			default : {
151				childInfoRef	last;
152				childInfoRef	this;
153
154				// grab the activeChildren mutex
155				pthread_mutex_lock(&lock);
156
157				last = NULL;
158				this = activeChildren;
159				while (this) {
160					if (this->pid == pid) {
161						/* save exit status & usage */
162						this->status = status;
163						this->rusage = rusage;
164
165						/* remove from activeChildren */
166						if (last) {
167							last->next = this->next;
168						} else {
169							activeChildren = this->next;
170						}
171
172						/* add to reapedChildren */
173						this->next = reapedChildren;
174						reapedChildren = this;
175
176						break;
177					} else {
178						/* if not this child */
179						last = this;
180						this = this->next;
181					}
182				}
183
184				// release the activeChildren mutex
185				pthread_mutex_unlock(&lock);
186
187				break;
188			}
189		}
190	} while (pid > 0);
191
192	/*
193	 * we need to know about any new children waiting to be reaped so
194	 * re-enable the SIGCHLD handler.
195
196	 */
197	unblockSignal();
198
199	while (reapedChildren) {
200		childInfoRef	child = reapedChildren;
201
202		reapedChildren = reapedChildren->next;
203		(*child->callout)(child->pid,
204				  child->status,
205				  &child->rusage,
206				  child->context);
207		CFAllocatorDeallocate(NULL, child);
208	}
209
210	return;
211}
212
213
214static CFStringRef
215childReapedMPCopyDescription(const void *info)
216{
217	return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SIGCHLD MP>"));
218}
219
220
221void
222_SCDPluginExecInit()
223{
224	struct sigaction	act;
225	CFMachPortContext	context	= { 0
226					  , (void *)1
227					  , NULL
228					  , NULL
229					  , childReapedMPCopyDescription
230					  };
231
232	CFRunLoopSourceRef	rls;
233
234	// create the "a child has been reaped" notification port
235	childReaped = CFMachPortCreate(NULL, childrenReaped, &context, NULL);
236
237	// set queue limit
238	{
239		mach_port_limits_t	limits;
240		kern_return_t		status;
241
242		limits.mpl_qlimit = 1;
243		status = mach_port_set_attributes(mach_task_self(),
244						  CFMachPortGetPort(childReaped),
245						  MACH_PORT_LIMITS_INFO,
246						  (mach_port_info_t)&limits,
247						  MACH_PORT_LIMITS_INFO_COUNT);
248		if (status != KERN_SUCCESS) {
249			perror("mach_port_set_attributes");
250		}
251	}
252
253	// add to our runloop
254	rls = CFMachPortCreateRunLoopSource(NULL, childReaped, 0);
255	CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
256	CFRelease(rls);
257
258	// enable signal handler
259	act.sa_handler = reaper;
260	sigemptyset(&act.sa_mask);
261	act.sa_flags = SA_RESTART|SA_NOCLDSTOP;
262	if (sigaction(SIGCHLD, &act, NULL) == -1) {
263		perror("sigaction");
264	}
265
266	return;
267}
268
269
270pid_t
271_SCDPluginExecCommand2(SCDPluginExecCallBack	callout,
272		       void			*context,
273		       uid_t			uid,
274		       gid_t			gid,
275		       const char		*path,
276		       char * const 		argv[],
277		       SCDPluginExecSetup	setup,
278		       void			*setupContext
279		       )
280{
281	char		buf[1024];
282	pid_t		pid;
283	struct passwd	pwd;
284	struct passwd	*result	= NULL;
285	char		*username = NULL;
286
287	// grab the activeChildren mutex
288	pthread_mutex_lock(&lock);
289
290	// cache the getpwuid_r result here to avoid spinning that can happen
291	// when calling it between fork and execv.
292	if ((getpwuid_r(uid, &pwd, buf, sizeof(buf), &result) == 0) &&
293	    (result != NULL)) {
294		username = result->pw_name;
295	}
296
297	// if needed, initialize
298	if (childReaped == NULL) {
299		_SCDPluginExecInit();
300	}
301
302	pid = fork();
303
304	switch (pid) {
305		case -1 : {	/* if error */
306
307			int	status;
308
309			status = errno;
310			printf("fork() failed: %s\n", strerror(status));
311			errno  = status;
312			break;
313		}
314
315		case 0 : {	/* if child */
316
317			gid_t	egid;
318			uid_t	euid;
319			int	i;
320			int	status;
321
322			if (setup != NULL) {
323				(setup)(pid, setupContext);
324			} else {
325				/* close any open FDs */
326				for (i = getdtablesize()-1; i>=0; i--) close(i);
327				open(_PATH_DEVNULL, O_RDWR, 0);
328				dup(0);
329				dup(0);
330			}
331
332			egid = getegid();
333			euid = geteuid();
334
335			if (egid != gid) {
336				(void) setgid(gid);
337			}
338
339			if (((euid != uid) || (egid != gid)) && username) {
340				initgroups(username, gid);
341			}
342
343			if (euid != uid) {
344				(void) setuid(uid);
345			}
346
347			/* ensure that our PATH environment variable is somewhat reasonable */
348			if (setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 0) == -1) {
349				printf("setenv() failed: %s\n", strerror(errno));
350				exit(EX_OSERR);
351			}
352
353			/* execute requested command */
354			(void) execv(path, argv);
355
356			/* if the execv failed */
357			status = W_EXITCODE(errno, 0);
358			_exit (WEXITSTATUS(status));
359		}
360
361		default : {	/* if parent */
362			if (setup != NULL) {
363				(setup)(pid, setupContext);
364			}
365
366			if (callout != NULL) {
367				childInfoRef	child;
368
369				// create child process info
370				child = CFAllocatorAllocate(NULL, sizeof(struct childInfo), 0);
371				bzero(child, sizeof(struct childInfo));
372				child->pid     = pid;
373				child->callout = callout;
374				child->context = context;
375
376				// add the new child to the activeChildren list
377				child->next = activeChildren;
378				activeChildren = child;
379			}
380			break;
381		}
382	}
383
384	// release the activeChildren mutex
385	pthread_mutex_unlock(&lock);
386
387	return pid;
388}
389
390
391pid_t
392_SCDPluginExecCommand(SCDPluginExecCallBack	callout,
393		     void			*context,
394		     uid_t			uid,
395		     gid_t			gid,
396		     const char			*path,
397		     char * const		argv[])
398{
399	return _SCDPluginExecCommand2(callout, context, uid, gid, path, argv, NULL, NULL);
400}
401