#include <fcntl.h>
#include <paths.h>
#include <pwd.h>
#include <pthread.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SCDPlugin.h>
#include <SystemConfiguration/SCPrivate.h>
typedef struct childInfo *childInfoRef;
struct childInfo {
pid_t pid;
SCDPluginExecCallBack callout;
void *context;
int status;
struct rusage rusage;
childInfoRef next;
};
static CFMachPortRef childReaped = NULL;
static childInfoRef activeChildren = NULL;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static __inline__ void
blockSignal()
{
sigset_t mask = sigmask(SIGCHLD);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
perror("sigprocmask(SIG_BLOCK)");
}
return;
}
static __inline__ void
unblockSignal()
{
sigset_t mask = sigmask(SIGCHLD);
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1) {
perror("sigprocmask(SIG_UNBLOCK)");
}
return;
}
static void
reaper(int sigraised)
{
blockSignal();
_SC_sendMachMessage(CFMachPortGetPort(childReaped), 0);
return;
}
static void
childrenReaped(CFMachPortRef port, void *msg, CFIndex size, void *info)
{
pid_t pid = 0;
childInfoRef reapedChildren = NULL;
do {
struct rusage rusage;
int status;
pid = wait4(-1, &status, WNOHANG, &rusage);
switch (pid) {
case -1 : if (errno != ECHILD) {
perror("wait4");
}
break;
case 0 : break;
default : {
childInfoRef last;
childInfoRef this;
pthread_mutex_lock(&lock);
last = NULL;
this = activeChildren;
while (this) {
if (this->pid == pid) {
this->status = status;
this->rusage = rusage;
if (last) {
last->next = this->next;
} else {
activeChildren = this->next;
}
this->next = reapedChildren;
reapedChildren = this;
break;
} else {
last = this;
this = this->next;
}
}
pthread_mutex_unlock(&lock);
break;
}
}
} while (pid > 0);
unblockSignal();
while (reapedChildren) {
childInfoRef child = reapedChildren;
reapedChildren = reapedChildren->next;
(*child->callout)(child->pid,
child->status,
&child->rusage,
child->context);
CFAllocatorDeallocate(NULL, child);
}
return;
}
static CFStringRef
childReapedMPCopyDescription(const void *info)
{
return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SIGCHLD MP>"));
}
void
_SCDPluginExecInit()
{
struct sigaction act;
CFMachPortContext context = { 0
, (void *)1
, NULL
, NULL
, childReapedMPCopyDescription
};
CFRunLoopSourceRef rls;
childReaped = CFMachPortCreate(NULL, childrenReaped, &context, NULL);
{
mach_port_limits_t limits;
kern_return_t status;
limits.mpl_qlimit = 1;
status = mach_port_set_attributes(mach_task_self(),
CFMachPortGetPort(childReaped),
MACH_PORT_LIMITS_INFO,
(mach_port_info_t)&limits,
MACH_PORT_LIMITS_INFO_COUNT);
if (status != KERN_SUCCESS) {
perror("mach_port_set_attributes");
}
}
rls = CFMachPortCreateRunLoopSource(NULL, childReaped, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
act.sa_handler = reaper;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART|SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &act, NULL) == -1) {
perror("sigaction");
}
return;
}
pid_t
_SCDPluginExecCommand2(SCDPluginExecCallBack callout,
void *context,
uid_t uid,
gid_t gid,
const char *path,
char * const argv[],
SCDPluginExecSetup setup,
void *setupContext
)
{
char buf[1024];
pid_t pid;
struct passwd pwd;
struct passwd *result = NULL;
char *username = NULL;
pthread_mutex_lock(&lock);
if ((getpwuid_r(uid, &pwd, buf, sizeof(buf), &result) == 0) &&
(result != NULL)) {
username = result->pw_name;
}
if (childReaped == NULL) {
_SCDPluginExecInit();
}
pid = fork();
switch (pid) {
case -1 : {
int status;
status = errno;
printf("fork() failed: %s\n", strerror(status));
errno = status;
break;
}
case 0 : {
gid_t egid;
uid_t euid;
int status;
if (setup != NULL) {
(setup)(pid, setupContext);
} else {
int fd;
int i;
for (i = getdtablesize()-1; i>=0; i--) close(i);
fd = open(_PATH_DEVNULL, O_RDWR, 0);
if (fd != -1) {
(void) dup2(fd, STDIN_FILENO);
(void) dup2(fd, STDOUT_FILENO);
(void) dup2(fd, STDERR_FILENO);
if ((fd != STDIN_FILENO) && (fd != STDOUT_FILENO) && (fd != STDERR_FILENO)) {
(void) close(fd);
}
}
}
egid = getegid();
euid = geteuid();
if (egid != gid) {
(void) setgid(gid);
}
if (((euid != uid) || (egid != gid)) && username) {
initgroups(username, gid);
}
if (euid != uid) {
(void) setuid(uid);
}
if (setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 0) == -1) {
printf("setenv() failed: %s\n", strerror(errno));
exit(EX_OSERR);
}
(void) execv(path, argv);
status = W_EXITCODE(errno, 0);
_exit (WEXITSTATUS(status));
}
default : {
if (setup != NULL) {
(setup)(pid, setupContext);
}
if (callout != NULL) {
childInfoRef child;
child = CFAllocatorAllocate(NULL, sizeof(struct childInfo), 0);
bzero(child, sizeof(struct childInfo));
child->pid = pid;
child->callout = callout;
child->context = context;
child->next = activeChildren;
activeChildren = child;
}
break;
}
}
pthread_mutex_unlock(&lock);
return pid;
}
pid_t
_SCDPluginExecCommand(SCDPluginExecCallBack callout,
void *context,
uid_t uid,
gid_t gid,
const char *path,
char * const argv[])
{
return _SCDPluginExecCommand2(callout, context, uid, gid, path, argv, NULL, NULL);
}