diff options
Diffstat (limited to 'mDNSResponder/mDNSMacOSX/helper.c')
-rw-r--r-- | mDNSResponder/mDNSMacOSX/helper.c | 2867 |
1 files changed, 2867 insertions, 0 deletions
diff --git a/mDNSResponder/mDNSMacOSX/helper.c b/mDNSResponder/mDNSMacOSX/helper.c new file mode 100644 index 00000000..0251e709 --- /dev/null +++ b/mDNSResponder/mDNSMacOSX/helper.c @@ -0,0 +1,2867 @@ +/* -*- 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. + */ + +#include <sys/cdefs.h> +#include <arpa/inet.h> +#include <bsm/libbsm.h> +#include <net/if.h> +#include <net/route.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <netinet6/in6_var.h> +#include <netinet6/nd6.h> +#include <netinet6/ipsec.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <asl.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <Security/Security.h> +#include <SystemConfiguration/SystemConfiguration.h> +#include <SystemConfiguration/SCPreferencesSetSpecific.h> +#include <TargetConditionals.h> +#include <IOKit/pwr_mgt/IOPMLib.h> +#include <net/bpf.h> +#include <sys/sysctl.h> + +#include "mDNSEmbeddedAPI.h" +#include "dns_sd.h" +#include "dnssd_ipc.h" +#include "libpfkey.h" +#include "helper.h" +#include "helpermsgServer.h" +#include "helper-server.h" +#include "ipsec_options.h" +#include "P2PPacketFilter.h" + +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#ifndef RTF_IFSCOPE +#define RTF_IFSCOPE 0x1000000 +#endif + +#if TARGET_OS_EMBEDDED +#ifndef MDNS_NO_IPSEC +#define MDNS_NO_IPSEC 1 +#endif +#define NO_CFUSERNOTIFICATION 1 +#define NO_SECURITYFRAMEWORK 1 +#endif + +// Embed the client stub code here, so we can access private functions like ConnectToServer, create_hdr, deliver_request +#include "../mDNSShared/dnssd_ipc.c" +#include "../mDNSShared/dnssd_clientstub.c" + +typedef struct sadb_x_policy *ipsec_policy_t; + +unsigned short InetChecksum(unsigned short *ptr,int nbytes); +unsigned long in_cksum(unsigned short *ptr,int nbytes); +void TCPCheckSum(int af, struct tcphdr *t, int tcplen, v6addr_t sadd6, v6addr_t dadd6); + +uid_t mDNSResponderUID; +gid_t mDNSResponderGID; + +void +debug_(const char *func, const char *fmt, ...) +{ + char buf[2048]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + helplog(ASL_LEVEL_DEBUG, "%s: %s", func, buf); +} + +static int +authorized(audit_token_t *token) +{ + int ok = 0; + pid_t pid = (pid_t)-1; + uid_t euid = (uid_t)-1; + + audit_token_to_au32(*token, NULL, &euid, NULL, NULL, NULL, &pid, NULL, + NULL); + ok = (euid == mDNSResponderUID || euid == 0); + if (!ok) + helplog(ASL_LEVEL_NOTICE, + "Unauthorized access by euid=%lu pid=%lu", + (unsigned long)euid, (unsigned long)pid); + return ok; +} + +kern_return_t +do_mDNSExit(__unused mach_port_t port, audit_token_t token) +{ + debug("entry"); + if (!authorized(&token)) + goto fin; + helplog(ASL_LEVEL_INFO, "exit"); + exit(0); + +fin: + debug("fin"); + return KERN_SUCCESS; +} + +kern_return_t do_mDNSRequestBPF(__unused mach_port_t port, audit_token_t token) +{ + if (!authorized(&token)) return KERN_SUCCESS; + DNSServiceRef ref; + DNSServiceErrorType err = ConnectToServer(&ref, 0, send_bpf, NULL, NULL, NULL); + if (err) { helplog(ASL_LEVEL_ERR, "do_mDNSRequestBPF: ConnectToServer %d", err); return err; } + + char *ptr; + size_t len = sizeof(DNSServiceFlags); + ipc_msg_hdr *hdr = create_hdr(send_bpf, &len, &ptr, 0, ref); + if (!hdr) { DNSServiceRefDeallocate(ref); return kDNSServiceErr_NoMemory; } + put_flags(0, &ptr); + deliver_request(hdr, ref); // Will free hdr for us + DNSServiceRefDeallocate(ref); + update_idle_timer(); + return KERN_SUCCESS; +} + +kern_return_t do_mDNSPowerRequest(__unused mach_port_t port, int key, int interval, int *err, audit_token_t token) +{ + *err = -1; + if (!authorized(&token)) { *err = kmDNSHelperNotAuthorized; goto fin; } + + CFArrayRef events = IOPMCopyScheduledPowerEvents(); + if (events) + { + int i; + CFIndex count = CFArrayGetCount(events); + for (i=0; i<count; i++) + { + CFDictionaryRef dict = CFArrayGetValueAtIndex(events, i); + CFStringRef id = CFDictionaryGetValue(dict, CFSTR(kIOPMPowerEventAppNameKey)); + if (CFEqual(id, CFSTR("mDNSResponderHelper"))) + { + CFDateRef EventTime = CFDictionaryGetValue(dict, CFSTR(kIOPMPowerEventTimeKey)); + CFStringRef EventType = CFDictionaryGetValue(dict, CFSTR(kIOPMPowerEventTypeKey)); + IOReturn result = IOPMCancelScheduledPowerEvent(EventTime, id, EventType); + //helplog(ASL_LEVEL_ERR, "Deleting old event %s"); + if (result) helplog(ASL_LEVEL_ERR, "IOPMCancelScheduledPowerEvent %d failed %d", i, result); + } + } + CFRelease(events); + } + + if (key < 0) // mDNSPowerRequest(-1,-1) means "clear any stale schedules" (see above) + *err = 0; + else if (key == 0) // mDNSPowerRequest(0, 0) means "sleep now" + { + IOReturn r = IOPMSleepSystem(IOPMFindPowerManagement(MACH_PORT_NULL)); + if (r) { usleep(100000); helplog(ASL_LEVEL_ERR, "IOPMSleepSystem %d", r); } + *err = r; + } + else if (key > 0) + { + CFDateRef w = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent() + interval); + if (w) + { + IOReturn r = IOPMSchedulePowerEvent(w, CFSTR("mDNSResponderHelper"), key ? CFSTR(kIOPMAutoWake) : CFSTR(kIOPMAutoSleep)); + if (r) { usleep(100000); helplog(ASL_LEVEL_ERR, "IOPMSchedulePowerEvent(%d) %d %x", interval, r, r); } + *err = r; + CFRelease(w); + } + } +fin: + update_idle_timer(); + return KERN_SUCCESS; +} + +kern_return_t do_mDNSSetLocalAddressCacheEntry(__unused mach_port_t port, int ifindex, int family, v6addr_t ip, ethaddr_t eth, int *err, audit_token_t token) +{ + #define IPv6FMTSTRING "%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X" + #define IPv6FMTARGS ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15] + #if 0 + if (family == 4) + helplog(ASL_LEVEL_ERR, "do_mDNSSetLocalAddressCacheEntry %d IPv%d %d.%d.%d.%d %02X:%02X:%02X:%02X:%02X:%02X", + ifindex, family, ip[0], ip[1], ip[2], ip[3], eth[0], eth[1], eth[2], eth[3], eth[4], eth[5]); + else + helplog(ASL_LEVEL_ERR, "do_mDNSSetLocalAddressCacheEntry %d IPv%d " IPv6FMTSTRING " %02X:%02X:%02X:%02X:%02X:%02X", + ifindex, family, IPv6FMTARGS, eth[0], eth[1], eth[2], eth[3], eth[4], eth[5]); + #endif + + *err = -1; + if (!authorized(&token)) { *err = kmDNSHelperNotAuthorized; goto fin; } + + static int s = -1, seq = 0; + if (s < 0) + { + s = socket(PF_ROUTE, SOCK_RAW, 0); + if (s < 0) helplog(ASL_LEVEL_ERR, "do_mDNSSetLocalAddressCacheEntry: socket(PF_ROUTE, SOCK_RAW, 0) failed %d (%s)", errno, strerror(errno)); + } + + if (s >= 0) + { + struct timeval tv; + gettimeofday(&tv, 0); + if (family == 4) + { + struct { struct rt_msghdr hdr; struct sockaddr_inarp dst; struct sockaddr_dl sdl; } rtmsg; + memset(&rtmsg, 0, sizeof(rtmsg)); + + rtmsg.hdr.rtm_msglen = sizeof(rtmsg); + rtmsg.hdr.rtm_version = RTM_VERSION; + rtmsg.hdr.rtm_type = RTM_ADD; + rtmsg.hdr.rtm_index = ifindex; + rtmsg.hdr.rtm_flags = RTF_HOST | RTF_STATIC | RTF_IFSCOPE; + rtmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY; + rtmsg.hdr.rtm_pid = 0; + rtmsg.hdr.rtm_seq = seq++; + rtmsg.hdr.rtm_errno = 0; + rtmsg.hdr.rtm_use = 0; + rtmsg.hdr.rtm_inits = RTV_EXPIRE; + rtmsg.hdr.rtm_rmx.rmx_expire = tv.tv_sec + 30; + + rtmsg.dst.sin_len = sizeof(rtmsg.dst); + rtmsg.dst.sin_family = AF_INET; + rtmsg.dst.sin_port = 0; + rtmsg.dst.sin_addr.s_addr = *(in_addr_t*)ip; + rtmsg.dst.sin_srcaddr.s_addr = 0; + rtmsg.dst.sin_tos = 0; + rtmsg.dst.sin_other = 0; + + rtmsg.sdl.sdl_len = sizeof(rtmsg.sdl); + rtmsg.sdl.sdl_family = AF_LINK; + rtmsg.sdl.sdl_index = ifindex; + rtmsg.sdl.sdl_type = IFT_ETHER; + rtmsg.sdl.sdl_nlen = 0; + rtmsg.sdl.sdl_alen = ETHER_ADDR_LEN; + rtmsg.sdl.sdl_slen = 0; + + // Target MAC address goes in rtmsg.sdl.sdl_data[0..5]; (See LLADDR() in /usr/include/net/if_dl.h) + memcpy(rtmsg.sdl.sdl_data, eth, sizeof(ethaddr_t)); + + int len = write(s, (char *)&rtmsg, sizeof(rtmsg)); + if (len < 0) + helplog(ASL_LEVEL_ERR, "do_mDNSSetLocalAddressCacheEntry: write(%d) interface %d address %d.%d.%d.%d seq %d result %d errno %d (%s)", + sizeof(rtmsg), ifindex, ip[0], ip[1], ip[2], ip[3], rtmsg.hdr.rtm_seq, len, errno, strerror(errno)); + len = read(s, (char *)&rtmsg, sizeof(rtmsg)); + if (len < 0 || rtmsg.hdr.rtm_errno) + helplog(ASL_LEVEL_ERR, "do_mDNSSetLocalAddressCacheEntry: read (%d) interface %d address %d.%d.%d.%d seq %d result %d errno %d (%s) %d", + sizeof(rtmsg), ifindex, ip[0], ip[1], ip[2], ip[3], rtmsg.hdr.rtm_seq, len, errno, strerror(errno), rtmsg.hdr.rtm_errno); + + *err = 0; + } + else + { + struct { struct rt_msghdr hdr; struct sockaddr_in6 dst; struct sockaddr_dl sdl; } rtmsg; + memset(&rtmsg, 0, sizeof(rtmsg)); + + rtmsg.hdr.rtm_msglen = sizeof(rtmsg); + rtmsg.hdr.rtm_version = RTM_VERSION; + rtmsg.hdr.rtm_type = RTM_ADD; + rtmsg.hdr.rtm_index = ifindex; + rtmsg.hdr.rtm_flags = RTF_HOST | RTF_STATIC | RTF_IFSCOPE; + rtmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY; + rtmsg.hdr.rtm_pid = 0; + rtmsg.hdr.rtm_seq = seq++; + rtmsg.hdr.rtm_errno = 0; + rtmsg.hdr.rtm_use = 0; + rtmsg.hdr.rtm_inits = RTV_EXPIRE; + rtmsg.hdr.rtm_rmx.rmx_expire = tv.tv_sec + 30; + + rtmsg.dst.sin6_len = sizeof(rtmsg.dst); + rtmsg.dst.sin6_family = AF_INET6; + rtmsg.dst.sin6_port = 0; + rtmsg.dst.sin6_flowinfo = 0; + rtmsg.dst.sin6_addr = *(struct in6_addr*)ip; + rtmsg.dst.sin6_scope_id = ifindex; + + rtmsg.sdl.sdl_len = sizeof(rtmsg.sdl); + rtmsg.sdl.sdl_family = AF_LINK; + rtmsg.sdl.sdl_index = ifindex; + rtmsg.sdl.sdl_type = IFT_ETHER; + rtmsg.sdl.sdl_nlen = 0; + rtmsg.sdl.sdl_alen = ETHER_ADDR_LEN; + rtmsg.sdl.sdl_slen = 0; + + // Target MAC address goes in rtmsg.sdl.sdl_data[0..5]; (See LLADDR() in /usr/include/net/if_dl.h) + memcpy(rtmsg.sdl.sdl_data, eth, sizeof(ethaddr_t)); + + int len = write(s, (char *)&rtmsg, sizeof(rtmsg)); + if (len < 0) + helplog(ASL_LEVEL_ERR, "do_mDNSSetLocalAddressCacheEntry: write(%d) interface %d address " IPv6FMTSTRING " seq %d result %d errno %d (%s)", + sizeof(rtmsg), ifindex, IPv6FMTARGS, rtmsg.hdr.rtm_seq, len, errno, strerror(errno)); + len = read(s, (char *)&rtmsg, sizeof(rtmsg)); + if (len < 0 || rtmsg.hdr.rtm_errno) + helplog(ASL_LEVEL_ERR, "do_mDNSSetLocalAddressCacheEntry: read (%d) interface %d address " IPv6FMTSTRING " seq %d result %d errno %d (%s) %d", + sizeof(rtmsg), ifindex, IPv6FMTARGS, rtmsg.hdr.rtm_seq, len, errno, strerror(errno), rtmsg.hdr.rtm_errno); + + *err = 0; + } + + } + +fin: + update_idle_timer(); + return KERN_SUCCESS; +} + +kern_return_t do_mDNSNotify(__unused mach_port_t port, const char *title, const char *msg, audit_token_t token) +{ + if (!authorized(&token)) return KERN_SUCCESS; + +#ifndef NO_CFUSERNOTIFICATION + static const char footer[] = "(Note: This message only appears on machines with 17.x.x.x IP addresses — i.e. at Apple — not on customer machines.)"; + CFStringRef alertHeader = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8); + CFStringRef alertBody = CFStringCreateWithCString(NULL, msg, kCFStringEncodingUTF8); + CFStringRef alertFooter = CFStringCreateWithCString(NULL, footer, kCFStringEncodingUTF8); + CFStringRef alertMessage = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@\r\r%@"), alertBody, alertFooter); + CFRelease(alertBody); + CFRelease(alertFooter); + int err = CFUserNotificationDisplayNotice(0.0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, alertHeader, alertMessage, NULL); + if (err) helplog(ASL_LEVEL_ERR, "CFUserNotificationDisplayNotice returned %d", err); + CFRelease(alertHeader); + CFRelease(alertMessage); +#else + (void)title; + (void)msg; +#endif /* NO_CFUSERNOTIFICATION */ + + update_idle_timer(); + return KERN_SUCCESS; +} + +char usercompname[MAX_DOMAIN_LABEL+1] = {0}; // the last computer name the user saw +char userhostname[MAX_DOMAIN_LABEL+1] = {0}; // the last local host name the user saw +char lastcompname[MAX_DOMAIN_LABEL+1] = {0}; // the last computer name saved to preferences +char lasthostname[MAX_DOMAIN_LABEL+1] = {0}; // the last local host name saved to preferences + +#ifndef NO_CFUSERNOTIFICATION +static CFStringRef CFS_OQ = NULL; +static CFStringRef CFS_CQ = NULL; +static CFStringRef CFS_Format = NULL; +static CFStringRef CFS_ComputerName = NULL; +static CFStringRef CFS_ComputerNameMsg = NULL; +static CFStringRef CFS_LocalHostName = NULL; +static CFStringRef CFS_LocalHostNameMsg = NULL; +static CFStringRef CFS_Problem = NULL; + +static CFUserNotificationRef gNotification = NULL; +static CFRunLoopSourceRef gNotificationRLS = NULL; + +static void NotificationCallBackDismissed(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) +{ + debug("entry"); + (void)responseFlags; // Unused + if (userNotification != gNotification) helplog(ASL_LEVEL_ERR, "NotificationCallBackDismissed: Wrong CFUserNotificationRef"); + if (gNotificationRLS) + { + // Caution: don't use CFRunLoopGetCurrent() here, because the currently executing thread may not be our "CFRunLoopRun" thread. + // We need to explicitly specify the desired CFRunLoop from which we want to remove this event source. + CFRunLoopRemoveSource(gRunLoop, gNotificationRLS, kCFRunLoopDefaultMode); + CFRelease(gNotificationRLS); + gNotificationRLS = NULL; + CFRelease(gNotification); + gNotification = NULL; + } + // By dismissing the alert, the user has conceptually acknowleged the rename. + // (e.g. the machine's name is now officially "computer-2.local", not "computer.local".) + // If we get *another* conflict, the new alert should refer to the 'old' name + // as now being "computer-2.local", not "computer.local" + usercompname[0] = 0; + userhostname[0] = 0; + lastcompname[0] = 0; + lasthostname[0] = 0; + update_idle_timer(); + unpause_idle_timer(); +} + +static void ShowNameConflictNotification(CFMutableArrayRef header, CFStringRef subtext) +{ + CFMutableDictionaryRef dictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (!dictionary) return; + + debug("entry"); + + CFDictionarySetValue(dictionary, kCFUserNotificationAlertHeaderKey, header); + CFDictionarySetValue(dictionary, kCFUserNotificationAlertMessageKey, subtext); + + CFURLRef urlRef = CFURLCreateWithFileSystemPath(NULL, CFSTR("/System/Library/CoreServices/mDNSResponder.bundle"), kCFURLPOSIXPathStyle, true); + if (urlRef) { CFDictionarySetValue(dictionary, kCFUserNotificationLocalizationURLKey, urlRef); CFRelease(urlRef); } + + if (gNotification) // If notification already on-screen, update it in place + CFUserNotificationUpdate(gNotification, 0, kCFUserNotificationCautionAlertLevel, dictionary); + else // else, we need to create it + { + SInt32 error; + gNotification = CFUserNotificationCreate(NULL, 0, kCFUserNotificationCautionAlertLevel, &error, dictionary); + if (!gNotification || error) { helplog(ASL_LEVEL_ERR, "ShowNameConflictNotification: CFUserNotificationRef: Error %d", error); return; } + gNotificationRLS = CFUserNotificationCreateRunLoopSource(NULL, gNotification, NotificationCallBackDismissed, 0); + if (!gNotificationRLS) { helplog(ASL_LEVEL_ERR,"ShowNameConflictNotification: RLS"); CFRelease(gNotification); gNotification = NULL; return; } + // Caution: don't use CFRunLoopGetCurrent() here, because the currently executing thread may not be our "CFRunLoopRun" thread. + // We need to explicitly specify the desired CFRunLoop to which we want to add this event source. + CFRunLoopAddSource(gRunLoop, gNotificationRLS, kCFRunLoopDefaultMode); + debug("gRunLoop=%p gNotification=%p gNotificationRLS=%p", gRunLoop, gNotification, gNotificationRLS); + pause_idle_timer(); + } + + CFRelease(dictionary); +} + +static CFMutableArrayRef GetHeader(const char* oldname, const char* newname, const CFStringRef msg, const char* suffix) +{ + CFMutableArrayRef alertHeader = NULL; + + const CFStringRef cfoldname = CFStringCreateWithCString(NULL, oldname, kCFStringEncodingUTF8); + // NULL newname means we've given up trying to construct a name that doesn't conflict + const CFStringRef cfnewname = newname ? CFStringCreateWithCString(NULL, newname, kCFStringEncodingUTF8) : NULL; + // We tag a zero-width non-breaking space at the end of the literal text to guarantee that, no matter what + // arbitrary computer name the user may choose, this exact text (with zero-width non-breaking space added) + // can never be one that occurs in the Localizable.strings translation file. + if (!cfoldname) + helplog(ASL_LEVEL_ERR,"Could not construct CFStrings for old=%s", newname); + else if (newname && !cfnewname) + helplog(ASL_LEVEL_ERR,"Could not construct CFStrings for new=%s", newname); + else + { + const CFStringRef s1 = CFStringCreateWithFormat(NULL, NULL, CFS_Format, cfoldname, suffix); + const CFStringRef s2 = cfnewname ? CFStringCreateWithFormat(NULL, NULL, CFS_Format, cfnewname, suffix) : NULL; + + alertHeader = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + + if (!s1) + helplog(ASL_LEVEL_ERR, "Could not construct secondary CFString for old=%s", oldname); + else if (cfnewname && !s2) + helplog(ASL_LEVEL_ERR, "Could not construct secondary CFString for new=%s", newname); + else if (!alertHeader) + helplog(ASL_LEVEL_ERR, "Could not construct CFArray for notification"); + else + { + // Make sure someone is logged in. We don't want this popping up over the login window + uid_t uid; + gid_t gid; + CFStringRef userName = SCDynamicStoreCopyConsoleUser(NULL, &uid, &gid); + if (userName) + { + CFRelease(userName); + CFArrayAppendValue(alertHeader, msg); // Opening phrase of message, provided by caller + CFArrayAppendValue(alertHeader, CFS_OQ); CFArrayAppendValue(alertHeader, s1); CFArrayAppendValue(alertHeader, CFS_CQ); + CFArrayAppendValue(alertHeader, CFSTR(" is already in use on this network. ")); + if (s2) + { + CFArrayAppendValue(alertHeader, CFSTR("The name has been changed to ")); + CFArrayAppendValue(alertHeader, CFS_OQ); CFArrayAppendValue(alertHeader, s2); CFArrayAppendValue(alertHeader, CFS_CQ); + CFArrayAppendValue(alertHeader, CFSTR(".")); + } + else + CFArrayAppendValue(alertHeader, CFSTR("All attempts to find an available name by adding a number to the name were also unsuccessful.")); + } + } + if (s1) CFRelease(s1); + if (s2) CFRelease(s2); + } + if (cfoldname) CFRelease(cfoldname); + if (cfnewname) CFRelease(cfnewname); + + return alertHeader; +} +#endif /* ndef NO_CFUSERNOTIFICATION */ + +static void update_notification(void) +{ +#ifndef NO_CFUSERNOTIFICATION + debug("entry ucn=%s, uhn=%s, lcn=%s, lhn=%s", usercompname, userhostname, lastcompname, lasthostname); + if (!CFS_OQ) + { + // Note: the "\xEF\xBB\xBF" byte sequence in the CFS_Format string is the UTF-8 encoding of the zero-width non-breaking space character. + // By appending this invisible character on the end of literal names, we ensure the these strings cannot inadvertently match any string + // in the localization file -- since we know for sure that none of our strings in the localization file contain the ZWNBS character. + // + // For languages that are written right to left, when we mix English (host names could be in english with brackets etc. and the + // rest in Arabic) we need unicode markups for proper formatting. The Unicode sequence 202C (UTF8 E2 80 AC), 200E (UTF8 E2 80 8E) and + // 202B (UTF8 E2 80 AB) helps with the formatting. See <rdar://problem/8629082> for more details. + CFS_OQ = CFStringCreateWithCString(NULL, "“\xE2\x80\xAB", kCFStringEncodingUTF8); + CFS_CQ = CFStringCreateWithCString(NULL, "\xE2\x80\xAC”", kCFStringEncodingUTF8); + CFS_Format = CFStringCreateWithCString(NULL, "%@%s\xEF\xBB\xBF\xE2\x80\x8E", kCFStringEncodingUTF8); + CFS_ComputerName = CFStringCreateWithCString(NULL, "The name of your computer ", kCFStringEncodingUTF8); + CFS_ComputerNameMsg = CFStringCreateWithCString(NULL, "To change the name of your computer, " + "open System Preferences and click Sharing, then type the name in the Computer Name field.", kCFStringEncodingUTF8); + CFS_LocalHostName = CFStringCreateWithCString(NULL, "This computer’s local hostname ", kCFStringEncodingUTF8); + CFS_LocalHostNameMsg = CFStringCreateWithCString(NULL, "To change the local hostname, " + "open System Preferences and click Sharing, then click “Edit” and type the name in the Local Hostname field.", kCFStringEncodingUTF8); + CFS_Problem = CFStringCreateWithCString(NULL, "This may indicate a problem with the local network. " + "Please inform your network administrator.", kCFStringEncodingUTF8); + } + + if (!usercompname[0] && !userhostname[0]) + { + if (gNotificationRLS) + { + debug("canceling notification %p", gNotification); + CFUserNotificationCancel(gNotification); + unpause_idle_timer(); + } + } + else + { + CFMutableArrayRef header = NULL; + CFStringRef* subtext = NULL; + if (userhostname[0] && !lasthostname[0]) // we've given up trying to construct a name that doesn't conflict + { + header = GetHeader(userhostname, NULL, CFS_LocalHostName, ".local"); + subtext = &CFS_Problem; + } + else if (usercompname[0]) + { + header = GetHeader(usercompname, lastcompname, CFS_ComputerName, ""); + subtext = &CFS_ComputerNameMsg; + } + else + { + header = GetHeader(userhostname, lasthostname, CFS_LocalHostName, ".local"); + subtext = &CFS_LocalHostNameMsg; + } + ShowNameConflictNotification(header, *subtext); + CFRelease(header); + } +#endif +} + +kern_return_t +do_mDNSPreferencesSetName(__unused mach_port_t port, int key, const char* old, const char* new, audit_token_t token) +{ + SCPreferencesRef session = NULL; + Boolean ok = FALSE; + Boolean locked = FALSE; + CFStringRef cfstr = NULL; + char* user = NULL; + char* last = NULL; + Boolean needUpdate = FALSE; + + debug("entry %s old=%s new=%s", key==kmDNSComputerName ? "ComputerName" : (key==kmDNSLocalHostName ? "LocalHostName" : "UNKNOWN"), old, new); + if (!authorized(&token)) goto fin; + + switch ((enum mDNSPreferencesSetNameKey)key) + { + case kmDNSComputerName: + user = usercompname; + last = lastcompname; + break; + case kmDNSLocalHostName: + user = userhostname; + last = lasthostname; + break; + default: + debug("unrecognized key: %d", key); + goto fin; + } + + if (!last) + { + helplog(ASL_LEVEL_ERR, "%s: no last ptr", __func__); + goto fin; + } + + if (!user) + { + helplog(ASL_LEVEL_ERR, "%s: no user ptr", __func__); + goto fin; + } + + if (0 == strncmp(old, new, MAX_DOMAIN_LABEL+1)) + { + // old and new are same means the config changed i.e, the user has set something in the preferences pane. + // This means the conflict has been resolved. We need to dismiss the dialogue. + if (last[0] && 0 != strncmp(last, new, MAX_DOMAIN_LABEL+1)) + { + last[0] = 0; + user[0] = 0; + needUpdate = TRUE; + } + goto fin; + } + else + { + // old and new are not same, this means there is a conflict. For the first conflict, we show + // the old value and the new value. For all subsequent conflicts, while the dialogue is still + // up, we do a real time update of the "new" value in the dialogue. That's why we update just + // "last" here and not "user". + if (strncmp(last, new, MAX_DOMAIN_LABEL+1)) + { + strncpy(last, new, MAX_DOMAIN_LABEL); + needUpdate = TRUE; + } + } + + // If we are not showing the dialogue, we need to remember the first "old" value so that + // we maintain the same through the lifetime of the dialogue. Subsequence conflicts don't + // update the "old" value. + if (!user[0]) + { + strncpy(user, old, MAX_DOMAIN_LABEL); + needUpdate = TRUE; + } + + if (!new[0]) // we've given up trying to construct a name that doesn't conflict + goto fin; + + cfstr = CFStringCreateWithCString(NULL, new, kCFStringEncodingUTF8); + + session = SCPreferencesCreate(NULL, CFSTR(kmDNSHelperServiceName), NULL); + + if (cfstr == NULL || session == NULL) + { + debug("SCPreferencesCreate failed"); + goto fin; + } + if (!SCPreferencesLock(session, 0)) + { + debug("lock failed"); + goto fin; + } + locked = TRUE; + + switch ((enum mDNSPreferencesSetNameKey)key) + { + case kmDNSComputerName: + { + // We want to write the new Computer Name to System Preferences, without disturbing the user-selected + // system-wide default character set used for things like AppleTalk NBP and NETBIOS service advertising. + // Note that this encoding is not used for the computer name, but since both are set by the same call, + // we need to take care to set the name without changing the character set. + CFStringEncoding encoding = kCFStringEncodingUTF8; + CFStringRef unused = SCDynamicStoreCopyComputerName(NULL, &encoding); + if (unused) { CFRelease(unused); unused = NULL; } + else encoding = kCFStringEncodingUTF8; + + ok = SCPreferencesSetComputerName(session, cfstr, encoding); + } + break; + case kmDNSLocalHostName: + ok = SCPreferencesSetLocalHostName(session, cfstr); + break; + default: + break; + } + + if (!ok || !SCPreferencesCommitChanges(session) || + !SCPreferencesApplyChanges(session)) + { + debug("SCPreferences update failed"); + goto fin; + } + debug("succeeded"); + +fin: + if (NULL != cfstr) + CFRelease(cfstr); + if (NULL != session) + { + if (locked) + SCPreferencesUnlock(session); + CFRelease(session); + } + update_idle_timer(); + if (needUpdate) update_notification(); + return KERN_SUCCESS; +} + +enum DNSKeyFormat +{ + formatNotDNSKey, formatDdnsTypeItem, formatDnsPrefixedServiceItem, formatBtmmPrefixedServiceItem +}; + +// On Mac OS X on Intel, the four-character string seems to be stored backwards, at least sometimes. +// I suspect some overenthusiastic inexperienced engineer said, "On Intel everything's backwards, +// therefore I need to add some byte swapping in this API to make this four-character string backwards too." +// To cope with this we allow *both* "ddns" and "sndd" as valid item types. + + +#ifndef NO_SECURITYFRAMEWORK +static const char btmmprefix[] = "btmmdns:"; +static const char dnsprefix[] = "dns:"; +static const char ddns[] = "ddns"; +static const char ddnsrev[] = "sndd"; + +static enum DNSKeyFormat +getDNSKeyFormat(SecKeychainItemRef item, SecKeychainAttributeList **attributesp) +{ + static UInt32 tags[4] = + { + kSecTypeItemAttr, kSecServiceItemAttr, kSecAccountItemAttr, kSecLabelItemAttr + }; + static SecKeychainAttributeInfo attributeInfo = + { + sizeof(tags)/sizeof(tags[0]), tags, NULL + }; + SecKeychainAttributeList *attributes = NULL; + enum DNSKeyFormat format; + Boolean malformed = FALSE; + OSStatus status = noErr; + int i = 0; + + *attributesp = NULL; + if (noErr != (status = SecKeychainItemCopyAttributesAndData(item, + &attributeInfo, NULL, &attributes, NULL, NULL))) + { + debug("SecKeychainItemCopyAttributesAndData %d - skipping", + status); + goto skip; + } + if (attributeInfo.count != attributes->count) + malformed = TRUE; + for (i = 0; !malformed && i < (int)attributeInfo.count; ++i) + if (attributeInfo.tag[i] != attributes->attr[i].tag) + malformed = TRUE; + if (malformed) + { + debug( + "malformed result from SecKeychainItemCopyAttributesAndData - skipping"); + goto skip; + } + + debug("entry (\"%.*s\", \"%.*s\", \"%.*s\")", + (int)attributes->attr[0].length, attributes->attr[0].data, + (int)attributes->attr[1].length, attributes->attr[1].data, + (int)attributes->attr[2].length, attributes->attr[2].data); + if (attributes->attr[1].length >= MAX_ESCAPED_DOMAIN_NAME + + sizeof(dnsprefix)-1) + { + debug("kSecServiceItemAttr too long (%u) - skipping", + (unsigned int)attributes->attr[1].length); + goto skip; + } + if (attributes->attr[2].length >= MAX_ESCAPED_DOMAIN_NAME) + { + debug("kSecAccountItemAttr too long (%u) - skipping", + (unsigned int)attributes->attr[2].length); + goto skip; + } + if (attributes->attr[1].length >= sizeof(dnsprefix)-1 && + 0 == strncasecmp(attributes->attr[1].data, dnsprefix, + sizeof(dnsprefix)-1)) + format = formatDnsPrefixedServiceItem; + else if (attributes->attr[1].length >= sizeof(btmmprefix)-1 && + 0 == strncasecmp(attributes->attr[1].data, btmmprefix, sizeof(btmmprefix)-1)) + format = formatBtmmPrefixedServiceItem; + else if (attributes->attr[0].length == sizeof(ddns)-1 && + 0 == strncasecmp(attributes->attr[0].data, ddns, sizeof(ddns)-1)) + format = formatDdnsTypeItem; + else if (attributes->attr[0].length == sizeof(ddnsrev)-1 && + 0 == strncasecmp(attributes->attr[0].data, ddnsrev, sizeof(ddnsrev)-1)) + format = formatDdnsTypeItem; + else + { + debug("uninterested in this entry"); + goto skip; + } + *attributesp = attributes; + debug("accepting this entry"); + return format; + +skip: + SecKeychainItemFreeAttributesAndData(attributes, NULL); + return formatNotDNSKey; +} + +// Insert the attributes as defined by mDNSKeyChainAttributes +static CFPropertyListRef +getKeychainItemInfo(SecKeychainItemRef item, + SecKeychainAttributeList *attributes, enum DNSKeyFormat format) +{ + CFMutableArrayRef entry = NULL; + CFDataRef data = NULL; + OSStatus status = noErr; + UInt32 keylen = 0; + void *keyp = 0; + + if (NULL == (entry = CFArrayCreateMutable(NULL, 0, + &kCFTypeArrayCallBacks))) + { + debug("CFArrayCreateMutable failed"); + goto error; + } + + // Insert the Account attribute (kmDNSKcWhere) + switch ((enum DNSKeyFormat)format) + { + case formatDdnsTypeItem: + data = CFDataCreate(kCFAllocatorDefault, + attributes->attr[1].data, attributes->attr[1].length); + break; + case formatDnsPrefixedServiceItem: + case formatBtmmPrefixedServiceItem: + data = CFDataCreate(kCFAllocatorDefault, + attributes->attr[1].data, attributes->attr[1].length); + break; + default: + assert("unknown DNSKeyFormat value"); + break; + } + if (NULL == data) + { + debug("CFDataCreate for attr[1] failed"); + goto error; + } + CFArrayAppendValue(entry, data); + CFRelease(data); + + // Insert the Where attribute (kmDNSKcAccount) + if (NULL == (data = CFDataCreate(kCFAllocatorDefault, + attributes->attr[2].data, attributes->attr[2].length))) + { + debug("CFDataCreate for attr[2] failed"); + goto error; + } + CFArrayAppendValue(entry, data); + CFRelease(data); + + // Insert the Key attribute (kmDNSKcKey) + if (noErr != (status = SecKeychainItemCopyAttributesAndData(item, NULL, + NULL, NULL, &keylen, &keyp))) + { + debug("could not retrieve key for \"%.*s\": %d", + (int)attributes->attr[1].length, attributes->attr[1].data, + status); + goto error; + } + data = CFDataCreate(kCFAllocatorDefault, keyp, keylen); + SecKeychainItemFreeAttributesAndData(NULL, keyp); + if (NULL == data) + { + debug("CFDataCreate for keyp failed"); + goto error; + } + CFArrayAppendValue(entry, data); + CFRelease(data); + + // Insert the Name attribute (kmDNSKcName) + if (NULL == (data = CFDataCreate(kCFAllocatorDefault, + attributes->attr[3].data, attributes->attr[3].length))) + { + debug("CFDataCreate for attr[3] failed"); + goto error; + } + CFArrayAppendValue(entry, data); + CFRelease(data); + return entry; + +error: + if (NULL != entry) + CFRelease(entry); + return NULL; +} +#endif + +kern_return_t +do_mDNSKeychainGetSecrets(__unused mach_port_t port, __unused unsigned int *numsecrets, + __unused vm_offset_t *secrets, __unused mach_msg_type_number_t *secretsCnt, __unused int *err, + __unused audit_token_t token) +{ +#ifndef NO_SECURITYFRAMEWORK + CFWriteStreamRef stream = NULL; + CFDataRef result = NULL; + CFPropertyListRef entry = NULL; + CFMutableArrayRef keys = NULL; + SecKeychainRef skc = NULL; + SecKeychainItemRef item = NULL; + SecKeychainSearchRef search = NULL; + SecKeychainAttributeList *attributes = NULL; + enum DNSKeyFormat format; + OSStatus status = 0; + + debug("entry"); + *err = 0; + *numsecrets = 0; + *secrets = (vm_offset_t)NULL; + if (!authorized(&token)) + { + *err = kmDNSHelperNotAuthorized; + goto fin; + } + if (NULL == (keys = CFArrayCreateMutable(NULL, 0, + &kCFTypeArrayCallBacks))) + { + debug("CFArrayCreateMutable failed"); + *err = kmDNSHelperCreationFailed; + goto fin; + } + if (noErr != (status = SecKeychainCopyDefault(&skc))) + { + *err = kmDNSHelperKeychainCopyDefaultFailed; + goto fin; + } + if (noErr != (status = SecKeychainSearchCreateFromAttributes(skc, kSecGenericPasswordItemClass, NULL, &search))) + { + *err = kmDNSHelperKeychainSearchCreationFailed; + goto fin; + } + for (status = SecKeychainSearchCopyNext(search, &item); + noErr == status; + status = SecKeychainSearchCopyNext(search, &item)) + { + if (formatNotDNSKey != (format = getDNSKeyFormat(item, + &attributes)) && + NULL != (entry = getKeychainItemInfo(item, attributes, + format))) + { + CFArrayAppendValue(keys, entry); + CFRelease(entry); + } + SecKeychainItemFreeAttributesAndData(attributes, NULL); + CFRelease(item); + } + if (errSecItemNotFound != status) + helplog(ASL_LEVEL_ERR, "%s: SecKeychainSearchCopyNext failed: %d", + __func__, status); + if (NULL == (stream = + CFWriteStreamCreateWithAllocatedBuffers(kCFAllocatorDefault, + kCFAllocatorDefault))) + { + *err = kmDNSHelperCreationFailed; + debug("CFWriteStreamCreateWithAllocatedBuffers failed"); + goto fin; + } + CFWriteStreamOpen(stream); + if (0 == CFPropertyListWriteToStream(keys, stream, + kCFPropertyListBinaryFormat_v1_0, NULL)) + { + *err = kmDNSHelperPListWriteFailed; + debug("CFPropertyListWriteToStream failed"); + goto fin; + } + result = CFWriteStreamCopyProperty(stream, + kCFStreamPropertyDataWritten); + if (KERN_SUCCESS != vm_allocate(mach_task_self(), secrets, + CFDataGetLength(result), VM_FLAGS_ANYWHERE)) + { + *err = kmDNSHelperCreationFailed; + debug("vm_allocate failed"); + goto fin; + } + CFDataGetBytes(result, CFRangeMake(0, CFDataGetLength(result)), + (void *)*secrets); + *secretsCnt = CFDataGetLength(result); + *numsecrets = CFArrayGetCount(keys); + debug("succeeded"); + +fin: + debug("returning %u secrets", *numsecrets); + if (NULL != stream) + { + CFWriteStreamClose(stream); + CFRelease(stream); + } + if (NULL != result) + CFRelease(result); + if (NULL != keys) + CFRelease(keys); + if (NULL != search) + CFRelease(search); + if (NULL != skc) + CFRelease(skc); + update_idle_timer(); + return KERN_SUCCESS; +#else + return KERN_FAILURE; +#endif +} + +#ifndef MDNS_NO_IPSEC +typedef enum _mDNSTunnelPolicyWhich +{ + kmDNSTunnelPolicySetup, + kmDNSTunnelPolicyTeardown, + kmDNSTunnelPolicyGenerate +} mDNSTunnelPolicyWhich; + +// For kmDNSTunnelPolicySetup, you can setup IPv6-in-IPv6 tunnel or IPv6-in-IPv4 tunnel +// kmDNSNoTunnel is used for other Policy types +typedef enum _mDNSTunnelType +{ + kmDNSNoTunnel, + kmDNSIPv6IPv4Tunnel, + kmDNSIPv6IPv6Tunnel +} mDNSTunnelType; + +static const uint8_t kWholeV6Mask = 128; + +#endif /* ifndef MDNS_NO_IPSEC */ + +#ifndef MDNS_NO_IPSEC + +static const char g_racoon_config_dir[] = "/var/run/racoon/"; +static const char g_racoon_config_dir_old[] = "/etc/racoon/remote/"; + +CF_EXPORT CFDictionaryRef _CFCopySystemVersionDictionary(void); +CF_EXPORT const CFStringRef _kCFSystemVersionBuildVersionKey; + +// Major version 6 is 10.2.x (Jaguar) +// Major version 7 is 10.3.x (Panther) +// Major version 8 is 10.4.x (Tiger) +// Major version 9 is 10.5.x (Leopard) +// Major version 10 is 10.6.x (SnowLeopard) +static int MacOSXSystemBuildNumber(char* letter_out, int* minor_out) +{ + int major = 0, minor = 0; + char letter = 0, buildver[256]="<Unknown>"; + CFDictionaryRef vers = _CFCopySystemVersionDictionary(); + if (vers) + { + CFStringRef cfbuildver = CFDictionaryGetValue(vers, _kCFSystemVersionBuildVersionKey); + if (cfbuildver) CFStringGetCString(cfbuildver, buildver, sizeof(buildver), kCFStringEncodingUTF8); + sscanf(buildver, "%d%c%d", &major, &letter, &minor); + CFRelease(vers); + } + else + helplog(ASL_LEVEL_NOTICE, "_CFCopySystemVersionDictionary failed"); + + if (!major) { major=10; letter = 'A'; minor = 190; helplog(ASL_LEVEL_NOTICE, "Note: No Major Build Version number found; assuming 10A190"); } + if (letter_out) *letter_out = letter; + if (minor_out) *minor_out = minor; + return(major); +} + +static int UseOldRacoon() +{ + static int g_oldRacoon = -1; + + if (g_oldRacoon == -1) + { + char letter = 0; + int minor = 0; + g_oldRacoon = (MacOSXSystemBuildNumber(&letter, &minor) < 10); + debug("%s", g_oldRacoon ? "old" : "new"); + } + + return g_oldRacoon; +} + +static int RacoonSignal() +{ + return UseOldRacoon() ? SIGHUP : SIGUSR1; +} + +static const char* GetRacoonConfigDir() +{ + return UseOldRacoon() ? g_racoon_config_dir_old : g_racoon_config_dir; +} + +static const char* GetOldRacoonConfigDir() +{ + return UseOldRacoon() ? NULL : g_racoon_config_dir_old; +} + +static const char racoon_config_file[] = "anonymous.conf"; +static const char racoon_config_file_orig[] = "anonymous.conf.orig"; + +static const char configHeader[] = "# BackToMyMac\n"; + +static int IsFamiliarRacoonConfiguration(const char* racoon_config_path) +{ + int fd = open(racoon_config_path, O_RDONLY); + debug("entry %s", racoon_config_path); + if (0 > fd) + { + helplog(ASL_LEVEL_NOTICE, "open \"%s\" failed: %s", racoon_config_path, strerror(errno)); + return 0; + } + else + { + char header[sizeof(configHeader)] = {0}; + ssize_t bytesRead = read(fd, header, sizeof(header)-1); + close(fd); + if (bytesRead != sizeof(header)-1) return 0; + return (0 == memcmp(header, configHeader, sizeof(header)-1)); + } +} + +static void +revertAnonymousRacoonConfiguration(const char* dir) +{ + if (!dir) return; + + debug("entry %s", dir); + + char racoon_config_path[64]; + strlcpy(racoon_config_path, dir, sizeof(racoon_config_path)); + strlcat(racoon_config_path, racoon_config_file, sizeof(racoon_config_path)); + + struct stat s; + int ret = stat(racoon_config_path, &s); + debug("stat(%s): %d errno=%d", racoon_config_path, ret, errno); + if (ret == 0) + { + if (IsFamiliarRacoonConfiguration(racoon_config_path)) + { + helplog(ASL_LEVEL_INFO, "\"%s\" looks familiar, unlinking", racoon_config_path); + unlink(racoon_config_path); + } + else + { + helplog(ASL_LEVEL_NOTICE, "\"%s\" does not look familiar, leaving in place", racoon_config_path); + return; + } + } + else if (errno != ENOENT) + { + helplog(ASL_LEVEL_NOTICE, "stat failed for \"%s\", leaving in place: %s", racoon_config_path, strerror(errno)); + return; + } + + char racoon_config_path_orig[64]; + strlcpy(racoon_config_path_orig, dir, sizeof(racoon_config_path_orig)); + strlcat(racoon_config_path_orig, racoon_config_file_orig, sizeof(racoon_config_path_orig)); + + ret = stat(racoon_config_path_orig, &s); + debug("stat(%s): %d errno=%d", racoon_config_path_orig, ret, errno); + if (ret == 0) + { + if (0 > rename(racoon_config_path_orig, racoon_config_path)) + helplog(ASL_LEVEL_NOTICE, "rename \"%s\" \"%s\" failed: %s", racoon_config_path_orig, racoon_config_path, strerror(errno)); + else + debug("reverted \"%s\" to \"%s\"", racoon_config_path_orig, racoon_config_path); + } + else if (errno != ENOENT) + { + helplog(ASL_LEVEL_NOTICE, "stat failed for \"%s\", leaving in place: %s", racoon_config_path_orig, strerror(errno)); + return; + } +} + +static void +moveAsideAnonymousRacoonConfiguration(const char* dir) +{ + if (!dir) return; + + debug("entry %s", dir); + + char racoon_config_path[64]; + strlcpy(racoon_config_path, dir, sizeof(racoon_config_path)); + strlcat(racoon_config_path, racoon_config_file, sizeof(racoon_config_path)); + + struct stat s; + int ret = stat(racoon_config_path, &s); + if (ret == 0) + { + if (IsFamiliarRacoonConfiguration(racoon_config_path)) + { + helplog(ASL_LEVEL_INFO, "\"%s\" looks familiar, unlinking", racoon_config_path); + unlink(racoon_config_path); + } + else + { + char racoon_config_path_orig[64]; + strlcpy(racoon_config_path_orig, dir, sizeof(racoon_config_path_orig)); + strlcat(racoon_config_path_orig, racoon_config_file_orig, sizeof(racoon_config_path_orig)); + if (0 > rename(racoon_config_path, racoon_config_path_orig)) // If we didn't write it, move it to the side so it can be reverted later + helplog(ASL_LEVEL_NOTICE, "rename \"%s\" to \"%s\" failed: %s", racoon_config_path, racoon_config_path_orig, strerror(errno)); + else + debug("successfully renamed \"%s\" to \"%s\"", racoon_config_path, racoon_config_path_orig); + } + } + else if (errno != ENOENT) + { + helplog(ASL_LEVEL_NOTICE, "stat failed for \"%s\", leaving in place: %s", racoon_config_path, strerror(errno)); + return; + } +} + +static int +ensureExistenceOfRacoonConfigDir(const char* const racoon_config_dir) +{ + struct stat s; + int ret = stat(racoon_config_dir, &s); + if (ret != 0) + { + if (errno != ENOENT) + { + helplog(ASL_LEVEL_ERR, "stat of \"%s\" failed (%d): %s", + racoon_config_dir, ret, strerror(errno)); + return -1; + } + else + { + ret = mkdir(racoon_config_dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret != 0) + { + helplog(ASL_LEVEL_ERR, "mkdir \"%s\" failed: %s", + racoon_config_dir, strerror(errno)); + return -1; + } + else + helplog(ASL_LEVEL_INFO, "created directory \"%s\"", racoon_config_dir); + } + } + else if (!(s.st_mode & S_IFDIR)) + { + helplog(ASL_LEVEL_ERR, "\"%s\" is not a directory!", + racoon_config_dir); + return -1; + } + + return 0; +} + +static int +createAnonymousRacoonConfiguration(const char *fqdn) +{ + static const char config1[] = + "remote anonymous {\n" + " exchange_mode aggressive;\n" + " doi ipsec_doi;\n" + " situation identity_only;\n" + " verify_identifier off;\n" + " generate_policy on;\n" + " shared_secret keychain_by_id \""; + static const char config2[] = + "\";\n" + " nonce_size 16;\n" + " lifetime time 15 min;\n" + " initial_contact on;\n" + " support_proxy on;\n" + " nat_traversal force;\n" + " proposal_check claim;\n" + " proposal {\n" + " encryption_algorithm aes;\n" + " hash_algorithm sha256;\n" + " authentication_method pre_shared_key;\n" + " dh_group 2;\n" + " lifetime time 15 min;\n" + " }\n" + " proposal {\n" + " encryption_algorithm aes;\n" + " hash_algorithm sha1;\n" + " authentication_method pre_shared_key;\n" + " dh_group 2;\n" + " lifetime time 15 min;\n" + " }\n" + "}\n\n" + "sainfo anonymous { \n" + " pfs_group 2;\n" + " lifetime time 10 min;\n" + " encryption_algorithm aes;\n" + " authentication_algorithm hmac_sha256,hmac_sha1;\n" + " compression_algorithm deflate;\n" + "}\n"; + char tmp_config_path[64]; + char racoon_config_path[64]; + const char* const racoon_config_dir = GetRacoonConfigDir(); + const char* const racoon_config_dir_old = GetOldRacoonConfigDir(); + int fd = -1; + + debug("entry"); + + if (0 > ensureExistenceOfRacoonConfigDir(racoon_config_dir)) + return -1; + + strlcpy(tmp_config_path, racoon_config_dir, sizeof(tmp_config_path)); + strlcat(tmp_config_path, "tmp.XXXXXX", sizeof(tmp_config_path)); + + fd = mkstemp(tmp_config_path); + + if (0 > fd) + { + helplog(ASL_LEVEL_ERR, "mkstemp \"%s\" failed: %s", + tmp_config_path, strerror(errno)); + return -1; + } + write(fd, configHeader, sizeof(configHeader)-1); + write(fd, config1, sizeof(config1)-1); + write(fd, fqdn, strlen(fqdn)); + write(fd, config2, sizeof(config2)-1); + close(fd); + + strlcpy(racoon_config_path, racoon_config_dir, sizeof(racoon_config_path)); + strlcat(racoon_config_path, racoon_config_file, sizeof(racoon_config_path)); + + moveAsideAnonymousRacoonConfiguration(racoon_config_dir_old); + moveAsideAnonymousRacoonConfiguration(racoon_config_dir); + + if (0 > rename(tmp_config_path, racoon_config_path)) + { + unlink(tmp_config_path); + helplog(ASL_LEVEL_ERR, "rename \"%s\" \"%s\" failed: %s", + tmp_config_path, racoon_config_path, strerror(errno)); + revertAnonymousRacoonConfiguration(racoon_config_dir_old); + revertAnonymousRacoonConfiguration(racoon_config_dir); + return -1; + } + + debug("successfully renamed \"%s\" \"%s\"", tmp_config_path, racoon_config_path); + return 0; +} + +static int +notifyRacoon(void) +{ + debug("entry"); + static const char racoon_pid_path[] = "/var/run/racoon.pid"; + char buf[] = "18446744073709551615"; /* largest 64-bit integer */ + char *p = NULL; + ssize_t n = 0; + unsigned long m = 0; + int fd = open(racoon_pid_path, O_RDONLY); + + if (0 > fd) + { + debug("open \"%s\" failed, and that's OK: %s", racoon_pid_path, + strerror(errno)); + return kmDNSHelperRacoonNotificationFailed; + } + n = read(fd, buf, sizeof(buf)-1); + close(fd); + if (1 > n) + { + debug("read of \"%s\" failed: %s", racoon_pid_path, + n == 0 ? "empty file" : strerror(errno)); + return kmDNSHelperRacoonNotificationFailed; + } + buf[n] = '\0'; + m = strtoul(buf, &p, 10); + if (*p != '\0' && !isspace(*p)) + { + debug("invalid PID \"%s\" (around '%c')", buf, *p); + return kmDNSHelperRacoonNotificationFailed; + } + if (2 > m) + { + debug("refusing to kill PID %lu", m); + return kmDNSHelperRacoonNotificationFailed; + } + if (0 != kill(m, RacoonSignal())) + { + debug("Could not signal racoon (%lu): %s", m, strerror(errno)); + return kmDNSHelperRacoonNotificationFailed; + } + debug("Sent racoon (%lu) signal %d", m, RacoonSignal()); + return 0; +} + +static void +closefds(int from) +{ + int fd = 0; + struct dirent entry, *entryp = NULL; + DIR *dirp = opendir("/dev/fd"); + + if (dirp == NULL) + { + /* fall back to the erroneous getdtablesize method */ + for (fd = from; fd < getdtablesize(); ++fd) + close(fd); + return; + } + while (0 == readdir_r(dirp, &entry, &entryp) && NULL != entryp) + { + fd = atoi(entryp->d_name); + if (fd >= from && fd != dirfd(dirp)) + close(fd); + } + closedir(dirp); +} + +static int +startRacoonOld(void) +{ + debug("entry"); + char * const racoon_args[] = { "/usr/sbin/racoon", "-e", NULL }; + ssize_t n = 0; + pid_t pid = 0; + int status = 0; + + if (0 == (pid = fork())) + { + closefds(0); + execve(racoon_args[0], racoon_args, NULL); + helplog(ASL_LEVEL_ERR, "execve of \"%s\" failed: %s", + racoon_args[0], strerror(errno)); + exit(2); + } + helplog(ASL_LEVEL_NOTICE, "racoon (pid=%lu) started", + (unsigned long)pid); + n = waitpid(pid, &status, 0); + if (-1 == n) + { + helplog(ASL_LEVEL_ERR, "Unexpected waitpid failure: %s", + strerror(errno)); + return kmDNSHelperRacoonStartFailed; + } + else if (pid != n) + { + helplog(ASL_LEVEL_ERR, "Unexpected waitpid return value %d", + (int)n); + return kmDNSHelperRacoonStartFailed; + } + else if (WIFSIGNALED(status)) + { + helplog(ASL_LEVEL_ERR, + "racoon (pid=%lu) terminated due to signal %d", + (unsigned long)pid, WTERMSIG(status)); + return kmDNSHelperRacoonStartFailed; + } + else if (WIFSTOPPED(status)) + { + helplog(ASL_LEVEL_ERR, + "racoon (pid=%lu) has stopped due to signal %d", + (unsigned long)pid, WSTOPSIG(status)); + return kmDNSHelperRacoonStartFailed; + } + else if (0 != WEXITSTATUS(status)) + { + helplog(ASL_LEVEL_ERR, + "racoon (pid=%lu) exited with status %d", + (unsigned long)pid, WEXITSTATUS(status)); + return kmDNSHelperRacoonStartFailed; + } + debug("racoon (pid=%lu) daemonized normally", (unsigned long)pid); + return 0; +} + +// constant and structure for the racoon control socket +#define VPNCTL_CMD_PING 0x0004 +typedef struct vpnctl_hdr_struct +{ + u_int16_t msg_type; + u_int16_t flags; + u_int32_t cookie; + u_int32_t reserved; + u_int16_t result; + u_int16_t len; +} vpnctl_hdr; + +static int +startRacoon(void) +{ + debug("entry"); + int fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (0 > fd) + { + helplog(ASL_LEVEL_ERR, "Could not create endpoint for racoon control socket: %d %s", + errno, strerror(errno)); + return kmDNSHelperRacoonStartFailed; + } + + struct sockaddr_un saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sun_family = AF_UNIX; + saddr.sun_len = sizeof(saddr); + static const char racoon_control_sock_path[] = "/var/run/vpncontrol.sock"; + strcpy(saddr.sun_path, racoon_control_sock_path); + int result = connect(fd, (struct sockaddr*) &saddr, saddr.sun_len); + if (0 > result) + { + helplog(ASL_LEVEL_ERR, "Could not connect racoon control socket %s: %d %s", + racoon_control_sock_path, errno, strerror(errno)); + return kmDNSHelperRacoonStartFailed; + } + + u_int32_t btmm_cookie = 0x4d4d5442; + vpnctl_hdr h = { htons(VPNCTL_CMD_PING), 0, btmm_cookie, 0, 0, 0 }; + size_t bytes = 0; + ssize_t ret = 0; + + while (bytes < sizeof(vpnctl_hdr)) + { + ret = write(fd, ((unsigned char*)&h)+bytes, sizeof(vpnctl_hdr) - bytes); + if (ret == -1) + { + helplog(ASL_LEVEL_ERR, "Could not write to racoon control socket: %d %s", + errno, strerror(errno)); + return kmDNSHelperRacoonStartFailed; + } + bytes += ret; + } + + int nfds = fd + 1; + fd_set fds; + int counter = 0; + struct timeval tv; + bytes = 0; + h.cookie = 0; + + for (counter = 0; counter < 100; counter++) + { + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv = (struct timeval){ 0, 10000 }; // 10 milliseconds * 100 iterations = 1 second max wait time + + result = select(nfds, &fds, (fd_set*)NULL, (fd_set*)NULL, &tv); + if (result > 0) + { + if (FD_ISSET(fd, &fds)) + { + ret = read(fd, ((unsigned char*)&h)+bytes, sizeof(vpnctl_hdr) - bytes); + + if (ret == -1) + { + helplog(ASL_LEVEL_ERR, "Could not read from racoon control socket: %d %s", + strerror(errno)); + break; + } + bytes += ret; + if (bytes >= sizeof(vpnctl_hdr)) break; + } + else + { + debug("select returned but fd_isset not on expected fd\n"); + } + } + else if (result < 0) + { + debug("select returned %d errno %d %s\n", result, errno, strerror(errno)); + if (errno != EINTR) break; + } + } + + close(fd); + + if (bytes < sizeof(vpnctl_hdr) || h.cookie != btmm_cookie) return kmDNSHelperRacoonStartFailed; + + debug("racoon started"); + return 0; +} + +static int +kickRacoon(void) +{ + if ( 0 == notifyRacoon() ) + return 0; + return UseOldRacoon() ? startRacoonOld() : startRacoon(); +} + +#endif /* ndef MDNS_NO_IPSEC */ + +int +do_mDNSConfigureServer(__unused mach_port_t port, int updown, const char *fqdn, audit_token_t token) +{ +#ifndef MDNS_NO_IPSEC + debug("entry"); + if (!authorized(&token)) goto fin; + + switch ((enum mDNSUpDown)updown) + { + case kmDNSUp: + if (0 != createAnonymousRacoonConfiguration(fqdn)) goto fin; + break; + case kmDNSDown: + revertAnonymousRacoonConfiguration(GetOldRacoonConfigDir()); + revertAnonymousRacoonConfiguration(GetRacoonConfigDir()); + break; + default: + goto fin; + } + + if (0 != kickRacoon()) + goto fin; + debug("succeeded"); + +fin: +#else + (void)port; (void)updown; (void)fqdn; (void)token; +#endif + update_idle_timer(); + return KERN_SUCCESS; +} + +#ifndef MDNS_NO_IPSEC + +static unsigned int routeSeq = 1; + +static int +setupTunnelRoute(v6addr_t local, v6addr_t remote) +{ + struct + { + struct rt_msghdr hdr; + struct sockaddr_in6 dst; + struct sockaddr_in6 gtwy; + } msg; + int err = 0; + int s = -1; + + if (0 > (s = socket(PF_ROUTE, SOCK_RAW, AF_INET))) + { + helplog(ASL_LEVEL_ERR, "socket(PF_ROUTE, ...) failed: %s", + strerror(errno)); + err = kmDNSHelperRoutingSocketCreationFailed; + goto fin; + } + memset(&msg, 0, sizeof(msg)); + msg.hdr.rtm_msglen = sizeof(msg); + msg.hdr.rtm_type = RTM_ADD; + /* The following flags are set by `route add -inet6 -host ...` */ + msg.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_HOST | RTF_STATIC; + msg.hdr.rtm_version = RTM_VERSION; + msg.hdr.rtm_seq = routeSeq++; + msg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY; + msg.hdr.rtm_inits = RTV_MTU; + msg.hdr.rtm_rmx.rmx_mtu = 1280; + + msg.dst.sin6_len = sizeof(msg.dst); + msg.dst.sin6_family = AF_INET6; + memcpy(&msg.dst.sin6_addr, remote, sizeof(msg.dst.sin6_addr)); + + msg.gtwy.sin6_len = sizeof(msg.gtwy); + msg.gtwy.sin6_family = AF_INET6; + memcpy(&msg.gtwy.sin6_addr, local, sizeof(msg.gtwy.sin6_addr)); + + /* send message, ignore error when route already exists */ + if (0 > write(s, &msg, msg.hdr.rtm_msglen)) + { + int errno_ = errno; + + debug("write to routing socket failed: %s", strerror(errno_)); + if (EEXIST != errno_) + { + err = kmDNSHelperRouteAdditionFailed; + goto fin; + } + } + +fin: + if (0 <= s) + close(s); + return err; +} + +static int +teardownTunnelRoute(v6addr_t remote) +{ + struct + { + struct rt_msghdr hdr; + struct sockaddr_in6 dst; + } msg; + int err = 0; + int s = -1; + + if (0 > (s = socket(PF_ROUTE, SOCK_RAW, AF_INET))) + { + helplog(ASL_LEVEL_ERR, "socket(PF_ROUTE, ...) failed: %s", + strerror(errno)); + err = kmDNSHelperRoutingSocketCreationFailed; + goto fin; + } + memset(&msg, 0, sizeof(msg)); + + msg.hdr.rtm_msglen = sizeof(msg); + msg.hdr.rtm_type = RTM_DELETE; + msg.hdr.rtm_version = RTM_VERSION; + msg.hdr.rtm_seq = routeSeq++; + msg.hdr.rtm_addrs = RTA_DST; + + msg.dst.sin6_len = sizeof(msg.dst); + msg.dst.sin6_family = AF_INET6; + memcpy(&msg.dst.sin6_addr, remote, sizeof(msg.dst.sin6_addr)); + if (0 > write(s, &msg, msg.hdr.rtm_msglen)) + { + int errno_ = errno; + + debug("write to routing socket failed: %s", strerror(errno_)); + if (ESRCH != errno_) + { + err = kmDNSHelperRouteDeletionFailed; + goto fin; + } + } + +fin: + if (0 <= s) + close(s); + return err; +} + +static int +v4addr_to_string(v4addr_t addr, char *buf, size_t buflen) +{ + if (NULL == inet_ntop(AF_INET, addr, buf, buflen)) + { + helplog(ASL_LEVEL_ERR, "inet_ntop failed: %s", + strerror(errno)); + return kmDNSHelperInvalidNetworkAddress; + } + else + return 0; +} + +static int +v6addr_to_string(v6addr_t addr, char *buf, size_t buflen) +{ + if (NULL == inet_ntop(AF_INET6, addr, buf, buflen)) + { + helplog(ASL_LEVEL_ERR, "inet_ntop failed: %s", + strerror(errno)); + return kmDNSHelperInvalidNetworkAddress; + } + else + return 0; +} + +/* Caller owns object returned in `policy' */ +static int +generateTunnelPolicy(mDNSTunnelPolicyWhich which, mDNSTunnelType type, int in, + v4addr_t src, uint16_t src_port, + v4addr_t dst, uint16_t dst_port, + v6addr_t src6, v6addr_t dst6, + ipsec_policy_t *policy, size_t *len) +{ + char srcs[INET_ADDRSTRLEN], dsts[INET_ADDRSTRLEN]; + char srcs6[INET6_ADDRSTRLEN], dsts6[INET6_ADDRSTRLEN]; + char buf[512]; + char *inOut = in ? "in" : "out"; + ssize_t n = 0; + int err = 0; + + *policy = NULL; + *len = 0; + + switch (which) + { + case kmDNSTunnelPolicySetup: + if (type == kmDNSIPv6IPv4Tunnel) + { + if (0 != (err = v4addr_to_string(src, srcs, sizeof(srcs)))) + goto fin; + if (0 != (err = v4addr_to_string(dst, dsts, sizeof(dsts)))) + goto fin; + n = snprintf(buf, sizeof(buf), + "%s ipsec esp/tunnel/%s[%u]-%s[%u]/require", + inOut, srcs, src_port, dsts, dst_port); + } + else if (type == kmDNSIPv6IPv6Tunnel) + { + if (0 != (err = v6addr_to_string(src6, srcs6, sizeof(srcs6)))) + goto fin; + if (0 != (err = v6addr_to_string(dst6, dsts6, sizeof(dsts6)))) + goto fin; + n = snprintf(buf, sizeof(buf), + "%s ipsec esp/tunnel/%s-%s/require", + inOut, srcs6, dsts6); + } + break; + case kmDNSTunnelPolicyTeardown: + n = strlcpy(buf, inOut, sizeof(buf)); + break; + case kmDNSTunnelPolicyGenerate: + n = snprintf(buf, sizeof(buf), "%s generate", inOut); + break; + default: + err = kmDNSHelperIPsecPolicyCreationFailed; + goto fin; + } + + if (n >= (int)sizeof(buf)) + { + err = kmDNSHelperResultTooLarge; + goto fin; + } + + debug("policy=\"%s\"", buf); + if (NULL == (*policy = (ipsec_policy_t)ipsec_set_policy(buf, n))) + { + helplog(ASL_LEVEL_ERR, + "Could not create IPsec policy from \"%s\"", buf); + err = kmDNSHelperIPsecPolicyCreationFailed; + goto fin; + } + *len = ((ipsec_policy_t)(*policy))->sadb_x_policy_len * 8; + +fin: + return err; +} + +static int +sendPolicy(int s, int setup, + struct sockaddr *src, uint8_t src_bits, + struct sockaddr *dst, uint8_t dst_bits, + ipsec_policy_t policy, size_t len) +{ + static unsigned int policySeq = 0; + int err = 0; + + debug("entry, setup=%d", setup); + if (setup) + err = pfkey_send_spdadd(s, src, src_bits, dst, dst_bits, -1, + (char *)policy, len, policySeq++); + else + err = pfkey_send_spddelete(s, src, src_bits, dst, dst_bits, -1, + (char *)policy, len, policySeq++); + if (0 > err) + { + helplog(ASL_LEVEL_ERR, "Could not set IPsec policy: %s", + ipsec_strerror()); + err = kmDNSHelperIPsecPolicySetFailed; + goto fin; + } + else + err = 0; + debug("succeeded"); + +fin: + return err; +} + +static int +removeSA(int s, struct sockaddr *src, struct sockaddr *dst) +{ + int err = 0; + + debug("entry"); + err = pfkey_send_delete_all(s, SADB_SATYPE_ESP, IPSEC_MODE_ANY, src, dst); + if (0 > err) + { + helplog(ASL_LEVEL_ERR, "Could not remove IPsec SA: %s", ipsec_strerror()); + err = kmDNSHelperIPsecRemoveSAFailed; + goto fin; + } + err = pfkey_send_delete_all(s, SADB_SATYPE_ESP, IPSEC_MODE_ANY, dst, src); + if (0 > err) + { + helplog(ASL_LEVEL_ERR, "Could not remove IPsec SA: %s", ipsec_strerror()); + err = kmDNSHelperIPsecRemoveSAFailed; + goto fin; + } + else + err = 0; + + debug("succeeded"); + +fin: + return err; +} + +static int +doTunnelPolicy(mDNSTunnelPolicyWhich which, mDNSTunnelType type, + v6addr_t loc_inner, uint8_t loc_bits, + v4addr_t loc_outer, uint16_t loc_port, + v6addr_t rmt_inner, uint8_t rmt_bits, + v4addr_t rmt_outer, uint16_t rmt_port, + v6addr_t loc_outer6, v6addr_t rmt_outer6) +{ + struct sockaddr_in6 sin6_loc; + struct sockaddr_in6 sin6_rmt; + ipsec_policy_t policy = NULL; + size_t len = 0; + int s = -1; + int err = 0; + + debug("entry"); + if (0 > (s = pfkey_open())) + { + helplog(ASL_LEVEL_ERR, + "Could not create IPsec policy socket: %s", + ipsec_strerror()); + err = kmDNSHelperIPsecPolicySocketCreationFailed; + goto fin; + } + + memset(&sin6_loc, 0, sizeof(sin6_loc)); + sin6_loc.sin6_len = sizeof(sin6_loc); + sin6_loc.sin6_family = AF_INET6; + sin6_loc.sin6_port = htons(0); + memcpy(&sin6_loc.sin6_addr, loc_inner, sizeof(sin6_loc.sin6_addr)); + + memset(&sin6_rmt, 0, sizeof(sin6_rmt)); + sin6_rmt.sin6_len = sizeof(sin6_rmt); + sin6_rmt.sin6_family = AF_INET6; + sin6_rmt.sin6_port = htons(0); + memcpy(&sin6_rmt.sin6_addr, rmt_inner, sizeof(sin6_rmt.sin6_addr)); + + int setup = which != kmDNSTunnelPolicyTeardown; + + if (0 != (err = generateTunnelPolicy(which, type, 1, + rmt_outer, rmt_port, + loc_outer, loc_port, + rmt_outer6, loc_outer6, + &policy, &len))) + goto fin; + if (0 != (err = sendPolicy(s, setup, + (struct sockaddr *)&sin6_rmt, rmt_bits, + (struct sockaddr *)&sin6_loc, loc_bits, + policy, len))) + goto fin; + if (NULL != policy) + { + free(policy); + policy = NULL; + } + if (0 != (err = generateTunnelPolicy(which, type, 0, + loc_outer, loc_port, + rmt_outer, rmt_port, + loc_outer6, rmt_outer6, + &policy, &len))) + goto fin; + if (0 != (err = sendPolicy(s, setup, + (struct sockaddr *)&sin6_loc, loc_bits, + (struct sockaddr *)&sin6_rmt, rmt_bits, + policy, len))) + goto fin; + + if (which == kmDNSTunnelPolicyTeardown) + { + if (rmt_port) // Outer tunnel is IPv4 + { + if (loc_outer && rmt_outer) + { + struct sockaddr_in sin_loc; + struct sockaddr_in sin_rmt; + memset(&sin_loc, 0, sizeof(sin_loc)); + sin_loc.sin_len = sizeof(sin_loc); + sin_loc.sin_family = AF_INET; + memcpy(&sin_loc.sin_addr, loc_outer, sizeof(sin_loc.sin_addr)); + + memset(&sin_rmt, 0, sizeof(sin_rmt)); + sin_rmt.sin_len = sizeof(sin_rmt); + sin_rmt.sin_family = AF_INET; + memcpy(&sin_rmt.sin_addr, rmt_outer, sizeof(sin_rmt.sin_addr)); + if (0 != (err = removeSA(s, (struct sockaddr *)&sin_loc, (struct sockaddr *)&sin_rmt))) + goto fin; + } + } + else + { + if (loc_outer6 && rmt_outer6) + { + struct sockaddr_in6 sin6_lo; + struct sockaddr_in6 sin6_rm; + + memset(&sin6_lo, 0, sizeof(sin6_lo)); + sin6_lo.sin6_len = sizeof(sin6_lo); + sin6_lo.sin6_family = AF_INET6; + memcpy(&sin6_lo.sin6_addr, loc_outer6, sizeof(sin6_lo.sin6_addr)); + + memset(&sin6_rm, 0, sizeof(sin6_rm)); + sin6_rm.sin6_len = sizeof(sin6_rm); + sin6_rm.sin6_family = AF_INET6; + memcpy(&sin6_rm.sin6_addr, rmt_outer6, sizeof(sin6_rm.sin6_addr)); + if (0 != (err = removeSA(s, (struct sockaddr *)&sin6_lo, (struct sockaddr *)&sin6_rm))) + goto fin; + } + } + } + + + debug("succeeded"); + +fin: + if (s >= 0) + pfkey_close(s); + if (NULL != policy) + free(policy); + return err; +} + +#endif /* ndef MDNS_NO_IPSEC */ + +int +do_mDNSAutoTunnelSetKeys(__unused mach_port_t port, int replacedelete, + v6addr_t loc_inner, v6addr_t loc_outer6, uint16_t loc_port, + v6addr_t rmt_inner, v6addr_t rmt_outer6, uint16_t rmt_port, + const char *id, int *err, audit_token_t token) +{ +#ifndef MDNS_NO_IPSEC + static const char config[] = + "%s" + "remote %s [%u] {\n" + " disconnect_on_idle idle_timeout 600 idle_direction idle_outbound;\n" + " exchange_mode aggressive;\n" + " doi ipsec_doi;\n" + " situation identity_only;\n" + " verify_identifier off;\n" + " generate_policy on;\n" + " my_identifier user_fqdn \"%s\";\n" + " shared_secret keychain \"%s\";\n" + " nonce_size 16;\n" + " lifetime time 15 min;\n" + " initial_contact on;\n" + " support_proxy on;\n" + " nat_traversal force;\n" + " proposal_check claim;\n" + " proposal {\n" + " encryption_algorithm aes;\n" + " hash_algorithm sha256;\n" + " authentication_method pre_shared_key;\n" + " dh_group 2;\n" + " lifetime time 15 min;\n" + " }\n" + " proposal {\n" + " encryption_algorithm aes;\n" + " hash_algorithm sha1;\n" + " authentication_method pre_shared_key;\n" + " dh_group 2;\n" + " lifetime time 15 min;\n" + " }\n" + "}\n\n" + "sainfo address %s any address %s any {\n" + " pfs_group 2;\n" + " lifetime time 10 min;\n" + " encryption_algorithm aes;\n" + " authentication_algorithm hmac_sha256,hmac_sha1;\n" + " compression_algorithm deflate;\n" + "}\n\n" + "sainfo address %s any address %s any {\n" + " pfs_group 2;\n" + " lifetime time 10 min;\n" + " encryption_algorithm aes;\n" + " authentication_algorithm hmac_sha256,hmac_sha1;\n" + " compression_algorithm deflate;\n" + "}\n"; + char path[PATH_MAX] = ""; + char li[INET6_ADDRSTRLEN], lo[INET_ADDRSTRLEN], lo6[INET6_ADDRSTRLEN], + ri[INET6_ADDRSTRLEN], ro[INET_ADDRSTRLEN], ro6[INET6_ADDRSTRLEN]; + FILE *fp = NULL; + int fd = -1; + char tmp_path[PATH_MAX] = ""; + v4addr_t loc_outer, rmt_outer; + + debug("entry"); + *err = 0; + if (!authorized(&token)) + { + *err = kmDNSHelperNotAuthorized; + goto fin; + } + switch ((enum mDNSAutoTunnelSetKeysReplaceDelete)replacedelete) + { + case kmDNSAutoTunnelSetKeysReplace: + case kmDNSAutoTunnelSetKeysDelete: + break; + default: + *err = kmDNSHelperInvalidTunnelSetKeysOperation; + goto fin; + } + + if (0 != (*err = v6addr_to_string(loc_inner, li, sizeof(li)))) + goto fin; + if (0 != (*err = v6addr_to_string(rmt_inner, ri, sizeof(ri)))) + goto fin; + + debug("loc_inner=%s rmt_inner=%s", li, ri); + if (!rmt_port) + { + loc_outer[0] = loc_outer[1] = loc_outer[2] = loc_outer[3] = 0; + rmt_outer[0] = rmt_outer[1] = rmt_outer[2] = rmt_outer[3] = 0; + + if (0 != (*err = v6addr_to_string(loc_outer6, lo6, sizeof(lo6)))) + goto fin; + if (0 != (*err = v6addr_to_string(rmt_outer6, ro6, sizeof(ro6)))) + goto fin; + debug("IPv6 outer tunnel: loc_outer6=%s rmt_outer6=%s", lo6, ro6); + if ((int)sizeof(path) <= snprintf(path, sizeof(path), + "%s%s.conf", GetRacoonConfigDir(), ro6)) + { + *err = kmDNSHelperResultTooLarge; + goto fin; + } + } + else + { + loc_outer[0] = loc_outer6[0]; + loc_outer[1] = loc_outer6[1]; + loc_outer[2] = loc_outer6[2]; + loc_outer[3] = loc_outer6[3]; + + rmt_outer[0] = rmt_outer6[0]; + rmt_outer[1] = rmt_outer6[1]; + rmt_outer[2] = rmt_outer6[2]; + rmt_outer[3] = rmt_outer6[3]; + + if (0 != (*err = v4addr_to_string(loc_outer, lo, sizeof(lo)))) + goto fin; + if (0 != (*err = v4addr_to_string(rmt_outer, ro, sizeof(ro)))) + goto fin; + debug("IPv4 outer tunnel: loc_outer=%s loc_port=%u rmt_outer=%s rmt_port=%u", + lo, loc_port, ro, rmt_port); + + if ((int)sizeof(path) <= snprintf(path, sizeof(path), + "%s%s.%u.conf", GetRacoonConfigDir(), ro, + rmt_port)) + { + *err = kmDNSHelperResultTooLarge; + goto fin; + } + } + + + + if (kmDNSAutoTunnelSetKeysReplace == replacedelete) + { + if (0 > ensureExistenceOfRacoonConfigDir(GetRacoonConfigDir())) + { + *err = kmDNSHelperRacoonConfigCreationFailed; + goto fin; + } + if ((int)sizeof(tmp_path) <= + snprintf(tmp_path, sizeof(tmp_path), "%s.XXXXXX", path)) + { + *err = kmDNSHelperResultTooLarge; + goto fin; + } + if (0 > (fd = mkstemp(tmp_path))) + { + helplog(ASL_LEVEL_ERR, "mkstemp \"%s\" failed: %s", + tmp_path, strerror(errno)); + *err = kmDNSHelperRacoonConfigCreationFailed; + goto fin; + } + if (NULL == (fp = fdopen(fd, "w"))) + { + helplog(ASL_LEVEL_ERR, "fdopen: %s", + strerror(errno)); + *err = kmDNSHelperRacoonConfigCreationFailed; + goto fin; + } + fd = -1; + fprintf(fp, config, configHeader, (!rmt_port ? ro6 : ro), rmt_port, id, id, ri, li, li, ri); + fclose(fp); + fp = NULL; + if (0 > rename(tmp_path, path)) + { + helplog(ASL_LEVEL_ERR, + "rename \"%s\" \"%s\" failed: %s", + tmp_path, path, strerror(errno)); + *err = kmDNSHelperRacoonConfigCreationFailed; + goto fin; + } + } + else + { + if (0 != unlink(path)) + debug("unlink \"%s\" failed: %s", path, + strerror(errno)); + } + + if (0 != (*err = doTunnelPolicy(kmDNSTunnelPolicyTeardown, kmDNSNoTunnel, + loc_inner, kWholeV6Mask, loc_outer, loc_port, + rmt_inner, kWholeV6Mask, rmt_outer, rmt_port, loc_outer6, rmt_outer6))) + goto fin; + if (kmDNSAutoTunnelSetKeysReplace == replacedelete && + 0 != (*err = doTunnelPolicy(kmDNSTunnelPolicySetup, (!rmt_port ? kmDNSIPv6IPv6Tunnel : kmDNSIPv6IPv4Tunnel), + loc_inner, kWholeV6Mask, loc_outer, loc_port, + rmt_inner, kWholeV6Mask, rmt_outer, rmt_port, loc_outer6, rmt_outer6))) + goto fin; + + if (0 != (*err = teardownTunnelRoute(rmt_inner))) + goto fin; + if (kmDNSAutoTunnelSetKeysReplace == replacedelete && + 0 != (*err = setupTunnelRoute(loc_inner, rmt_inner))) + goto fin; + + if (kmDNSAutoTunnelSetKeysReplace == replacedelete && + 0 != (*err = kickRacoon())) + goto fin; + + debug("succeeded"); + +fin: + if (NULL != fp) + fclose(fp); + if (0 <= fd) + close(fd); + unlink(tmp_path); +#else + (void)replacedelete; (void)loc_inner; (void)loc_outer6; (void)loc_port; (void)rmt_inner; + (void)rmt_outer6; (void)rmt_port; (void)id; (void)token; + + *err = kmDNSHelperIPsecDisabled; +#endif /* MDNS_NO_IPSEC */ + update_idle_timer(); + return KERN_SUCCESS; +} + +kern_return_t +do_mDNSSendWakeupPacket(__unused mach_port_t port, unsigned ifid, const char *eth_addr, const char *ip_addr, int iteration, audit_token_t token) +{ + int bpf_fd, i, j; + struct ifreq ifr; + char ifname[IFNAMSIZ]; + char packet[512]; + char *ptr = packet; + char bpf_device[12]; + struct ether_addr *ea; + (void) ip_addr; // unused + (void) iteration; // unused + (void) token; // unused + + if (if_indextoname(ifid, ifname) == NULL) + { + helplog(ASL_LEVEL_ERR, "do_mDNSSendWakeupPacket invalid interface index %u", ifid); + return errno; + } + + ea = ether_aton(eth_addr); + if (ea == NULL) + { + helplog(ASL_LEVEL_ERR, "do_mDNSSendWakeupPacket invalid ethernet address %s", eth_addr); + return errno; + } + + for (i = 0; i < 100; i++) + { + snprintf(bpf_device, sizeof(bpf_device), "/dev/bpf%d", i); + bpf_fd = open(bpf_device, O_RDWR, 0); + if (bpf_fd == -1) + continue; + else break; + } + + if (bpf_fd == -1) + { + helplog(ASL_LEVEL_ERR, "do_mDNSSendWakeupPacket cannot find a bpf device"); + return ENXIO; + } + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if (ioctl(bpf_fd, BIOCSETIF, (char *)&ifr) < 0) + { + helplog(ASL_LEVEL_ERR, "do_mDNSSendWakeupPacket BIOCSETIF failed %s", strerror(errno)); + return errno; + } + + // 0x00 Destination address + for (i=0; i<6; i++) *ptr++ = ea->octet[i]; + + // 0x06 Source address (Note: Since we don't currently set the BIOCSHDRCMPLT option, BPF will fill in the real interface address for us) + for (i=0; i<6; i++) *ptr++ = 0; + + // 0x0C Ethertype (0x0842) + *ptr++ = 0x08; + *ptr++ = 0x42; + + // 0x0E Wakeup sync sequence + for (i=0; i<6; i++) *ptr++ = 0xFF; + + // 0x14 Wakeup data + for (j=0; j<16; j++) for (i=0; i<6; i++) *ptr++ = ea->octet[i]; + + // 0x74 Password + for (i=0; i<6; i++) *ptr++ = 0; + + if (write(bpf_fd, packet, ptr - packet) < 0) + { + helplog(ASL_LEVEL_ERR, "do_mDNSSendWakeupPacket write failed %s", strerror(errno)); + return errno; + } + helplog(ASL_LEVEL_INFO, "do_mDNSSendWakeupPacket sent unicast eth_addr %s, ip_addr %s", eth_addr, ip_addr); + // Send a broadcast one to handle ethernet switches that don't flood forward packets with + // unknown mac addresses. + for (i=0; i<6; i++) packet[i] = 0xFF; + if (write(bpf_fd, packet, ptr - packet) < 0) + { + helplog(ASL_LEVEL_ERR, "do_mDNSSendWakeupPacket write failed %s", strerror(errno)); + return errno; + } + helplog(ASL_LEVEL_INFO, "do_mDNSSendWakeupPacket sent broadcast eth_addr %s, ip_addr %s", eth_addr, ip_addr); + close(bpf_fd); + return KERN_SUCCESS; +} + +// Open the specified port for protocol in the P2P firewall. +kern_return_t +do_mDNSPacketFilterControl(__unused mach_port_t port, uint32_t command, const char * ifname, uint32_t count, pfArray_t portArray, pfArray_t protocolArray, audit_token_t token) +{ + (void) token; // unused + int error; + kern_return_t result = KERN_SUCCESS; + + helplog(ASL_LEVEL_INFO, "do_mDNSPacketFilterControl: command %d ifname %s, count %d", + command, ifname, count); + + switch (command) + { + case PF_SET_RULES: + error = P2PPacketFilterAddBonjourRuleSet(ifname, count, portArray, protocolArray); + if (error) + { + helplog(ASL_LEVEL_ERR, "P2PPacketFilterAddBonjourRuleSet failed %s", strerror(error)); + result = KERN_FAILURE; + } + break; + + case PF_CLEAR_RULES: + error = P2PPacketFilterClearBonjourRules(); + if (error) + { + helplog(ASL_LEVEL_ERR, "P2PPacketFilterClearBonjourRules failed %s", strerror(error)); + result = KERN_FAILURE; + } + break; + + default: + helplog(ASL_LEVEL_ERR, "do_mDNSPacketFilterControl: invalid command %d", command); + result = KERN_INVALID_ARGUMENT; + break; + } + + return result; +} + +unsigned long +in_cksum(unsigned short *ptr,int nbytes) +{ + unsigned long sum; + u_short oddbyte; + + /* + * Our algorithm is simple, using a 32-bit accumulator (sum), + * we add sequential 16-bit words to it, and at the end, fold back + * all the carry bits from the top 16 bits into the lower 16 bits. + */ + sum = 0; + while (nbytes > 1) { + sum += *ptr++; + nbytes -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nbytes == 1) { + /* make sure top half is zero */ + oddbyte = 0; + + /* one byte only */ + *((u_char *)&oddbyte) = *(u_char *)ptr; + sum += oddbyte; + } + /* Add back carry outs from top 16 bits to low 16 bits. */ + sum = (sum >> 16) + (sum & 0xffff); + + /* add carry */ + sum += (sum >> 16); + + return sum; +} + +unsigned short +InetChecksum(unsigned short *ptr,int nbytes) +{ + unsigned long sum; + + sum = in_cksum(ptr, nbytes); + return (unsigned short)~sum; +} + +void TCPCheckSum(int af, struct tcphdr *t, int tcplen, v6addr_t sadd6, v6addr_t dadd6) +{ + unsigned long sum = 0; + unsigned short *ptr; + + /* TCP header checksum */ + sum = in_cksum((unsigned short *)t, tcplen); + + if (af == AF_INET) + { + /* Pseudo header */ + ptr = (unsigned short *)sadd6; + sum += *ptr++; + sum += *ptr++; + ptr = (unsigned short *)dadd6; + sum += *ptr++; + sum += *ptr++; + } + else if (af == AF_INET6) + { + /* Pseudo header */ + ptr = (unsigned short *)sadd6; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + ptr = (unsigned short *)dadd6; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + sum += *ptr++; + } + + sum += htons(tcplen); + sum += htons(IPPROTO_TCP); + + while (sum >> 16) + sum = (sum >> 16) + (sum & 0xFFFF); + + t->th_sum = ~sum; + +} + +kern_return_t do_mDNSSendKeepalive(__unused mach_port_t port, v6addr_t sadd6, v6addr_t dadd6, uint16_t lport, uint16_t rport, unsigned seq, unsigned ack, uint16_t win, audit_token_t token) +{ + struct packet4 { + struct ip ip; + struct tcphdr tcp; + } packet4; + struct packet6 { + struct tcphdr tcp; + } packet6; + int sock, on; + struct tcphdr *t; + int af; + struct sockaddr_storage ss_to; + struct sockaddr_in *sin_to = (struct sockaddr_in *)&ss_to; + struct sockaddr_in6 *sin6_to = (struct sockaddr_in6 *)&ss_to; + void *packet; + ssize_t packetlen; + char ctlbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + struct msghdr msghdr; + struct iovec iov; + ssize_t len; + + if (!authorized(&token)) + { + helplog(ASL_LEVEL_ERR, "mDNSSendKeepalive: Not authorized"); + return kmDNSHelperNotAuthorized; + } + + helplog(ASL_LEVEL_ERR, "mDNSSendKeepalive: called"); + + // all the incoming arguments are in network order + if ((*(unsigned *)(sadd6 +4) == 0) && (*(unsigned *)(sadd6 + 8) == 0) && (*(unsigned *)(sadd6 + 12) == 0)) + { + af = AF_INET; + memset(&packet4, 0, sizeof (packet4)); + + /* Fill in all the IP header information - should be in host order*/ + packet4.ip.ip_v = 4; /* 4-bit Version */ + packet4.ip.ip_hl = 5; /* 4-bit Header Length */ + packet4.ip.ip_tos = 0; /* 8-bit Type of service */ + packet4.ip.ip_len = 40; /* 16-bit Total length */ + packet4.ip.ip_id = 9864; /* 16-bit ID field */ + packet4.ip.ip_off = 0; /* 13-bit Fragment offset */ + packet4.ip.ip_ttl = 63; /* 8-bit Time To Live */ + packet4.ip.ip_p = IPPROTO_TCP; /* 8-bit Protocol */ + packet4.ip.ip_sum = 0; /* 16-bit Header checksum (below) */ + memcpy(&packet4.ip.ip_src.s_addr, sadd6, 4); + memcpy(&packet4.ip.ip_dst.s_addr, dadd6, 4); + + /* IP header checksum */ + packet4.ip.ip_sum = InetChecksum((unsigned short *)&packet4.ip, 20); + t = &packet4.tcp; + packet = &packet4; + packetlen = 40; // sum of IPv4 header len(20) and TCP header len(20) + } + else + { + af = AF_INET6; + memset(&packet6, 0, sizeof (packet6)); + t = &packet6.tcp; + packet = &packet6; + // We don't send IPv6 header, hence just the TCP header len (20) + packetlen = 20; + } + + /* Fill in all the TCP header information */ + t->th_sport = lport; /* 16-bit Source port number */ + t->th_dport = rport; /* 16-bit Destination port */ + t->th_seq = seq; /* 32-bit Sequence Number */ + t->th_ack = ack; /* 32-bit Acknowledgement Number */ + t->th_off = 5; /* Data offset */ + t->th_flags = TH_ACK; + t->th_win = win; + t->th_sum = 0; /* 16-bit checksum (below) */ + t->th_urp = 0; /* 16-bit urgent offset */ + + TCPCheckSum(af, t, 20, sadd6, dadd6); + + /* Open up a RAW socket */ + if ((sock = socket(af, SOCK_RAW, IPPROTO_TCP)) < 0) + { + helplog(ASL_LEVEL_ERR, "mDNSSendKeepalive: socket %s", strerror(errno)); + return errno; + } + + + if (af == AF_INET) + { + on = 1; + if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof (on))) + { + close(sock); + helplog(ASL_LEVEL_ERR, "mDNSSendKeepalive: setsockopt %s", strerror(errno)); + return errno; + } + + memset(sin_to, 0, sizeof(struct sockaddr_in)); + sin_to->sin_len = sizeof(struct sockaddr_in); + sin_to->sin_family = AF_INET; + memcpy(&sin_to->sin_addr, sadd6, sizeof(struct in_addr)); + sin_to->sin_port = rport; + + msghdr.msg_control = NULL; + msghdr.msg_controllen = 0; + + } + else + { + struct cmsghdr *ctl; + + memset(sin6_to, 0, sizeof(struct sockaddr_in6)); + sin6_to->sin6_len = sizeof(struct sockaddr_in6); + sin6_to->sin6_family = AF_INET6; + memcpy(&sin6_to->sin6_addr, dadd6, sizeof(struct in6_addr)); + + sin6_to->sin6_port = rport; + sin6_to->sin6_flowinfo = 0; + + + msghdr.msg_control = ctlbuf; + msghdr.msg_controllen = sizeof(ctlbuf); + ctl = CMSG_FIRSTHDR(&msghdr); + ctl->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + ctl->cmsg_level = IPPROTO_IPV6; + ctl->cmsg_type = IPV6_PKTINFO; + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *) CMSG_DATA(ctl); + memcpy(&pktinfo->ipi6_addr, sadd6, sizeof(struct in6_addr)); + pktinfo->ipi6_ifindex = 0; + } + + msghdr.msg_name = (struct sockaddr *)&ss_to; + msghdr.msg_namelen = ss_to.ss_len; + iov.iov_base = packet; + iov.iov_len = packetlen; + msghdr.msg_iov = &iov; + msghdr.msg_iovlen = 1; + msghdr.msg_flags = 0; +again: + len = sendmsg(sock, &msghdr, 0); + if (len == -1) + { + if (errno == EINTR) + goto again; + } + + if (len != packetlen) + { + helplog(ASL_LEVEL_ERR, "mDNSSendKeepalive: sendmsg failed %s", strerror(errno)); + } + else + { + char source[INET6_ADDRSTRLEN], dest[INET6_ADDRSTRLEN]; + + inet_ntop(af, (void *)sadd6, source, sizeof(source)); + inet_ntop(af, (void *)dadd6, dest, sizeof(dest)); + + helplog(ASL_LEVEL_ERR, "mDNSSendKeepalive: Success Source %s:%d, Dest %s:%d, %u, %u, %u", source, ntohs(lport), dest, ntohs(rport), ntohl(seq), ntohl(ack), ntohs(win)); + + } + close(sock); + return KERN_SUCCESS; +} + + +kern_return_t do_mDNSRetrieveTCPInfo(__unused mach_port_t port, int family, v6addr_t laddr, uint16_t lport, v6addr_t raddr, uint16_t rport, + uint32_t *seq, uint32_t *ack, uint16_t *win, int32_t *intfid, audit_token_t token) +{ + struct tcp_info ti; + struct info_tuple itpl; + int mib[4]; + unsigned int miblen; + size_t len; + size_t sz; + + memset(&itpl, 0, sizeof(struct info_tuple)); + memset(&ti, 0, sizeof(struct tcp_info)); + + if (!authorized(&token)) + { + helplog(ASL_LEVEL_ERR, "mDNSRetrieveTCPInfo: Not authorized"); + return kmDNSHelperNotAuthorized; + } + + if (family == AF_INET) + { + memcpy(&itpl.itpl_local_sin.sin_addr, laddr, sizeof(struct in_addr)); + memcpy(&itpl.itpl_remote_sin.sin_addr, raddr, sizeof(struct in_addr)); + itpl.itpl_local_sin.sin_port = lport; + itpl.itpl_remote_sin.sin_port = rport; + itpl.itpl_local_sin.sin_family = AF_INET; + itpl.itpl_remote_sin.sin_family = AF_INET; + } + else + { + memcpy(&itpl.itpl_local_sin6.sin6_addr, laddr, sizeof(struct in6_addr)); + memcpy(&itpl.itpl_remote_sin6.sin6_addr, raddr, sizeof(struct in6_addr)); + itpl.itpl_local_sin6.sin6_port = lport; + itpl.itpl_remote_sin6.sin6_port = rport; + itpl.itpl_local_sin6.sin6_family = AF_INET6; + itpl.itpl_remote_sin6.sin6_family = AF_INET6; + } + itpl.itpl_proto = IPPROTO_TCP; + sz = sizeof(mib)/sizeof(mib[0]); + if (sysctlnametomib("net.inet.tcp.info", mib, &sz) == -1) + { + helplog(ASL_LEVEL_ERR, "do_RetrieveTCPInfo: sysctlnametomib failed %d, %s", errno, strerror(errno)); + return errno; + } + miblen = (unsigned int)sz; + len = sizeof(struct tcp_info); + if (sysctl(mib, miblen, &ti, &len, &itpl, sizeof(struct info_tuple)) == -1) + { + helplog(ASL_LEVEL_ERR, "do_RetrieveTCPInfo: sysctl failed %d, %s", errno, strerror(errno)); + return errno; + } + + *seq = ti.tcpi_snd_nxt - 1; + *ack = ti.tcpi_rcv_nxt; + *win = ti.tcpi_rcv_space >> ti.tcpi_rcv_wscale; + *intfid = ti.tcpi_last_outif; + return KERN_SUCCESS; +} + +static int getMACAddress(int family, v6addr_t raddr, v6addr_t gaddr, int *gfamily, ethaddr_t eth) +{ + struct + { + struct rt_msghdr m_rtm; + char m_space[512]; + } m_rtmsg; + + struct rt_msghdr *rtm = &(m_rtmsg.m_rtm); + char *cp = m_rtmsg.m_space; + int seq = 6367, sock, rlen, i; + struct sockaddr_in *sin = NULL; + struct sockaddr_in6 *sin6 = NULL; + struct sockaddr_dl *sdl = NULL; + struct sockaddr_storage sins; + struct sockaddr_dl sdl_m; + +#define NEXTADDR(w, s, len) \ + if (rtm->rtm_addrs & (w)) \ + { \ + bcopy((char *)s, cp, len); \ + cp += len; \ + } + + bzero(&sins, sizeof(struct sockaddr_storage)); + bzero(&sdl_m, sizeof(struct sockaddr_dl)); + bzero((char *)&m_rtmsg, sizeof(m_rtmsg)); + + sock = socket(PF_ROUTE, SOCK_RAW, 0); + if (sock < 0) + { + helplog(ASL_LEVEL_ERR, "mDNSGetRemoteMAC: Can not open the socket - %s", strerror(errno)); + return errno; + } + + rtm->rtm_addrs |= RTA_DST | RTA_GATEWAY; + rtm->rtm_type = RTM_GET; + rtm->rtm_flags = 0; + rtm->rtm_version = RTM_VERSION; + rtm->rtm_seq = ++seq; + + sdl_m.sdl_len = sizeof(sdl_m); + sdl_m.sdl_family = AF_LINK; + if (family == AF_INET) + { + sin = (struct sockaddr_in*)&sins; + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + memcpy(&sin->sin_addr, raddr, sizeof(struct in_addr)); + NEXTADDR(RTA_DST, sin, sin->sin_len); + } + else if (family == AF_INET6) + { + sin6 = (struct sockaddr_in6 *)&sins; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, raddr, sizeof(struct in6_addr)); + NEXTADDR(RTA_DST, sin6, sin6->sin6_len); + } + NEXTADDR(RTA_GATEWAY, &sdl_m, sdl_m.sdl_len); + rtm->rtm_msglen = rlen = cp - (char *)&m_rtmsg; + + if (write(sock, (char *)&m_rtmsg, rlen) < 0) + { + helplog(ASL_LEVEL_INFO, "do_mDNSGetRemoteMAC: writing to routing socket: %s", strerror(errno)); + close(sock); + return errno; + } + + do + { + rlen = read(sock, (char *)&m_rtmsg, sizeof(m_rtmsg)); + } + while (rlen > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != getpid())); + + if (rlen < 0) + helplog(ASL_LEVEL_ERR, "do_mDNSGetRemoteMAC: Read from routing socket failed"); + + if (family == AF_INET) + { + sin = (struct sockaddr_in *) (rtm + 1); + sdl = (struct sockaddr_dl *) (sin->sin_len + (char *) sin); + } + else if (family == AF_INET6) + { + sin6 = (struct sockaddr_in6 *) (rtm +1); + sdl = (struct sockaddr_dl *) (sin6->sin6_len + (char *) sin6); + } + // If the address is not on the local net, we get the IP address of the gateway. + // We would have to repeat the process to get the MAC address of the gateway + *gfamily = sdl->sdl_family; + if (sdl->sdl_family == AF_INET) + { + struct sockaddr_in *new_sin = (struct sockaddr_in *)(sin->sin_len +(char*) sin); + memcpy(gaddr, &new_sin->sin_addr, sizeof(struct in_addr)); + close(sock); + return -1; + } + else if (sdl->sdl_family == AF_INET6) + { + struct sockaddr_in6 *new_sin6 = (struct sockaddr_in6 *)(sin6->sin6_len +(char*) sin6); + memcpy(gaddr, &new_sin6->sin6_addr, sizeof(struct in6_addr)); + close(sock); + return -1; + } + + unsigned char *ptr = (unsigned char *)LLADDR(sdl); + for (i = 0; i < ETHER_ADDR_LEN; i++) + (eth)[i] = *(ptr +i); + + close(sock); + return KERN_SUCCESS; +} + +kern_return_t do_mDNSGetRemoteMAC(__unused mach_port_t port, int family, v6addr_t raddr, ethaddr_t eth, audit_token_t token) +{ + int ret = 0; + v6addr_t gateway; + int gfamily; + int count = 0; + + if (!authorized(&token)) + { + helplog(ASL_LEVEL_ERR, "mDNSGetRemoteMAC: Not authorized"); + return kmDNSHelperNotAuthorized; + } + + do + { + ret = getMACAddress(family, raddr, gateway, &gfamily, eth); + if (ret == -1) + { + memcpy(raddr, gateway, sizeof(family)); + family = gfamily; + count++; + } + } + while ((ret == -1) && (count < 5)); + return ret; +} + + +kern_return_t do_mDNSStoreSPSMACAddress(__unused mach_port_t port, int family, v6addr_t spsaddr, const char *ifname, audit_token_t token) +{ + ethaddr_t eth; + char spsip[INET6_ADDRSTRLEN]; + int ret = 0; + CFStringRef sckey = NULL; + SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:StoreSPSMACAddress"), NULL, NULL); + SCDynamicStoreRef ipstore = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:GetIPv6Addresses"), NULL, NULL); + CFMutableDictionaryRef dict = NULL; + CFStringRef entityname = NULL; + CFDictionaryRef ipdict = NULL; + CFArrayRef addrs = NULL; + + if (!authorized(&token)) + { + helplog(ASL_LEVEL_ERR, "mDNSStoreSPSMAC: Not authorized"); + return kmDNSHelperNotAuthorized; + } + + if ((store == NULL) || (ipstore == NULL)) + { + helplog(ASL_LEVEL_ERR, "Unable to access SC Dynamic Store"); + return KERN_FAILURE; + } + + // Get the MAC address of the Sleep Proxy Server + memset(eth, 0, sizeof(eth)); + ret = do_mDNSGetRemoteMAC(port, family, spsaddr, eth, token); + if (ret != KERN_SUCCESS) + { + helplog(ASL_LEVEL_ERR, "mDNSStoreSPSMAC: Failed to determine the MAC address"); + goto fin; + } + + // Create/Update the dynamic store entry for the specified interface + sckey = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%s%s"), "State:/Network/Interface/", ifname, "/BonjourSleepProxyAddress"); + dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (!dict) + { + helplog(ASL_LEVEL_ERR, "SPSCreateDict: Could not create CFDictionary dict"); + ret = KERN_FAILURE; + goto fin; + } + + CFStringRef macaddr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%02x:%02x:%02x:%02x:%02x:%02x"), eth[0], eth[1], eth[2], eth[3], eth[4], eth[5]); + CFDictionarySetValue(dict, CFSTR("MACAddress"), macaddr); + if (NULL != macaddr) CFRelease(macaddr); + + if( NULL == inet_ntop(family, (void *)spsaddr, spsip, sizeof(spsip))) + { + helplog(ASL_LEVEL_ERR, "inet_ntop failed: %s", strerror(errno)); + ret = kmDNSHelperInvalidNetworkAddress; + goto fin; + } + + CFStringRef ipaddr = CFStringCreateWithCString(NULL, spsip, kCFStringEncodingUTF8); + CFDictionarySetValue(dict, CFSTR("IPAddress"), ipaddr); + if (NULL != ipaddr) CFRelease(ipaddr); + + // Get the current IPv6 addresses on this interface and store them so NAs can be sent on wakeup + if ((entityname = CFStringCreateWithFormat(NULL, NULL, CFSTR("State:/Network/Interface/%s/IPv6"), ifname)) != NULL) + { + if ((ipdict = SCDynamicStoreCopyValue(ipstore, entityname)) != NULL) + { + if((addrs = CFDictionaryGetValue(ipdict, CFSTR("Addresses"))) != NULL) + { + addrs = CFRetain(addrs); + CFDictionarySetValue(dict, CFSTR("RegisteredAddresses"), addrs); + } + } + } + SCDynamicStoreSetValue(store, sckey, dict); + +fin: + if (store) CFRelease(store); + if (ipstore) CFRelease(ipstore); + if (sckey) CFRelease(sckey); + if (dict) CFRelease(dict); + if (ipdict) CFRelease(ipdict); + if (entityname) CFRelease(entityname); + if (addrs) CFRelease(addrs); + + update_idle_timer(); + return ret; +} |