| /* -*- Mode: C; tab-width: 4 -*- |
| * |
| * Copyright (c) 2007 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 |
| |
| // We set VERSION_MIN_REQUIRED to 10.4 to avoid "bootstrap_register is deprecated" warnings from bootstrap.h |
| #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_4 |
| |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <sys/cdefs.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <mach/mach.h> |
| #include <mach/mach_error.h> |
| #include <servers/bootstrap.h> |
| #include <asl.h> |
| #include <launch.h> |
| #include <pwd.h> |
| #include <pthread.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <Security/Security.h> |
| #include "helper.h" |
| #include "helper-server.h" |
| #include "helpermsg.h" |
| #include "helpermsgServer.h" |
| #include <vproc.h> |
| |
| #if TARGET_OS_EMBEDDED |
| #include <bootstrap_priv.h> |
| #define NO_SECURITYFRAMEWORK 1 |
| |
| #define bootstrap_register(A,B,C) bootstrap_register2((A),(B),(C),0) |
| #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 |
| |
| union max_msg_size |
| { |
| union __RequestUnion__proxy_helper_subsystem req; |
| union __ReplyUnion__proxy_helper_subsystem rep; |
| }; |
| |
| static const mach_msg_size_t MAX_MSG_SIZE = sizeof(union max_msg_size) + MAX_TRAILER_SIZE; |
| static aslclient logclient = NULL; |
| static int opt_debug; |
| static pthread_t idletimer_thread; |
| |
| unsigned long maxidle = 15; |
| unsigned long actualidle = 3600; |
| |
| CFRunLoopRef gRunLoop = NULL; |
| CFRunLoopTimerRef gTimer = NULL; |
| |
| mach_port_t gPort = MACH_PORT_NULL; |
| |
| static void helplogv(int level, const char *fmt, va_list ap) |
| { |
| if (NULL == logclient) { vfprintf(stderr, fmt, ap); fflush(stderr); } |
| else asl_vlog(logclient, NULL, level, fmt, ap); |
| } |
| |
| void helplog(int level, const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| helplogv(level, fmt, ap); |
| va_end(ap); |
| } |
| |
| // for safe_vproc |
| void LogMsgWithLevel(mDNSLogLevel_t logLevel, const char *fmt, ...) |
| { |
| (void)logLevel; |
| va_list ap; |
| va_start(ap, fmt); |
| // safe_vproc only calls LogMsg, so assume logLevel maps to ASL_LEVEL_ERR |
| helplog(ASL_LEVEL_ERR, fmt, ap); |
| va_end(ap); |
| } |
| |
| static void handle_sigterm(int sig) |
| { |
| // debug("entry sig=%d", sig); Can't use syslog from within a signal handler |
| assert(sig == SIGTERM); |
| (void)proxy_mDNSExit(gPort); |
| } |
| |
| static void initialize_logging(void) |
| { |
| logclient = asl_open(NULL, kmDNSHelperServiceName, (opt_debug ? ASL_OPT_STDERR : 0)); |
| if (NULL == logclient) { fprintf(stderr, "Could not initialize ASL logging.\n"); fflush(stderr); return; } |
| if (opt_debug) asl_set_filter(logclient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); |
| } |
| |
| 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) { helplog(ASL_LEVEL_ERR, "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) |
| { |
| debug("entry %p %p %d", timer, context, maxidle); |
| assert(gTimer == timer); |
| if (maxidle) |
| (void)proxy_mDNSExit(gPort); |
| } |
| |
| void pause_idle_timer(void) |
| { |
| debug("entry"); |
| assert(gTimer); |
| assert(gRunLoop); |
| CFRunLoopRemoveTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode); |
| } |
| |
| void unpause_idle_timer(void) |
| { |
| debug("entry"); |
| assert(gRunLoop); |
| assert(gTimer); |
| CFRunLoopAddTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode); |
| } |
| |
| void update_idle_timer(void) |
| { |
| debug("entry"); |
| assert(gTimer); |
| CFRunLoopTimerSetNextFireDate(gTimer, CFAbsoluteTimeGetCurrent() + actualidle); |
| } |
| |
| static void *idletimer(void *context) |
| { |
| debug("entry context=%p", context); |
| gRunLoop = CFRunLoopGetCurrent(); |
| |
| unpause_idle_timer(); |
| |
| for (;;) |
| { |
| debug("Running CFRunLoop"); |
| CFRunLoopRun(); |
| sleep(1); |
| } |
| |
| return NULL; |
| } |
| |
| static int initialize_timer() |
| { |
| gTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + actualidle, actualidle, 0, 0, diediedie, NULL); |
| int err = 0; |
| |
| debug("entry"); |
| if (0 != (err = pthread_create(&idletimer_thread, NULL, idletimer, NULL))) |
| helplog(ASL_LEVEL_ERR, "Could not start idletimer thread: %s", strerror(err)); |
| |
| return err; |
| } |
| |
| static mach_port_t checkin(char *service_name) |
| { |
| kern_return_t kr = KERN_SUCCESS; |
| mach_port_t port = MACH_PORT_NULL; |
| launch_data_t msg = NULL, reply = NULL, datum = NULL; |
| |
| if (NULL == (msg = launch_data_new_string(LAUNCH_KEY_CHECKIN))) |
| { helplog(ASL_LEVEL_ERR, "Could not create checkin message for launchd."); goto fin; } |
| if (NULL == (reply = launch_msg(msg))) |
| { helplog(ASL_LEVEL_ERR, "Could not message launchd."); goto fin; } |
| if (LAUNCH_DATA_ERRNO == launch_data_get_type(reply)) |
| { |
| if (launch_data_get_errno(reply) == EACCES) { launch_data_free(msg); launch_data_free(reply); return(MACH_PORT_NULL); } |
| helplog(ASL_LEVEL_ERR, "Launchd checkin failed: %s.", strerror(launch_data_get_errno(reply))); goto fin; |
| } |
| if (NULL == (datum = launch_data_dict_lookup(reply, LAUNCH_JOBKEY_MACHSERVICES)) || LAUNCH_DATA_DICTIONARY != launch_data_get_type(datum)) |
| { helplog(ASL_LEVEL_ERR, "Launchd reply does not contain %s dictionary.", LAUNCH_JOBKEY_MACHSERVICES); goto fin; } |
| if (NULL == (datum = launch_data_dict_lookup(datum, service_name)) || LAUNCH_DATA_MACHPORT != launch_data_get_type(datum)) |
| { helplog(ASL_LEVEL_ERR, "Launchd reply does not contain %s Mach port.", service_name); goto fin; } |
| if (MACH_PORT_NULL == (port = launch_data_get_machport(datum))) |
| { helplog(ASL_LEVEL_ERR, "Launchd gave me a null Mach port."); goto fin; } |
| if (KERN_SUCCESS != (kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND))) |
| { helplog(ASL_LEVEL_ERR, "mach_port_insert_right: %d %X %s", kr, kr, mach_error_string(kr)); goto fin; } |
| |
| fin: |
| if (NULL != msg) launch_data_free(msg); |
| if (NULL != reply) launch_data_free(reply); |
| if (MACH_PORT_NULL == port) exit(EXIT_FAILURE); |
| return port; |
| } |
| |
| static mach_port_t register_service(const char *service_name) |
| { |
| mach_port_t port = MACH_PORT_NULL; |
| kern_return_t kr; |
| |
| if (KERN_SUCCESS == (kr = bootstrap_check_in(bootstrap_port, (char *)service_name, &port))) |
| { |
| if (KERN_SUCCESS != (kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND))) |
| helplog(ASL_LEVEL_ERR, "mach_port_insert_right: %d %X %s", kr, kr, mach_error_string(kr)); |
| else |
| return port; |
| } |
| if (KERN_SUCCESS != (kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port))) |
| { helplog(ASL_LEVEL_ERR, "mach_port_allocate: %d %X %s", kr, kr, mach_error_string(kr)); goto error; } |
| if (KERN_SUCCESS != (kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND))) |
| { helplog(ASL_LEVEL_ERR, "mach_port_insert_right: %d %X %s", kr, kr, mach_error_string(kr)); goto error; } |
| |
| // XXX bootstrap_register does not modify its second argument, but the prototype does not include const. |
| if (KERN_SUCCESS != (kr = bootstrap_register(bootstrap_port, (char *)service_name, port))) |
| { helplog(ASL_LEVEL_ERR, "bootstrap_register failed: %s %d %X %s", service_name, kr, kr, mach_error_string(kr)); goto error; } |
| |
| return port; |
| error: |
| if (MACH_PORT_NULL != port) mach_port_deallocate(mach_task_self(), port); |
| return MACH_PORT_NULL; |
| } |
| |
| int main(int ac, char *av[]) |
| { |
| char *p = NULL; |
| kern_return_t kr = KERN_FAILURE; |
| long n; |
| int ch; |
| mach_msg_header_t hdr; |
| |
| 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; |
| |
| initialize_logging(); |
| helplog(ASL_LEVEL_INFO, "Starting"); |
| initialize_id(); |
| |
| #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 |
| gPort = checkin(kmDNSHelperServiceName); |
| if (!gPort) |
| { |
| helplog(ASL_LEVEL_ERR, "Launchd provided no launchdata; will open Mach port explicitly"); |
| gPort = register_service(kmDNSHelperServiceName); |
| } |
| |
| if (maxidle) actualidle = maxidle; |
| |
| signal(SIGTERM, handle_sigterm); |
| |
| // We use BeginTransactionAtShutdown in the plist that ensures that we will |
| // receive a SIGTERM during shutdown rather than a SIGKILL. But launchd (due to some |
| // limitation) currently requires us to still start and end the transaction for |
| // its proper initialization. |
| vproc_transaction_t vt = vproc_transaction_begin(NULL); |
| if (vt) vproc_transaction_end(NULL, vt); |
| |
| if (initialize_timer()) exit(EXIT_FAILURE); |
| for (n=0; n<100000; n++) if (!gRunLoop) usleep(100); |
| if (!gRunLoop) |
| { |
| helplog(ASL_LEVEL_ERR, "gRunLoop not set after waiting"); |
| exit(EXIT_FAILURE); |
| } |
| |
| for(;;) |
| { |
| hdr.msgh_bits = 0; |
| hdr.msgh_local_port = gPort; |
| hdr.msgh_remote_port = MACH_PORT_NULL; |
| hdr.msgh_size = sizeof(hdr); |
| hdr.msgh_id = 0; |
| kr = mach_msg(&hdr, MACH_RCV_LARGE | MACH_RCV_MSG, 0, hdr.msgh_size, gPort, 0, 0); |
| if (MACH_RCV_TOO_LARGE != kr) |
| helplog(ASL_LEVEL_ERR, "main MACH_RCV_MSG error: %d %X %s", kr, kr, mach_error_string(kr)); |
| |
| kr = mach_msg_server_once(helper_server, MAX_MSG_SIZE, gPort, |
| MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)); |
| if (KERN_SUCCESS != kr) |
| { helplog(ASL_LEVEL_ERR, "mach_msg_server: %d %X %s", kr, kr, mach_error_string(kr)); exit(EXIT_FAILURE); } |
| |
| } |
| exit(EXIT_SUCCESS); |
| } |
| |
| // 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 |