#include <notify.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/types.h>
#include <servers/bootstrap.h>
#include <mach/mach.h>
#include <bsm/libbsm.h>
#include <dispatch/dispatch.h>
#include <xpc/xpc.h>
#include <os/state_private.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SCPrivate.h>
#include "libSystemConfiguration_client.h"
#include "libSystemConfiguration_server.h"
#include "dnsinfo_create.h"
#include "dnsinfo_server.h"
#include "dnsinfo_private.h"
#ifdef SC_LOG_HANDLE
#include <os/log.h>
os_log_t SC_LOG_HANDLE(void);
#endif //SC_LOG_HANDLE
#pragma mark -
#pragma mark Globals
static libSC_info_server_t S_dns_info;
static _dns_sync_handler_t S_sync_handler = NULL;
#pragma mark -
#pragma mark Support functions
#pragma mark -
#pragma mark DNS configuration server
static dispatch_queue_t
_dnsinfo_server_queue()
{
static dispatch_once_t once;
static dispatch_queue_t q;
dispatch_once(&once, ^{
q = dispatch_queue_create(DNSINFO_SERVICE_NAME ".server", NULL);
});
return q;
}
static void
_dnsinfo_copy(xpc_connection_t connection, xpc_object_t request)
{
CFDataRef data;
uint64_t generation;
const char *proc_name;
xpc_connection_t remote;
xpc_object_t reply;
remote = xpc_dictionary_get_remote_connection(request);
reply = xpc_dictionary_create_reply(request);
if (reply == NULL) {
SC_log(LOG_ERR, "<%p> _dnsinfo_copy: xpc_dictionary_create_reply: failed",
connection);
return;
}
data = _libSC_info_server_get_data(&S_dns_info,
connection,
&generation);
proc_name = xpc_dictionary_get_string(request, DNSINFO_PROC_NAME);
if (proc_name == NULL) {
proc_name = "???";
}
SC_log(LOG_DEBUG, "<%p:%s[%d]> DNS configuration copy: %llu",
connection,
proc_name,
xpc_connection_get_pid(connection),
generation);
if (data != NULL) {
xpc_dictionary_set_data(reply,
DNSINFO_CONFIGURATION,
CFDataGetBytePtr(data),
CFDataGetLength(data));
}
xpc_connection_send_message(remote, reply);
xpc_release(reply);
return;
}
static void
_dnsinfo_acknowledge(xpc_connection_t connection, xpc_object_t request)
{
uint64_t generation;
Boolean inSync;
generation = xpc_dictionary_get_uint64(request, DNSINFO_GENERATION);
SC_log(LOG_DEBUG, "<%p:%d> DNS configuration ack: %llu",
connection,
xpc_connection_get_pid(connection),
generation);
(void) _libSC_info_server_acknowledged(&S_dns_info, connection, generation);
inSync = _libSC_info_server_in_sync(&S_dns_info);
if (S_sync_handler != NULL) {
S_sync_handler(inSync);
}
return;
}
static void
process_request(xpc_connection_t connection, xpc_object_t request)
{
int64_t op;
op = xpc_dictionary_get_int64(request, DNSINFO_REQUEST);
switch (op) {
case DNSINFO_REQUEST_COPY :
_dnsinfo_copy(connection, request);
break;
case DNSINFO_REQUEST_ACKNOWLEDGE :
_dnsinfo_acknowledge(connection, request);
break;
default :
SC_log(LOG_ERR, "<%p> unknown request : %lld",
connection,
op);
break;
}
return;
}
static void
process_new_connection(xpc_connection_t c)
{
SC_log(LOG_DEBUG, "<%p:%d> DNS configuration session: open",
c,
xpc_connection_get_pid(c));
_libSC_info_server_open(&S_dns_info, c);
xpc_connection_set_target_queue(c, _dnsinfo_server_queue());
xpc_connection_set_event_handler(c, ^(xpc_object_t xobj) {
xpc_type_t type;
type = xpc_get_type(xobj);
if (type == XPC_TYPE_DICTIONARY) {
process_request(c, xobj);
} else if (type == XPC_TYPE_ERROR) {
const char *desc;
desc = xpc_dictionary_get_string(xobj, XPC_ERROR_KEY_DESCRIPTION);
if (xobj == XPC_ERROR_CONNECTION_INVALID) {
Boolean changed;
SC_log(LOG_DEBUG, "<%p:%d> DNS configuration session: close",
c,
xpc_connection_get_pid(c));
changed = _libSC_info_server_close(&S_dns_info, c);
if (changed) {
Boolean inSync;
inSync = _libSC_info_server_in_sync(&S_dns_info);
if (S_sync_handler != NULL) {
S_sync_handler(inSync);
}
}
} else if (xobj == XPC_ERROR_CONNECTION_INTERRUPTED) {
SC_log(LOG_ERR, "<%p:%d> %s",
c,
xpc_connection_get_pid(c),
desc);
} else {
SC_log(LOG_ERR, "<%p:%d> Connection error: %p : %s",
c,
xpc_connection_get_pid(c),
xobj,
desc);
}
} else {
SC_log(LOG_ERR, "<%p:%d> unknown event type : %p",
c,
xpc_connection_get_pid(c),
type);
}
});
xpc_connection_resume(c);
return;
}
#pragma mark -
#pragma mark DNS configuration state
static void
add_state_handler()
{
#if !TARGET_OS_SIMULATOR
os_state_block_t state_block;
os_state_handle_t state_handle;
state_block = ^os_state_data_t(os_state_hints_t hints) {
#pragma unused(hints)
CFIndex dnsinfo_len;
os_state_data_t state_data;
size_t state_data_size;
dnsinfo_len = (S_dns_info.data != NULL) ? CFDataGetLength(S_dns_info.data) : 0;
state_data_size = OS_STATE_DATA_SIZE_NEEDED(dnsinfo_len);
if (state_data_size > MAX_STATEDUMP_SIZE) {
SC_log(LOG_ERR, "DNS configuration: state data too large (%zd > %zd)",
state_data_size,
(size_t)MAX_STATEDUMP_SIZE);
return NULL;
}
state_data = calloc(1, state_data_size);
if (state_data == NULL) {
SC_log(LOG_ERR, "DNS configuration: could not allocate state data");
return NULL;
}
state_data->osd_type = OS_STATE_DATA_CUSTOM;
state_data->osd_data_size = (uint32_t)dnsinfo_len;
strlcpy(state_data->osd_decoder.osdd_library,
"SystemConfiguration",
sizeof(state_data->osd_decoder.osdd_library));
strlcpy(state_data->osd_decoder.osdd_type,
"dnsinfo",
sizeof(state_data->osd_decoder.osdd_type));
strlcpy(state_data->osd_title, "DNS Configuration", sizeof(state_data->osd_title));
if (dnsinfo_len > 0) {
memcpy(state_data->osd_data, CFDataGetBytePtr(S_dns_info.data), dnsinfo_len);
}
return state_data;
};
state_handle = os_state_add_handler(_dnsinfo_server_queue(), state_block);
if (state_handle == 0) {
SC_log(LOG_ERR, "DNS configuration: os_state_add_handler() failed");
}
#endif // !TARGET_OS_SIMULATOR
return;
}
#pragma mark -
#pragma mark DNS configuration server SPIs
__private_extern__
void
load_DNSConfiguration(CFBundleRef bundle,
_dns_sync_handler_t syncHandler)
{
#pragma unused(bundle)
xpc_connection_t c;
const char *name;
_libSC_info_server_init(&S_dns_info);
add_state_handler();
S_sync_handler = Block_copy(syncHandler);
name = getenv(DNSINFO_SERVICE_NAME);
if (name == NULL) {
name = DNSINFO_SERVICE_NAME;
}
c = xpc_connection_create_mach_service(name,
_dnsinfo_server_queue(),
XPC_CONNECTION_MACH_SERVICE_LISTENER);
xpc_connection_set_event_handler(c, ^(xpc_object_t event) {
xpc_type_t type;
type = xpc_get_type(event);
if (type == XPC_TYPE_CONNECTION) {
process_new_connection(event);
} else if (type == XPC_TYPE_ERROR) {
const char *desc;
desc = xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION);
if (event == XPC_ERROR_CONNECTION_INVALID) {
SC_log(LOG_ERR, "DNS configuration server: %s", desc);
xpc_release(c);
} else if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
SC_log(LOG_ERR, "DNS configuration server: %s", desc);
} else {
SC_log(LOG_ERR, "DNS configuration server: Connection error: %p : %s",
event,
desc);
}
} else {
SC_log(LOG_ERR, "DNS configuration server: unknown event type : %p", type);
}
});
xpc_connection_resume(c);
SC_log(LOG_DEBUG, "XPC server \"%s\" started", name);
return;
}
__private_extern__
_Bool
_dns_configuration_store(dns_create_config_t *_config)
{
_dns_config_buf_t *config = (_dns_config_buf_t *)*_config;
Boolean in_sync;
uint64_t new_generation = 0;
CFDataRef new_dns_info = NULL;
const char *notify_key;
if (config != NULL) {
const UInt8 *bytes;
CFIndex len;
new_generation = config->config.generation;
SC_log(LOG_INFO, "DNS configuration updated: %llu",
new_generation);
bytes = (const UInt8 *)config;
len = sizeof(_dns_config_buf_t) + ntohl(config->n_attribute);
new_dns_info = CFDataCreate(NULL, bytes, len);
}
dispatch_sync(_dnsinfo_server_queue(), ^{
_libSC_info_server_set_data(&S_dns_info, new_dns_info, new_generation);
});
if (new_dns_info != NULL) {
CFRelease(new_dns_info);
}
in_sync = _libSC_info_server_in_sync(&S_dns_info);
if (S_sync_handler != NULL) {
S_sync_handler(in_sync);
}
notify_key = dns_configuration_notify_key();
if (notify_key != NULL) {
uint32_t status;
status = notify_post(notify_key);
if (status != NOTIFY_STATUS_OK) {
SC_log(LOG_ERR, "notify_post() failed: %d", status);
}
}
return TRUE;
}
#pragma mark -
#pragma mark Testing
#ifdef MAIN
int
main(int argc, char **argv)
{
static Boolean verbose = (argc > 1) ? TRUE : FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
_sc_debug = TRUE;
load_DNSConfiguration(CFBundleGetMainBundle(), ^(Boolean inSync) { SC_log(LOG_INFO,
"in sync: %s",
inSync ? "Yes" : "No")
});
CFRunLoopRun();
exit(0);
return 0;
}
#endif