/* -*- Mode: C; tab-width: 4 -*- * * Copyright (c) 2007-2012 Apple Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _FORTIFY_SOURCE 2 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helper.h" #include "helper-server.h" #include #if TARGET_OS_EMBEDDED #define NO_SECURITYFRAMEWORK 1 #endif #ifndef LAUNCH_JOBKEY_MACHSERVICES #define LAUNCH_JOBKEY_MACHSERVICES "MachServices" #define LAUNCH_DATA_MACHPORT 10 #define launch_data_get_machport launch_data_get_fd #endif int mDNSHelperLogEnabled = 0; os_log_t log_handle = NULL; static dispatch_queue_t xpc_queue = NULL; static int opt_debug; static pthread_t idletimer_thread; unsigned long maxidle = 15; unsigned long actualidle = 3600; CFRunLoopRef gRunLoop = NULL; CFRunLoopTimerRef gTimer = NULL; static void handle_sigterm(int sig) { // os_log_debug(log_handle,"entry sig=%d", sig); Can't use syslog from within a signal handler assert(sig == SIGTERM); helper_exit(); } static void initialize_logging(void) { log_handle = os_log_create("com.apple.mDNSResponderHelper", "INFO"); if (!log_handle) { // OS_LOG_DEFAULT is the default logging object, if you are not creating a custom subsystem/category os_log_error(OS_LOG_DEFAULT, "Could NOT create log handle in mDNSResponderHelper"); } } static void initialize_id(void) { static char login[] = "_mdnsresponder"; struct passwd hardcode; struct passwd *pwd = &hardcode; // getpwnam(login); hardcode.pw_uid = 65; hardcode.pw_gid = 65; if (!pwd) { os_log(log_handle, "Could not find account name `%s'. I will only help root.", login); return; } mDNSResponderUID = pwd->pw_uid; mDNSResponderGID = pwd->pw_gid; } static void diediedie(CFRunLoopTimerRef timer, void *context) { os_log_info(log_handle, "entry %p %p %lu", timer, context, actualidle); assert(gTimer == timer); os_log_info(log_handle, "mDNSResponderHelper exiting after [%lu] seconds", actualidle); if (actualidle) helper_exit(); } void pause_idle_timer(void) { os_log_debug(log_handle,"entry"); assert(gTimer); assert(gRunLoop); CFRunLoopRemoveTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode); } void unpause_idle_timer(void) { os_log_debug(log_handle,"entry"); assert(gRunLoop); assert(gTimer); CFRunLoopAddTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode); } void update_idle_timer(void) { os_log_debug(log_handle,"entry"); assert(gTimer); CFRunLoopTimerSetNextFireDate(gTimer, CFAbsoluteTimeGetCurrent() + actualidle); } static void *idletimer(void *context) { os_log_debug(log_handle,"entry context=%p", context); gRunLoop = CFRunLoopGetMain(); unpause_idle_timer(); for (;;) { // os_log_debug(log_handle,"Running CFRunLoop"); CFRunLoopRun(); sleep(1); } return NULL; } static int initialize_timer() { gTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + actualidle, actualidle, 0, 0, diediedie, NULL); int err = 0; os_log_info(log_handle, "mDNSResponderHelper initialize_timer() started"); if (0 != (err = pthread_create(&idletimer_thread, NULL, idletimer, NULL))) os_log(log_handle, "Could not start idletimer thread: %s", strerror(err)); return err; } /* Reads the user's program arguments for mDNSResponderHelper For now we have only one option: mDNSHelperDebugLogging which is used to turn on mDNSResponderHelperLogging To turn ON mDNSResponderHelper Verbose Logging, 1] sudo defaults write /Library/Preferences/com.apple.mDNSResponderHelper.plist mDNSHelperDebugLogging -bool YES 2] sudo reboot To turn OFF mDNSResponderHelper Logging, 1] sudo defaults delete /Library/Preferences/com.apple.mDNSResponderHelper.plist To view the current options set, 1] plutil -p /Library/Preferences/com.apple.mDNSResponderHelper.plist OR 1] sudo defaults read /Library/Preferences/com.apple.mDNSResponderHelper.plist */ static mDNSBool HelperPrefsGetValueBool(CFStringRef key, mDNSBool defaultVal) { CFBooleanRef boolean; mDNSBool result = defaultVal; boolean = CFPreferencesCopyAppValue(key, kmDNSHelperProgramArgs); if (boolean) { if (CFGetTypeID(boolean) == CFBooleanGetTypeID()) result = CFBooleanGetValue(boolean) ? mDNStrue : mDNSfalse; CFRelease(boolean); } return result; } // Verify Client's Entitlement static mDNSBool check_entitlement(xpc_connection_t conn, const char *password) { mDNSBool entitled = mDNSfalse; xpc_object_t ent = xpc_connection_copy_entitlement_value(conn, password); if (ent) { if (xpc_get_type(ent) == XPC_TYPE_BOOL && xpc_bool_get_value(ent)) { entitled = mDNStrue; } xpc_release(ent); } else { os_log(log_handle, "client entitlement is NULL"); } if (!entitled) os_log(log_handle, "entitlement check failed -> client is missing entitlement!"); return entitled; } static void handle_request(xpc_object_t req) { mDNSu32 helper_mode = 0; int error_code = 0; xpc_connection_t remote_conn = xpc_dictionary_get_remote_connection(req); xpc_object_t response = xpc_dictionary_create_reply(req); // switch here based on dictionary to handle different requests from mDNSResponder if ((xpc_dictionary_get_uint64(req, kHelperMode))) { os_log_info(log_handle, "Getting mDNSResponder request mode"); helper_mode = (mDNSu32)(xpc_dictionary_get_uint64(req, kHelperMode)); } switch (helper_mode) { case bpf_request: { os_log_info(log_handle, "Calling new RequestBPF()"); RequestBPF(); break; } case set_name: { const char *old_name; const char *new_name; int pref_key = 0; pref_key = (int)(xpc_dictionary_get_uint64(req, kPrefsNameKey)); old_name = xpc_dictionary_get_string(req, kPrefsOldName); new_name = xpc_dictionary_get_string(req, kPrefsNewName); os_log_info(log_handle, "Calling new SetName() oldname: %s newname: %s key:%d", old_name, new_name, pref_key); PreferencesSetName(pref_key, old_name, new_name); break; } case p2p_packetfilter: { pfArray_t pfports; pfArray_t pfprotocols; const char *if_name; uint32_t cmd; uint32_t count; cmd = xpc_dictionary_get_uint64(req, "pf_opcode"); if_name = xpc_dictionary_get_string(req, "pf_ifname"); count = xpc_dictionary_get_uint64(req, "pf_count"); pfports[0] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port0"); pfports[1] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port1"); pfports[2] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port2"); pfports[3] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port3"); pfprotocols[0] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol0"); pfprotocols[1] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol1"); pfprotocols[2] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol2"); pfprotocols[3] = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol3"); os_log_info(log_handle,"Calling new PacketFilterControl()"); PacketFilterControl(cmd, if_name, count, pfports, pfprotocols); break; } case user_notify: { const char *title; const char *msg; title = xpc_dictionary_get_string(req, "notify_title"); msg = xpc_dictionary_get_string(req, "notify_msg"); os_log_info(log_handle,"Calling new UserNotify() title:%s msg:%s", title, msg); UserNotify(title, msg); break; } case power_req: { int key, interval; key = xpc_dictionary_get_uint64(req, "powerreq_key"); interval = xpc_dictionary_get_uint64(req, "powerreq_interval"); os_log_info(log_handle,"Calling new PowerRequest() key[%d] interval[%d]", key, interval); PowerRequest(key, interval, &error_code); break; } case send_wakepkt: { const char *ether_addr; const char *ip_addr; int iteration; unsigned int if_id; if_id = (unsigned int)xpc_dictionary_get_uint64(req, "interface_index"); ether_addr = xpc_dictionary_get_string(req, "ethernet_address"); ip_addr = xpc_dictionary_get_string(req, "ip_address"); iteration = (int)xpc_dictionary_get_uint64(req, "swp_iteration"); os_log_info(log_handle, "Calling new SendWakeupPacket() ether_addr[%s] ip_addr[%s] if_id[%d] iteration[%d]", ether_addr, ip_addr, if_id, iteration); SendWakeupPacket(if_id, ether_addr, ip_addr, iteration); break; } case set_localaddr_cacheentry: { int if_index, family; if_index = xpc_dictionary_get_uint64(req, "slace_ifindex"); family = xpc_dictionary_get_uint64(req, "slace_family"); const uint8_t* ip = xpc_dictionary_get_data(req, "slace_ip", NULL); const uint8_t* eth = xpc_dictionary_get_data(req, "slace_eth", NULL); os_log_info(log_handle, "Calling new SetLocalAddressCacheEntry() if_index[%d] family[%d] ", if_index, family); SetLocalAddressCacheEntry(if_index, family, ip, eth, &error_code); /* static int v6addr_to_string(const v6addr_t addr, char *buf, size_t buflen) { if (NULL == inet_ntop(AF_INET6, addr, buf, buflen)) { os_log(log_handle, "inet_ntop failed: %s", strerror(errno)); return -1; } else { return 0; } } ethaddr_t eth = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 } ; const uint8_t* slace_ip = NULL; v6addr_t addr_ipv6; size_t ip_len; slace_ip = xpc_dictionary_get_data(req, "slace_ip", &ip_len); if (slace_ip && (ip_len == sizeof(v6addr_t))) { os_log(log_handle, "mDNSResponderHelper: doing memcpy()"); memcpy(&addr_ipv6, slace_ip, ip_len); } char test_ipv6_str[46]; v6addr_to_string(addr_ipv6, test_ipv6_str, sizeof(test_ipv6_str)); os_log(log_handle, "mDNSResponderHelper: handle_request: set_localaddr_cacheentry: test_ipv6_str is %s", test_ipv6_str); */ break; } case send_keepalive: { uint16_t lport, rport, win; uint32_t seq, ack; lport = xpc_dictionary_get_uint64(req, "send_keepalive_lport"); rport = xpc_dictionary_get_uint64(req, "send_keepalive_rport"); seq = xpc_dictionary_get_uint64(req, "send_keepalive_seq"); ack = xpc_dictionary_get_uint64(req, "send_keepalive_ack"); win = xpc_dictionary_get_uint64(req, "send_keepalive_win"); const uint8_t* sadd6 = xpc_dictionary_get_data(req, "send_keepalive_sadd", NULL); const uint8_t* dadd6 = xpc_dictionary_get_data(req, "send_keepalive_dadd", NULL); os_log_info(log_handle, "helper-main: handle_request: send_keepalive: lport is[%d] rport is[%d] seq is[%d] ack is[%d] win is[%d]", lport, rport, seq, ack, win); SendKeepalive(sadd6, dadd6, lport, rport, seq, ack, win); break; } case retreive_tcpinfo: { uint16_t lport, rport; int family; uint32_t seq, ack; uint16_t win; int32_t intfid; lport = xpc_dictionary_get_uint64(req, "retreive_tcpinfo_lport"); rport = xpc_dictionary_get_uint64(req, "retreive_tcpinfo_rport"); family = xpc_dictionary_get_uint64(req, "retreive_tcpinfo_family"); const uint8_t* laddr = xpc_dictionary_get_data(req, "retreive_tcpinfo_laddr", NULL); const uint8_t* raddr = xpc_dictionary_get_data(req, "retreive_tcpinfo_raddr", NULL); os_log_info(log_handle, "helper-main: handle_request: retreive_tcpinfo: lport is[%d] rport is[%d] family is [%d]", lport, rport, family); RetrieveTCPInfo(family, laddr, lport, raddr, rport, &seq, &ack, &win, &intfid, &error_code); if (response) { xpc_dictionary_set_uint64(response, "retreive_tcpinfo_seq", seq); xpc_dictionary_set_uint64(response, "retreive_tcpinfo_ack", ack); xpc_dictionary_set_uint64(response, "retreive_tcpinfo_win", win); xpc_dictionary_set_uint64(response, "retreive_tcpinfo_ifid", intfid); } os_log_info(log_handle, "helper-main: handle_request: retreive_tcpinfo: seq is[%d] ack is[%d] win is [%d] intfid is [%d]", seq, ack, win, intfid); break; } case autotunnel_setkeys: { uint16_t lport, rport; int replace_del; const char *fqdnstr; lport = xpc_dictionary_get_uint64(req, "autotunnelsetkeys_lport"); rport = xpc_dictionary_get_uint64(req, "autotunnelsetkeys_rport"); replace_del = xpc_dictionary_get_uint64(req, "autotunnelsetkeys_repdel"); const uint8_t* local_inner = xpc_dictionary_get_data(req, "autotunnelsetkeys_localinner", NULL); const uint8_t* local_outer = xpc_dictionary_get_data(req, "autotunnelsetkeys_localouter", NULL); const uint8_t* remote_inner = xpc_dictionary_get_data(req, "autotunnelsetkeys_remoteinner", NULL); const uint8_t* remote_outer = xpc_dictionary_get_data(req, "autotunnelsetkeys_remoteouter", NULL); fqdnstr = xpc_dictionary_get_string(req, "autotunnelsetkeys_fqdnStr"); os_log_info(log_handle, "helper-main: handle_request: autotunnel_setkeys: lport is[%d] rport is[%d] replace_del is [%d]", lport, rport, replace_del); HelperAutoTunnelSetKeys(replace_del, local_inner, local_outer, lport, remote_inner, remote_outer, rport, fqdnstr, &error_code); break; } case keychain_getsecrets: { unsigned int num_sec = 0; unsigned long secrets = 0; unsigned int sec_cnt = 0; os_log_info(log_handle,"Calling new KeyChainGetSecrets()"); KeychainGetSecrets(&num_sec, &secrets, &sec_cnt, &error_code); if (response) { xpc_dictionary_set_uint64(response, "keychain_num_secrets", num_sec); xpc_dictionary_set_data(response, "keychain_secrets", (void *)secrets, sec_cnt); xpc_dictionary_set_uint64(response, "keychain_secrets_count", sec_cnt); } os_log_info(log_handle,"helper-main: handle_request: keychain_getsecrets: num_secrets is %d, secrets is %lu, secrets_Cnt is %d", num_sec, secrets, sec_cnt); if (secrets) vm_deallocate(mach_task_self(), secrets, sec_cnt); break; } default: { os_log(log_handle, "handle_request: Unrecognized mode!"); error_code = kHelperErr_UndefinedMode; break; } } // Return Response Status back to the client (essentially ACKing the request) if (response) { xpc_dictionary_set_uint64(response, kHelperReplyStatus, kHelperReply_ACK); xpc_dictionary_set_int64(response, kHelperErrCode, error_code); xpc_connection_send_message(remote_conn, response); xpc_release(response); } else { os_log(log_handle, "handle_requests: Response Dictionary could not be created!"); return; } } static void accept_client(xpc_connection_t conn) { int c_pid = xpc_connection_get_pid(conn); if (!(check_entitlement(conn, kHelperService))) { os_log(log_handle, "accept_client: Helper Client PID[%d] is missing Entitlement. Cancelling connection", c_pid); xpc_connection_cancel(conn); return; } xpc_retain(conn); xpc_connection_set_target_queue(conn, xpc_queue); xpc_connection_set_event_handler(conn, ^(xpc_object_t req_msg) { xpc_type_t type = xpc_get_type(req_msg); if (type == XPC_TYPE_DICTIONARY) { os_log_info(log_handle,"accept_client:conn:[%p] client[%d](mDNSResponder) requesting service", (void *) conn, c_pid); handle_request(req_msg); } else // We hit this case ONLY if Client Terminated Connection OR Crashed { os_log(log_handle, "accept_client:conn:[%p] client[%d](mDNSResponder) teared down the connection (OR Crashed)", (void *) conn, c_pid); // handle_termination(); xpc_release(conn); } }); xpc_connection_resume(conn); } static void init_helper_service(const char *service_name) { xpc_connection_t xpc_listener = xpc_connection_create_mach_service(service_name, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER); if (!xpc_listener || xpc_get_type(xpc_listener) != XPC_TYPE_CONNECTION) { os_log(log_handle, "init_helper_service: Error Creating XPC Listener for mDNSResponderHelperService !!"); return; } os_log_info(log_handle,"init_helper_service: XPC Listener for mDNSResponderHelperService Listening"); xpc_queue = dispatch_queue_create("com.apple.mDNSHelper.service_queue", NULL); xpc_connection_set_event_handler(xpc_listener, ^(xpc_object_t eventmsg) { xpc_type_t type = xpc_get_type(eventmsg); if (type == XPC_TYPE_CONNECTION) { os_log_info(log_handle,"init_helper_service: new mDNSResponderHelper Client %p", eventmsg); accept_client(eventmsg); } else if (type == XPC_TYPE_ERROR) // Ideally, we would never hit these cases below { os_log(log_handle, "init_helper_service: XPCError: %s", xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION)); return; } else { os_log(log_handle, "init_helper_service: Unknown EventMsg type"); return; } }); xpc_connection_resume(xpc_listener); } int main(int ac, char *av[]) { char *p = NULL; long n; int ch; while ((ch = getopt(ac, av, "dt:")) != -1) { switch (ch) { case 'd': opt_debug = 1; break; case 't': n = strtol(optarg, &p, 0); if ('\0' == optarg[0] || '\0' != *p || n > LONG_MAX || n < 0) { fprintf(stderr, "Invalid idle timeout: %s\n", optarg); exit(EXIT_FAILURE); } maxidle = n; break; case '?': default: fprintf(stderr, "Usage: mDNSResponderHelper [-d] [-t maxidle]\n"); exit(EXIT_FAILURE); } } ac -= optind; av += optind; (void)ac; // Unused (void)av; // Unused initialize_logging(); initialize_id(); mDNSHelperLogEnabled = HelperPrefsGetValueBool(kPreferencesKey_mDNSHelperLog, mDNSHelperLogEnabled); // Currently on Fuji/Whitetail releases we are keeping the logging always enabled. // Hence mDNSHelperLogEnabled is set to true below by default. mDNSHelperLogEnabled = 1; os_log_info(log_handle,"mDNSResponderHelper Starting to run"); #ifndef NO_SECURITYFRAMEWORK // We should normally be running as a system daemon. However, that might not be the case in some scenarios (e.g. debugging). // Explicitly ensure that our Keychain operations utilize the system domain. if (opt_debug) SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); #endif if (maxidle) actualidle = maxidle; signal(SIGTERM, handle_sigterm); if (initialize_timer()) exit(EXIT_FAILURE); for (n=0; n<100000; n++) if (!gRunLoop) usleep(100); if (!gRunLoop) { os_log(log_handle, "gRunLoop not set after waiting"); exit(EXIT_FAILURE); } init_helper_service(kHelperService); os_log_info(log_handle,"mDNSResponderHelper is now running"); dispatch_main(); } // Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion // e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4" // To expand "version" to its value before making the string, use STRINGIFY(version) instead #define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) # s #define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) // For convenience when using the "strings" command, this is the last thing in the file // The "@(#) " pattern is a special prefix the "what" command looks for const char VersionString_SCCS[] = "@(#) mDNSResponderHelper " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; #if _BUILDING_XCODE_PROJECT_ // If the process crashes, then this string will be magically included in the automatically-generated crash log const char *__crashreporter_info__ = VersionString_SCCS + 5; asm (".desc ___crashreporter_info__, 0x10"); #endif