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