#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/types.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFRuntime.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h>
#include <SystemConfiguration/SCValidation.h>
#include <pthread.h>
#include <sysexits.h>
#include "SCPreferencesInternal.h"
#include "SCHelper_client.h"
#include "helper_comm.h"
#if TARGET_OS_IPHONE
#include <grp.h>
__private_extern__ int
getgrnam_r(const char *name, __unused struct group *grp, __unused char *buf, __unused size_t bufsize, struct group **grpP)
{
*grpP = getgrnam(name);
return (*grpP == NULL) ? -1 : 0;
}
#endif // TARGET_OS_IPHONE
#pragma mark -
#pragma mark Session managment
typedef const struct __SCHelperSession * SCHelperSessionRef;
typedef struct {
CFRuntimeBase cfBase;
AuthorizationRef authorization;
#if TARGET_OS_IPHONE
uid_t peer_euid;
gid_t peer_egid;
#endif // TARGET_OS_IPHONE
SCPreferencesRef prefs;
} SCHelperSessionPrivate, *SCHelperSessionPrivateRef;
static AuthorizationRef
__SCHelperSessionGetAuthorization(SCHelperSessionRef session)
{
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)session;
return sessionPrivate->authorization;
}
static Boolean
__SCHelperSessionSetAuthorization(SCHelperSessionRef session, CFTypeRef authorizationData)
{
Boolean ok = TRUE;
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)session;
#if !TARGET_OS_IPHONE
if (sessionPrivate->authorization != NULL) {
AuthorizationFree(sessionPrivate->authorization, kAuthorizationFlagDefaults);
sessionPrivate->authorization = NULL;
}
if (isA_CFData(authorizationData)) {
AuthorizationExternalForm extForm;
if (CFDataGetLength(authorizationData) == sizeof(extForm.bytes)) {
OSStatus err;
bcopy(CFDataGetBytePtr(authorizationData), extForm.bytes, sizeof(extForm.bytes));
err = AuthorizationCreateFromExternalForm(&extForm,
&sessionPrivate->authorization);
if (err != errAuthorizationSuccess) {
SCLog(TRUE, LOG_ERR,
CFSTR("AuthorizationCreateFromExternalForm() failed: status = %d"),
(int)err);
sessionPrivate->authorization = NULL;
ok = FALSE;
}
}
}
#else // !TARGET_OS_IPHONE
if (sessionPrivate->authorization != NULL) {
CFRelease(sessionPrivate->authorization);
sessionPrivate->authorization = NULL;
}
if (isA_CFString(authorizationData)) {
sessionPrivate->authorization = (void *)CFRetain(authorizationData);
}
#endif // !TARGET_OS_IPHONE
return ok;
}
#if TARGET_OS_IPHONE
static void
__SCHelperSessionGetCredentials(SCHelperSessionRef session, uid_t *euid, gid_t *egid)
{
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)session;
if (euid != NULL) *euid = sessionPrivate->peer_euid;
if (egid != NULL) *egid = sessionPrivate->peer_egid;
return;
}
static Boolean
__SCHelperSessionSetCredentials(SCHelperSessionRef session, uid_t euid, gid_t egid)
{
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)session;
sessionPrivate->peer_euid = euid;
sessionPrivate->peer_egid = egid;
return TRUE;
}
#endif // TARGET_OS_IPHONE
static SCPreferencesRef
__SCHelperSessionGetPreferences(SCHelperSessionRef session)
{
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)session;
return sessionPrivate->prefs;
}
static Boolean
__SCHelperSessionSetPreferences(SCHelperSessionRef session, SCPreferencesRef prefs)
{
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)session;
if (prefs != NULL) {
CFRetain(prefs);
}
if (sessionPrivate->prefs != NULL) {
CFRelease(sessionPrivate->prefs);
}
sessionPrivate->prefs = prefs;
return TRUE;
}
static CFStringRef __SCHelperSessionCopyDescription (CFTypeRef cf);
static void __SCHelperSessionDeallocate (CFTypeRef cf);
static CFTypeID __kSCHelperSessionTypeID = _kCFRuntimeNotATypeID;
static Boolean debug = FALSE;
static pthread_once_t initialized = PTHREAD_ONCE_INIT;
static CFRunLoopRef main_runLoop = NULL;
static CFMutableSetRef sessions = NULL;
static int sessions_closed = 0; static pthread_mutex_t sessions_lock = PTHREAD_MUTEX_INITIALIZER;
static const CFRuntimeClass __SCHelperSessionClass = {
0, "SCHelperSession", NULL, NULL, __SCHelperSessionDeallocate, NULL, NULL, NULL, __SCHelperSessionCopyDescription };
static CFStringRef
__SCHelperSessionCopyDescription(CFTypeRef cf)
{
CFAllocatorRef allocator = CFGetAllocator(cf);
CFMutableStringRef result;
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)cf;
result = CFStringCreateMutable(allocator, 0);
CFStringAppendFormat(result, NULL, CFSTR("<SCHelperSession %p [%p]> {"), cf, allocator);
CFStringAppendFormat(result, NULL, CFSTR("authorization = %p"), sessionPrivate->authorization);
CFStringAppendFormat(result, NULL, CFSTR(", prefs = %p"), sessionPrivate->prefs);
CFStringAppendFormat(result, NULL, CFSTR("}"));
return result;
}
static void
__SCHelperSessionDeallocate(CFTypeRef cf)
{
SCHelperSessionPrivateRef sessionPrivate = (SCHelperSessionPrivateRef)cf;
__SCHelperSessionSetAuthorization((SCHelperSessionRef)sessionPrivate, NULL);
__SCHelperSessionSetPreferences ((SCHelperSessionRef)sessionPrivate, NULL);
pthread_mutex_lock(&sessions_lock);
CFSetRemoveValue(sessions, sessionPrivate);
sessions_closed++;
pthread_mutex_unlock(&sessions_lock);
CFRunLoopWakeUp(main_runLoop);
return;
}
static void
__SCHelperSessionInitialize(void)
{
__kSCHelperSessionTypeID = _CFRuntimeRegisterClass(&__SCHelperSessionClass);
return;
}
static SCHelperSessionRef
__SCHelperSessionCreate(CFAllocatorRef allocator)
{
SCHelperSessionPrivateRef sessionPrivate;
uint32_t size;
pthread_once(&initialized, __SCHelperSessionInitialize);
size = sizeof(SCHelperSessionPrivate) - sizeof(CFRuntimeBase);
sessionPrivate = (SCHelperSessionPrivateRef)_CFRuntimeCreateInstance(allocator,
__kSCHelperSessionTypeID,
size,
NULL);
if (sessionPrivate == NULL) {
return NULL;
}
sessionPrivate->authorization = NULL;
#if TARGET_OS_IPHONE
sessionPrivate->peer_euid = 0;
sessionPrivate->peer_egid = 0;
#endif // TARGET_OS_IPHONE
sessionPrivate->prefs = NULL;
pthread_mutex_lock(&sessions_lock);
if (sessions == NULL) {
sessions = CFSetCreateMutable(NULL, 0, NULL); }
CFSetAddValue(sessions, sessionPrivate);
pthread_mutex_unlock(&sessions_lock);
return (SCHelperSessionRef)sessionPrivate;
}
#pragma mark -
#pragma mark Helpers
static Boolean
do_Exit(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
*status = -1;
return FALSE;
}
static Boolean
do_Auth(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
Boolean ok;
#if !TARGET_OS_IPHONE
ok = __SCHelperSessionSetAuthorization(session, data);
#else //!TARGET_OS_IPHONE
CFStringRef authorizationInfo = NULL;
if ((data != NULL) && !_SCUnserializeString(&authorizationInfo, data, NULL, 0)) {
return FALSE;
}
if (!isA_CFString(authorizationInfo)) {
if (authorizationInfo != NULL) CFRelease(authorizationInfo);
return FALSE;
}
ok = __SCHelperSessionSetAuthorization(session, authorizationInfo);
if (authorizationInfo != NULL) CFRelease(authorizationInfo);
#endif // !TARGET_OS_IPHONE
*status = ok ? 0 : 1;
return TRUE;
}
#if !TARGET_OS_IPHONE
static Boolean
do_keychain_copy(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
SCPreferencesRef prefs;
CFStringRef unique_id = NULL;
if ((data != NULL) && !_SCUnserializeString(&unique_id, data, NULL, 0)) {
return FALSE;
}
if (!isA_CFString(unique_id)) {
return FALSE;
}
prefs = __SCHelperSessionGetPreferences(session);
*reply = _SCPreferencesSystemKeychainPasswordItemCopy(prefs, unique_id);
CFRelease(unique_id);
if (*reply == NULL) {
*status = SCError();
}
return TRUE;
}
static Boolean
do_keychain_exists(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
Boolean ok;
SCPreferencesRef prefs;
CFStringRef unique_id = NULL;
if ((data != NULL) && !_SCUnserializeString(&unique_id, data, NULL, 0)) {
return FALSE;
}
if (!isA_CFString(unique_id)) {
if (unique_id != NULL) CFRelease(unique_id);
return FALSE;
}
prefs = __SCHelperSessionGetPreferences(session);
ok = _SCPreferencesSystemKeychainPasswordItemExists(prefs, unique_id);
CFRelease(unique_id);
if (!ok) {
*status = SCError();
}
return TRUE;
}
static Boolean
do_keychain_remove(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
Boolean ok;
SCPreferencesRef prefs;
CFStringRef unique_id = NULL;
if ((data != NULL) && !_SCUnserializeString(&unique_id, data, NULL, 0)) {
return FALSE;
}
if (!isA_CFString(unique_id)) {
if (unique_id != NULL) CFRelease(unique_id);
return FALSE;
}
prefs = __SCHelperSessionGetPreferences(session);
ok = _SCPreferencesSystemKeychainPasswordItemRemove(prefs, unique_id);
CFRelease(unique_id);
if (!ok) {
*status = SCError();
}
return TRUE;
}
static Boolean
do_keychain_set(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
CFStringRef account;
CFStringRef description;
CFArrayRef executablePaths = NULL;
CFStringRef label;
Boolean ok;
CFDictionaryRef options = NULL;
CFDataRef password;
SCPreferencesRef prefs;
CFStringRef unique_id;
if ((data != NULL) && !_SCUnserialize((CFPropertyListRef *)&options, data, NULL, 0)) {
return FALSE;
}
if (!isA_CFDictionary(options)) {
if (options != NULL) CFRelease(options);
return FALSE;
}
if (CFDictionaryGetValueIfPresent(options,
kSCKeychainOptionsAllowedExecutables,
(const void **)&executablePaths)) {
CFMutableArrayRef executableURLs;
CFIndex i;
CFIndex n;
CFMutableDictionaryRef newOptions;
executableURLs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
n = CFArrayGetCount(executablePaths);
for (i = 0; i < n; i++) {
CFDataRef path;
CFURLRef url;
path = CFArrayGetValueAtIndex(executablePaths, i);
url = CFURLCreateFromFileSystemRepresentation(NULL,
CFDataGetBytePtr(path),
CFDataGetLength(path),
FALSE);
if (url != NULL) {
CFArrayAppendValue(executableURLs, url);
CFRelease(url);
}
}
newOptions = CFDictionaryCreateMutableCopy(NULL, 0, options);
CFDictionarySetValue(newOptions, kSCKeychainOptionsAllowedExecutables, executableURLs);
CFRelease(executableURLs);
CFRelease(options);
options = newOptions;
}
unique_id = CFDictionaryGetValue(options, kSCKeychainOptionsUniqueID);
label = CFDictionaryGetValue(options, kSCKeychainOptionsLabel);
description = CFDictionaryGetValue(options, kSCKeychainOptionsDescription);
account = CFDictionaryGetValue(options, kSCKeychainOptionsAccount);
password = CFDictionaryGetValue(options, kSCKeychainOptionsPassword);
prefs = __SCHelperSessionGetPreferences(session);
ok = _SCPreferencesSystemKeychainPasswordItemSet(prefs,
unique_id,
label,
description,
account,
password,
options);
CFRelease(options);
if (!ok) {
*status = SCError();
}
return TRUE;
}
#endif // !TARGET_OS_IPHONE
static Boolean
do_interface_refresh(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
CFStringRef ifName = NULL;
Boolean ok;
if ((data != NULL) && !_SCUnserializeString(&ifName, data, NULL, 0)) {
SCLog(TRUE, LOG_ERR, CFSTR("interface name not valid"));
return FALSE;
}
if (!isA_CFString(ifName)) {
SCLog(TRUE, LOG_ERR, CFSTR("interface name not valid"));
if (ifName != NULL) CFRelease(ifName);
return FALSE;
}
ok = _SCNetworkInterfaceForceConfigurationRefresh(ifName);
CFRelease(ifName);
if (!ok) {
*status = SCError();
}
return TRUE;
}
static Boolean
do_prefs_Open(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
CFStringRef name;
CFNumberRef pid;
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
CFDictionaryRef prefsInfo = NULL;
CFStringRef prefsID;
CFStringRef prefsName;
if (prefs != NULL) {
return FALSE;
}
if ((data != NULL) && !_SCUnserialize((CFPropertyListRef *)&prefsInfo, data, NULL, 0)) {
SCLog(TRUE, LOG_ERR, CFSTR("data not valid, %@"), data);
return FALSE;
}
if ((prefsInfo == NULL) || !isA_CFDictionary(prefsInfo)) {
SCLog(TRUE, LOG_ERR, CFSTR("info not valid"));
if (prefsInfo != NULL) CFRelease(prefsInfo);
return FALSE;
}
prefsID = CFDictionaryGetValue(prefsInfo, CFSTR("prefsID"));
prefsID = isA_CFString(prefsID);
if (prefsID != NULL) {
if (CFStringHasPrefix(prefsID, CFSTR("/")) ||
CFStringHasPrefix(prefsID, CFSTR("../")) ||
CFStringHasSuffix(prefsID, CFSTR("/..")) ||
(CFStringFind(prefsID, CFSTR("/../"), 0).location != kCFNotFound)) {
SCLog(TRUE, LOG_ERR, CFSTR("prefsID (%@) not valid"), prefsID);
CFRelease(prefsInfo);
*status = kSCStatusInvalidArgument;
return TRUE;
}
}
name = CFDictionaryGetValue(prefsInfo, CFSTR("name"));
if (!isA_CFString(name)) {
SCLog(TRUE, LOG_ERR, CFSTR("session \"name\" not valid"));
CFRelease(prefsInfo);
return FALSE;
}
pid = CFDictionaryGetValue(prefsInfo, CFSTR("PID"));
if (!isA_CFNumber(pid)) {
SCLog(TRUE, LOG_ERR, CFSTR("PID not valid"));
CFRelease(prefsInfo);
return FALSE;
}
prefsName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@:%@"), pid, name);
prefs = SCPreferencesCreate(NULL, prefsName, prefsID);
CFRelease(prefsName);
CFRelease(prefsInfo);
__SCHelperSessionSetPreferences(session, prefs);
if (prefs != NULL) {
CFRelease(prefs);
} else {
*status = SCError();
}
return TRUE;
}
static Boolean
do_prefs_Access(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
Boolean ok;
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
CFDataRef signature;
if (prefs == NULL) {
return FALSE;
}
signature = SCPreferencesGetSignature(prefs);
if (signature != NULL) {
const void * dictKeys[2];
const void * dictVals[2];
SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
CFDictionaryRef replyDict;
dictKeys[0] = CFSTR("signature");
dictVals[0] = signature;
dictKeys[1] = CFSTR("preferences");
dictVals[1] = prefsPrivate->prefs;
replyDict = CFDictionaryCreate(NULL,
(const void **)&dictKeys,
(const void **)&dictVals,
sizeof(dictKeys)/sizeof(dictKeys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
ok = _SCSerialize(replyDict, reply, NULL, NULL);
CFRelease(replyDict);
if (!ok) {
return FALSE;
}
} else {
*status = SCError();
}
return TRUE;
}
static Boolean
do_prefs_Lock(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
CFDataRef clientSignature = (CFDataRef)data;
Boolean ok;
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
Boolean wait = (info == (void *)FALSE) ? FALSE : TRUE;
if (prefs == NULL) {
return FALSE;
}
ok = SCPreferencesLock(prefs, wait);
if (!ok) {
*status = SCError();
return TRUE;
}
if (clientSignature != NULL) {
CFDataRef serverSignature;
serverSignature = SCPreferencesGetSignature(prefs);
if (!CFEqual(clientSignature, serverSignature)) {
(void)SCPreferencesUnlock(prefs);
*status = kSCStatusStale;
}
}
return TRUE;
}
static Boolean
do_prefs_Commit(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
Boolean ok;
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
if (prefs == NULL) {
return FALSE;
}
if (data != NULL) {
SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
if (prefsPrivate->prefs != NULL) {
CFRelease(prefsPrivate->prefs);
}
ok = _SCUnserialize((CFPropertyListRef *)&prefsPrivate->prefs, data, NULL, 0);
if (!ok) {
return FALSE;
}
prefsPrivate->accessed = TRUE;
prefsPrivate->changed = TRUE;
}
ok = SCPreferencesCommitChanges(prefs);
if (ok) {
*reply = SCPreferencesGetSignature(prefs);
CFRetain(*reply);
} else {
*status = SCError();
}
return TRUE;
}
static Boolean
do_prefs_Apply(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
Boolean ok;
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
if (prefs == NULL) {
return FALSE;
}
ok = SCPreferencesApplyChanges(prefs);
if (!ok) {
*status = SCError();
}
return TRUE;
}
static Boolean
do_prefs_Unlock(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
Boolean ok;
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
if (prefs == NULL) {
return FALSE;
}
ok = SCPreferencesUnlock(prefs);
if (!ok) {
*status = SCError();
}
return TRUE;
}
static Boolean
do_prefs_Close(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
if (prefs == NULL) {
return FALSE;
}
__SCHelperSessionSetPreferences(session, NULL);
*status = -1;
return TRUE;
}
static Boolean
do_prefs_Synchronize(SCHelperSessionRef session, void *info, CFDataRef data, uint32_t *status, CFDataRef *reply)
{
SCPreferencesRef prefs = __SCHelperSessionGetPreferences(session);
if (prefs == NULL) {
return FALSE;
}
SCPreferencesSynchronize(prefs);
*status = kSCStatusOK;
return TRUE;
}
#pragma mark -
#pragma mark Process commands
static Boolean
hasAuthorization(SCHelperSessionRef session)
{
AuthorizationRef authorization = __SCHelperSessionGetAuthorization(session);
#if !TARGET_OS_IPHONE
AuthorizationFlags flags;
AuthorizationItem items[1];
AuthorizationRights rights;
OSStatus status;
if (authorization == NULL) {
return FALSE;
}
items[0].name = "system.preferences";
items[0].value = NULL;
items[0].valueLength = 0;
items[0].flags = 0;
rights.count = sizeof(items) / sizeof(items[0]);
rights.items = items;
flags = kAuthorizationFlagDefaults;
flags |= kAuthorizationFlagExtendRights;
flags |= kAuthorizationFlagInteractionAllowed;
status = AuthorizationCopyRights(authorization,
&rights,
kAuthorizationEmptyEnvironment,
flags,
NULL);
if (status != errAuthorizationSuccess) {
return FALSE;
}
#else // !TARGET_OS_IPHONE
uid_t peer_euid;
gid_t peer_egid;
if (authorization == NULL) {
return FALSE;
}
__SCHelperSessionGetCredentials(session, &peer_euid, &peer_egid);
if ((peer_euid != 0) && (peer_egid != 0)) {
static gid_t mobile_gid = -1;
if (mobile_gid == -1) {
char buffer[1024];
struct group grp;
struct group *grpP;
if (getgrnam_r("mobile", &grp, buffer, sizeof(buffer), &grpP) == 0) {
mobile_gid = grpP->gr_gid;
}
}
if (peer_egid != mobile_gid) {
return FALSE;
}
}
#endif // !TARGET_OS_IPHONE
return TRUE;
}
typedef Boolean (*helperFunction) (SCHelperSessionRef session,
void *info,
CFDataRef data,
uint32_t *status,
CFDataRef *reply);
static const struct helper {
int command;
const char *commandName;
Boolean needsAuthorization;
helperFunction func;
void *info;
} helpers[] = {
{ SCHELPER_MSG_AUTH, "AUTH", FALSE, do_Auth , NULL },
{ SCHELPER_MSG_PREFS_OPEN, "PREFS open", FALSE, do_prefs_Open , NULL },
{ SCHELPER_MSG_PREFS_ACCESS, "PREFS access", TRUE, do_prefs_Access , NULL },
{ SCHELPER_MSG_PREFS_LOCK, "PREFS lock", TRUE, do_prefs_Lock , (void *)FALSE },
{ SCHELPER_MSG_PREFS_LOCKWAIT, "PREFS lock/wait", TRUE, do_prefs_Lock , (void *)TRUE },
{ SCHELPER_MSG_PREFS_COMMIT, "PREFS commit", TRUE, do_prefs_Commit , NULL },
{ SCHELPER_MSG_PREFS_APPLY, "PREFS apply", TRUE, do_prefs_Apply , NULL },
{ SCHELPER_MSG_PREFS_UNLOCK, "PREFS unlock", FALSE, do_prefs_Unlock , NULL },
{ SCHELPER_MSG_PREFS_CLOSE, "PREFS close", FALSE, do_prefs_Close , NULL },
{ SCHELPER_MSG_PREFS_SYNCHRONIZE, "PREFS synchronize", FALSE, do_prefs_Synchronize , NULL },
{ SCHELPER_MSG_INTERFACE_REFRESH, "INTERFACE refresh", TRUE, do_interface_refresh , NULL },
#if !TARGET_OS_IPHONE
{ SCHELPER_MSG_KEYCHAIN_COPY, "KEYCHAIN copy", TRUE, do_keychain_copy , NULL },
{ SCHELPER_MSG_KEYCHAIN_EXISTS, "KEYCHAIN exists", TRUE, do_keychain_exists , NULL },
{ SCHELPER_MSG_KEYCHAIN_REMOVE, "KEYCHAIN remove", TRUE, do_keychain_remove , NULL },
{ SCHELPER_MSG_KEYCHAIN_SET, "KEYCHAIN set", TRUE, do_keychain_set , NULL },
#endif // !TARGET_OS_IPHONE
{ SCHELPER_MSG_EXIT, "EXIT", FALSE, do_Exit , NULL }
};
#define nHELPERS (sizeof(helpers)/sizeof(struct helper))
static int
findHelper(uint32_t command)
{
int i;
for (i = 0; i < (int)nHELPERS; i++) {
if (helpers[i].command == command) {
return i;
}
}
return -1;
}
static Boolean
process_command(SCHelperSessionRef session, int fd, int *err)
{
uint32_t command = 0;
CFDataRef data = NULL;
int i;
Boolean ok = FALSE;
CFDataRef reply = NULL;
uint32_t status = kSCStatusOK;
if (!__SCHelper_rxMessage(fd, &command, &data)) {
SCLog(TRUE, LOG_ERR, CFSTR("no command"));
*err = EIO;
goto done;
}
i = findHelper(command);
if (i == -1) {
SCLog(TRUE, LOG_ERR, CFSTR("received unknown command : %u"), command);
*err = EINVAL;
goto done;
}
SCLog(debug, LOG_DEBUG,
CFSTR("processing command \"%s\"%s"),
helpers[i].commandName,
(data != NULL) ? " w/data" : "");
if (helpers[i].needsAuthorization && !hasAuthorization(session)) {
SCLog(debug, LOG_DEBUG,
CFSTR("command \"%s\" : not authorized"),
helpers[i].commandName);
status = kSCStatusAccessError;
}
if (status == kSCStatusOK) {
ok = (*helpers[i].func)(session, helpers[i].info, data, &status, &reply);
}
if ((status != -1) || (reply != NULL)) {
SCLog(debug, LOG_DEBUG,
CFSTR("sending status %u%s"),
status,
(reply != NULL) ? " w/reply" : "");
if (!__SCHelper_txMessage(fd, status, reply)) {
*err = EIO;
ok = FALSE;
goto done;
}
}
done :
if (data != NULL) {
CFRelease(data);
}
if (reply != NULL) {
CFRelease(reply);
}
return ok;
}
#pragma mark -
#pragma mark Main loop
static void
readCallback(CFSocketRef s,
CFSocketCallBackType callbackType,
CFDataRef address,
const void *data,
void *info)
{
CFSocketNativeHandle fd;
int err = 0;
Boolean ok;
SCHelperSessionRef session = (SCHelperSessionRef)info;
if (callbackType != kCFSocketReadCallBack) {
SCLog(TRUE, LOG_ERR, CFSTR("readCallback w/callbackType = %d"), callbackType);
return;
}
fd = CFSocketGetNative(s);
ok = process_command(session, fd, &err);
if (!ok) {
SCLog(debug, LOG_DEBUG, CFSTR("per-session socket : invalidate fd %d"), fd);
CFSocketInvalidate(s);
}
return;
}
static void *
newHelper(void *arg)
{
CFSocketContext context = { 0, NULL, CFRetain, CFRelease, CFCopyDescription };
CFSocketNativeHandle fd = (CFSocketNativeHandle)(intptr_t)arg;
CFRunLoopSourceRef rls;
SCHelperSessionRef session;
CFSocketRef sock;
#if TARGET_OS_IPHONE
uid_t peer_euid;
gid_t peer_egid;
#endif // TARGET_OS_IPHONE
session = __SCHelperSessionCreate(NULL);
#if TARGET_OS_IPHONE
if (getpeereid(fd, &peer_euid, &peer_egid) == 0) {
__SCHelperSessionSetCredentials(session, peer_euid, peer_egid);
} else {
SCLog(TRUE, LOG_ERR, CFSTR("getpeereid() failed: %s"), strerror(errno));
}
#endif // TARGET_OS_IPHONE
context.info = (void *)session;
sock = CFSocketCreateWithNative(NULL,
fd,
kCFSocketReadCallBack,
readCallback,
&context);
CFRelease(session);
rls = CFSocketCreateRunLoopSource(NULL, sock, 0);
CFRelease(sock);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
CFRunLoopRun();
return NULL;
}
static void
acceptCallback(CFSocketRef s,
CFSocketCallBackType callbackType,
CFDataRef address,
const void *data,
void *info)
{
CFSocketNativeHandle fd;
pthread_attr_t tattr;
pthread_t tid;
static int yes = 1;
if (callbackType != kCFSocketAcceptCallBack) {
SCLog(TRUE, LOG_ERR, CFSTR("acceptCallback w/callbackType = %d"), callbackType);
return;
}
if ((data == NULL) ||
((fd = *((CFSocketNativeHandle *)data)) == -1)) {
SCLog(TRUE, LOG_ERR, CFSTR("accept w/no FD"));
return;
}
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (const void *)&yes, sizeof(yes)) == -1) {
SCLog(TRUE, LOG_ERR, CFSTR("setsockopt(SO_NOSIGPIPE) failed: %s"), strerror(errno));
return;
}
pthread_attr_init(&tattr);
pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize(&tattr, 96 * 1024); pthread_create(&tid, &tattr, newHelper, (void *)(intptr_t)fd);
pthread_attr_destroy(&tattr);
return;
}
#include <launch.h>
static const struct option longopts[] = {
{ "debug", no_argument, 0, 'd' },
{ 0, 0, 0, 0 }
};
int
main(int argc, char **argv)
{
Boolean done = FALSE;
int err = 0;
int i;
launch_data_t l_listeners;
launch_data_t l_msg;
launch_data_t l_reply;
launch_data_t l_sockets;
launch_data_type_t l_type;
int n = 0;
extern int optind;
int opt;
int opti;
openlog("SCHelper", LOG_CONS|LOG_PID, LOG_DAEMON);
while ((opt = getopt_long(argc, argv, "d", longopts, &opti)) != -1) {
switch(opt) {
case 'd':
debug = TRUE;
break;
case 0 :
break;
case '?':
default :
SCLog(TRUE, LOG_ERR,
CFSTR("ignoring unknown or ambiguous command line option"));
break;
}
}
if (geteuid() != 0) {
SCLog(TRUE, LOG_ERR, CFSTR("%s"), strerror(EACCES));
exit(EACCES);
}
main_runLoop = CFRunLoopGetCurrent();
l_msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
l_reply = launch_msg(l_msg);
launch_data_free(l_msg);
l_type = (l_reply != NULL) ? launch_data_get_type(l_reply) : 0;
if (l_type != LAUNCH_DATA_DICTIONARY) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCHelper: error w/launchd " LAUNCH_KEY_CHECKIN " dictionary (%p, %d)"),
l_reply,
l_type);
err = 1;
goto done;
}
l_sockets = launch_data_dict_lookup(l_reply, LAUNCH_JOBKEY_SOCKETS);
l_type = (l_sockets != NULL) ? launch_data_get_type(l_sockets) : 0;
if (l_type != LAUNCH_DATA_DICTIONARY) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCHelper: error w/" LAUNCH_JOBKEY_SOCKETS " (%p, %d)"),
l_sockets,
l_type);
err = 1;
goto done;
}
l_listeners = launch_data_dict_lookup(l_sockets, "Listeners");
l_type = (l_listeners != NULL) ? launch_data_get_type(l_listeners) : 0;
if (l_type != LAUNCH_DATA_ARRAY) {
SCLog(TRUE, LOG_ERR, CFSTR("SCHelper: error w/Listeners (%p, %d)"),
l_listeners,
l_type);
goto done;
}
n = launch_data_array_get_count(l_listeners);
for (i = 0; i < n; i++) {
CFSocketNativeHandle fd;
launch_data_t l_fd;
CFRunLoopSourceRef rls;
CFSocketRef sock;
l_fd = launch_data_array_get_index(l_listeners, i);
l_type = (l_fd != NULL) ? launch_data_get_type(l_fd) : 0;
if (l_type != LAUNCH_DATA_FD) {
SCLog(TRUE, LOG_ERR, CFSTR("SCHelper: error w/Listeners[%d] (%p, %d)"),
i,
l_fd,
l_type);
err = 1;
goto done;
}
fd = launch_data_get_fd(l_fd);
sock = CFSocketCreateWithNative(NULL,
fd,
kCFSocketAcceptCallBack,
acceptCallback,
NULL);
rls = CFSocketCreateRunLoopSource(NULL, sock, 0);
CFRunLoopAddSource(main_runLoop, rls, kCFRunLoopDefaultMode);
CFRelease(rls);
CFRelease(sock);
}
done :
if (l_reply != NULL) launch_data_free(l_reply);
if ((err != 0) || (n == 0)) {
exit(err);
}
while (!done) {
SInt32 rlStatus;
rlStatus = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 15.0, TRUE);
if (rlStatus == kCFRunLoopRunTimedOut) {
pthread_mutex_lock(&sessions_lock);
done = ((sessions != NULL) &&
(CFSetGetCount(sessions) == 0) &&
(sessions_closed == 0));
sessions_closed = 0;
pthread_mutex_unlock(&sessions_lock);
}
}
exit(EX_OK);
}