1/* -*- Mode: C; tab-width: 4 -*- 2 * 3 * Copyright (c) 2007 Apple Inc. All rights reserved. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18#define _FORTIFY_SOURCE 2 19 20#include <CoreFoundation/CoreFoundation.h> 21#include <sys/cdefs.h> 22#include <sys/time.h> 23#include <sys/types.h> 24#include <mach/mach.h> 25#include <mach/mach_error.h> 26#include <servers/bootstrap.h> 27#include <asl.h> 28#include <launch.h> 29#include <pwd.h> 30#include <pthread.h> 31#include <stdarg.h> 32#include <stdbool.h> 33#include <stdio.h> 34#include <stdlib.h> 35#include <time.h> 36#include <unistd.h> 37#include <Security/Security.h> 38#include "helper.h" 39#include "helper-server.h" 40#include "helpermsg.h" 41#include "helpermsgServer.h" 42#include <vproc.h> 43 44#if TARGET_OS_EMBEDDED 45#define NO_SECURITYFRAMEWORK 1 46#endif 47 48#ifndef LAUNCH_JOBKEY_MACHSERVICES 49#define LAUNCH_JOBKEY_MACHSERVICES "MachServices" 50#define LAUNCH_DATA_MACHPORT 10 51#define launch_data_get_machport launch_data_get_fd 52#endif 53 54union max_msg_size 55{ 56 union __RequestUnion__proxy_helper_subsystem req; 57 union __ReplyUnion__proxy_helper_subsystem rep; 58}; 59 60static const mach_msg_size_t MAX_MSG_SIZE = sizeof(union max_msg_size) + MAX_TRAILER_SIZE; 61static aslclient logclient = NULL; 62static int opt_debug; 63static pthread_t idletimer_thread; 64 65unsigned long maxidle = 15; 66unsigned long actualidle = 3600; 67 68CFRunLoopRef gRunLoop = NULL; 69CFRunLoopTimerRef gTimer = NULL; 70 71mach_port_t gPort = MACH_PORT_NULL; 72 73static void helplogv(int level, const char *fmt, va_list ap) 74{ 75 if (NULL == logclient) { vfprintf(stderr, fmt, ap); fflush(stderr); } 76 else asl_vlog(logclient, NULL, level, fmt, ap); 77} 78 79void helplog(int level, const char *fmt, ...) 80{ 81 va_list ap; 82 va_start(ap, fmt); 83 helplogv(level, fmt, ap); 84 va_end(ap); 85} 86 87// for safe_vproc 88void LogMsgWithLevel(mDNSLogLevel_t logLevel, const char *fmt, ...) 89{ 90 (void)logLevel; 91 va_list ap; 92 va_start(ap, fmt); 93 // safe_vproc only calls LogMsg, so assume logLevel maps to ASL_LEVEL_ERR 94 helplog(ASL_LEVEL_ERR, fmt, ap); 95 va_end(ap); 96} 97 98static void handle_sigterm(int sig) 99{ 100 // debug("entry sig=%d", sig); Can't use syslog from within a signal handler 101 assert(sig == SIGTERM); 102 (void)proxy_mDNSExit(gPort); 103} 104 105static void initialize_logging(void) 106{ 107 logclient = asl_open(NULL, kmDNSHelperServiceName, (opt_debug ? ASL_OPT_STDERR : 0)); 108 if (NULL == logclient) { fprintf(stderr, "Could not initialize ASL logging.\n"); fflush(stderr); return; } 109 if (opt_debug) asl_set_filter(logclient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); 110} 111 112static void initialize_id(void) 113{ 114 static char login[] = "_mdnsresponder"; 115 struct passwd hardcode; 116 struct passwd *pwd = &hardcode; // getpwnam(login); 117 hardcode.pw_uid = 65; 118 hardcode.pw_gid = 65; 119 120 if (!pwd) { helplog(ASL_LEVEL_ERR, "Could not find account name `%s'. I will only help root.", login); return; } 121 mDNSResponderUID = pwd->pw_uid; 122 mDNSResponderGID = pwd->pw_gid; 123} 124 125static void diediedie(CFRunLoopTimerRef timer, void *context) 126{ 127 debug("entry %p %p %d", timer, context, maxidle); 128 assert(gTimer == timer); 129 if (maxidle) 130 (void)proxy_mDNSExit(gPort); 131} 132 133void pause_idle_timer(void) 134{ 135 debug("entry"); 136 assert(gTimer); 137 assert(gRunLoop); 138 CFRunLoopRemoveTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode); 139} 140 141void unpause_idle_timer(void) 142{ 143 debug("entry"); 144 assert(gRunLoop); 145 assert(gTimer); 146 CFRunLoopAddTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode); 147} 148 149void update_idle_timer(void) 150{ 151 debug("entry"); 152 assert(gTimer); 153 CFRunLoopTimerSetNextFireDate(gTimer, CFAbsoluteTimeGetCurrent() + actualidle); 154} 155 156static void *idletimer(void *context) 157{ 158 debug("entry context=%p", context); 159 gRunLoop = CFRunLoopGetCurrent(); 160 161 unpause_idle_timer(); 162 163 for (;;) 164 { 165 debug("Running CFRunLoop"); 166 CFRunLoopRun(); 167 sleep(1); 168 } 169 170 return NULL; 171} 172 173static int initialize_timer() 174{ 175 gTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + actualidle, actualidle, 0, 0, diediedie, NULL); 176 int err = 0; 177 178 debug("entry"); 179 if (0 != (err = pthread_create(&idletimer_thread, NULL, idletimer, NULL))) 180 helplog(ASL_LEVEL_ERR, "Could not start idletimer thread: %s", strerror(err)); 181 182 return err; 183} 184 185static mach_port_t register_service(const char *service_name) 186{ 187 mach_port_t port = MACH_PORT_NULL; 188 kern_return_t kr; 189 190 if (KERN_SUCCESS != (kr = bootstrap_check_in(bootstrap_port, (char *)service_name, &port))) 191 { 192 helplog(ASL_LEVEL_ERR, "bootstrap_check_in: %d %X %s", kr, kr, mach_error_string(kr)); 193 return MACH_PORT_NULL; 194 } 195 196 if (KERN_SUCCESS != (kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND))) 197 { 198 helplog(ASL_LEVEL_ERR, "mach_port_insert_right: %d %X %s", kr, kr, mach_error_string(kr)); 199 mach_port_deallocate(mach_task_self(), port); 200 return MACH_PORT_NULL; 201 } 202 203 return port; 204} 205 206int main(int ac, char *av[]) 207{ 208 char *p = NULL; 209 kern_return_t kr = KERN_FAILURE; 210 long n; 211 int ch; 212 mach_msg_header_t hdr; 213 214 while ((ch = getopt(ac, av, "dt:")) != -1) 215 switch (ch) 216 { 217 case 'd': opt_debug = 1; break; 218 case 't': 219 n = strtol(optarg, &p, 0); 220 if ('\0' == optarg[0] || '\0' != *p || n > LONG_MAX || n < 0) 221 { fprintf(stderr, "Invalid idle timeout: %s\n", optarg); exit(EXIT_FAILURE); } 222 maxidle = n; 223 break; 224 case '?': 225 default: 226 fprintf(stderr, "Usage: mDNSResponderHelper [-d] [-t maxidle]\n"); 227 exit(EXIT_FAILURE); 228 } 229 ac -= optind; 230 av += optind; 231 232 initialize_logging(); 233 helplog(ASL_LEVEL_INFO, "Starting"); 234 initialize_id(); 235 236#ifndef NO_SECURITYFRAMEWORK 237 // We should normally be running as a system daemon. However, that might not be the case in some scenarios (e.g. debugging). 238 // Explicitly ensure that our Keychain operations utilize the system domain. 239 if (opt_debug) SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); 240#endif 241 gPort = register_service(kmDNSHelperServiceName); 242 if (!gPort) 243 exit(EXIT_FAILURE); 244 245 if (maxidle) actualidle = maxidle; 246 247 signal(SIGTERM, handle_sigterm); 248 249 // We use BeginTransactionAtShutdown in the plist that ensures that we will 250 // receive a SIGTERM during shutdown rather than a SIGKILL. But launchd (due to some 251 // limitation) currently requires us to still start and end the transaction for 252 // its proper initialization. 253 vproc_transaction_t vt = vproc_transaction_begin(NULL); 254 if (vt) vproc_transaction_end(NULL, vt); 255 256 if (initialize_timer()) exit(EXIT_FAILURE); 257 for (n=0; n<100000; n++) if (!gRunLoop) usleep(100); 258 if (!gRunLoop) 259 { 260 helplog(ASL_LEVEL_ERR, "gRunLoop not set after waiting"); 261 exit(EXIT_FAILURE); 262 } 263 264 for(;;) 265 { 266 hdr.msgh_bits = 0; 267 hdr.msgh_local_port = gPort; 268 hdr.msgh_remote_port = MACH_PORT_NULL; 269 hdr.msgh_size = sizeof(hdr); 270 hdr.msgh_id = 0; 271 kr = mach_msg(&hdr, MACH_RCV_LARGE | MACH_RCV_MSG, 0, hdr.msgh_size, gPort, 0, 0); 272 if (MACH_RCV_TOO_LARGE != kr) 273 helplog(ASL_LEVEL_ERR, "main MACH_RCV_MSG error: %d %X %s", kr, kr, mach_error_string(kr)); 274 275 kr = mach_msg_server_once(helper_server, MAX_MSG_SIZE, gPort, 276 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)); 277 if (KERN_SUCCESS != kr) 278 { helplog(ASL_LEVEL_ERR, "mach_msg_server: %d %X %s", kr, kr, mach_error_string(kr)); exit(EXIT_FAILURE); } 279 280 } 281 exit(EXIT_SUCCESS); 282} 283 284// Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion 285// e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4" 286// To expand "version" to its value before making the string, use STRINGIFY(version) instead 287#define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) # s 288#define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) 289 290// For convenience when using the "strings" command, this is the last thing in the file 291// The "@(#) " pattern is a special prefix the "what" command looks for 292const char VersionString_SCCS[] = "@(#) mDNSResponderHelper " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; 293 294#if _BUILDING_XCODE_PROJECT_ 295// If the process crashes, then this string will be magically included in the automatically-generated crash log 296const char *__crashreporter_info__ = VersionString_SCCS + 5; 297asm (".desc ___crashreporter_info__, 0x10"); 298#endif 299