/* * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * DNSRegistrationThread.cpp * DSNSLPlugins * * Created by Kevin Arnold on Tue Mar 19 2002. * Copyright (c) 2002 Apple Computer. All rights reserved. * */ #include "DNSRegistrationThread.h" #include "mDNSPlugin.h" #include "LinkAddresses.h" #include "CNSLTimingUtils.h" #define kOurSpecialRegRef -1 typedef struct DNSRegData { CFNetServiceRef fCFNetServiceRef; UInt32 fCount; } DNSRegData; const CFStringRef kDNSSCDynamicStoreKeySAFE_CFSTR = CFSTR("com.apple.DirectoryServices.DNS"); const CFStringRef kWorkstationTypeSAFE_CFSTR = CFSTR("_workstation._tcp."); const CFStringRef kWorkstationPortSAFE_CFSTR = CFSTR("9"); const CFStringRef kSpaceLeftBracketSAFE_CFSTR = CFSTR(" ["); const CFStringRef kRightBracketSAFE_CFSTR = CFSTR("]"); const CFStringRef kZeroedMACAddressSAFE_CFSTR = CFSTR("0:0:0:0:0:0"); static void RegisterEntityCallBack(CFNetServiceRef theEntity, CFStreamError* error, void* info); CFStringRef CopyCancelRegDescription( const void* info ); CFStringRef CopyRegistrationDescription( const void* info ); boolean_t SystemConfigurationNameChangedCallBack(SCDynamicStoreRef session, void *callback_argument); DNSRegistrationThread::DNSRegistrationThread( mDNSPlugin* parentPlugin ) // : DSLThread() { mParentPlugin = parentPlugin; mRunLoopRef = 0; mSCRef = NULL; mRegisteredServicesTable = NULL; mMachineService = NULL; mCanceled = false; mOurSpecialRegKey = NULL; } DNSRegistrationThread::~DNSRegistrationThread() { mParentPlugin = NULL; mRunLoopRef = 0; if ( mRegisteredServicesTable ) { ::CFDictionaryRemoveAllValues( mRegisteredServicesTable ); ::CFRelease( mRegisteredServicesTable ); mRegisteredServicesTable = NULL; } if ( mSCRef ) CFRelease( mSCRef ); mSCRef = NULL; if ( mOurSpecialRegKey ) CFRelease( mOurSpecialRegKey ); mOurSpecialRegKey = NULL; } void DNSRegistrationThread::Cancel( void ) { /* if ( mRunLoopRef ) CFRunLoopStop( mRunLoopRef ); */ mCanceled = true; } void DNSRegistrationThread::Initialize( CFRunLoopRef idleRunLoopRef ) { // use these for the reftable dictionary CFDictionaryKeyCallBacks keyCallBack; CFDictionaryValueCallBacks valueCallBack; keyCallBack.version = 0; keyCallBack.retain = NULL; keyCallBack.release = NULL; keyCallBack.copyDescription = NULL; keyCallBack.equal = NULL; keyCallBack.hash = NULL; // this is fine valueCallBack.version = 0; valueCallBack.retain = NULL; valueCallBack.release = NULL; valueCallBack.copyDescription = NULL; valueCallBack.equal = NULL; mRegisteredServicesTable = ::CFDictionaryCreateMutable( NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &valueCallBack ); mRunLoopRef = idleRunLoopRef; if ( !mSCRef ) mSCRef = ::SCDynamicStoreCreate(NULL, kDNSSCDynamicStoreKeySAFE_CFSTR, NULL, NULL); SInt32 scdStatus = 0; CFStringRef key = 0; Boolean setStatus = FALSE; CFMutableArrayRef notifyKeys = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); CFMutableArrayRef notifyPatterns = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); // name changes DBGLOG( "RegisterForNetworkChange for SCDynamicStoreKeyCreateComputerName:\n" ); key = SCDynamicStoreKeyCreateComputerName(NULL); CFArrayAppendValue(notifyKeys, key); CFRelease(key); /* DBGLOG( "RegisterForNetworkChange for SCDynamicStoreKeyCreateHostNames:\n" ); key = SCDynamicStoreKeyCreateHostNames(NULL); CFArrayAppendValue(notifyKeys, key); CFRelease(key); */ setStatus = SCDynamicStoreSetNotificationKeys(mSCRef, notifyKeys, notifyPatterns); CFRelease(notifyKeys); CFRelease(notifyPatterns); if ( mRunLoopRef ) { ::CFRunLoopAddCommonMode( mRunLoopRef, kCFRunLoopDefaultMode ); scdStatus = ::SCDynamicStoreNotifyCallback( mSCRef, mRunLoopRef, SystemConfigurationNameChangedCallBack, this ); DBGLOG( "NSLRequestMgrThread::RegisterForNetworkChanges, SCDynamicStoreNotifyCallback returned %ld\n", scdStatus ); } else DBGLOG( "NSLRequestMgrThread::RegisterForNetworkChanges, No Current Run Loop, couldn't store Notify callback\n" ); } // we want to set this up so that we will keep some special services registered (e.g. MacManager's workstation // and we want to also keep track of the machine name since we'll want to keep this changing its registration when // the machine changes. tDirStatus DNSRegistrationThread::RegisterHostedServices( void ) { tDirStatus registrationStatus = eDSNoErr; CFStringEncoding encoding; CFStringRef computerName = SCDynamicStoreCopyComputerName(NULL, &encoding); if ( computerName ) { CFStringRef ethernetAddress = CreateComputerNameEthernetString(computerName); if ( ethernetAddress ) { char ethernetAddressStr[1024] = {0,}; CFStringGetCString( ethernetAddress, ethernetAddressStr, sizeof(ethernetAddressStr), kCFStringEncodingUTF8 ); DBGLOG( "DNSRegistrationThread::RegisterHostedServices, registering %s\n", ethernetAddressStr ); registrationStatus = PerformRegistration( ethernetAddress, kWorkstationTypeSAFE_CFSTR, kEmptySAFE_CFSTR, NULL, kWorkstationPortSAFE_CFSTR, &mOurSpecialRegKey ); if ( mOurSpecialRegKey ) { CFStringGetCString( mOurSpecialRegKey, ethernetAddressStr, sizeof(ethernetAddressStr), kCFStringEncodingUTF8 ); DBGLOG( "DNSRegistrationThread::RegisterHostedServices set mOurSpecialRegKey to %s\n", ethernetAddressStr ); } ::CFRelease( ethernetAddress ); } else DBGLOG("DNSRegistrationThread::RegisterHostedServices Could't get Ethernet Address!\n" ); ::CFRelease( computerName ); } else DBGLOG("DNSRegistrationThread::RegisterHostedServices Could't get computer name!\n" ); return registrationStatus; } tDirStatus DNSRegistrationThread::PerformRegistration( CFStringRef nameRef, CFStringRef typeRef, CFStringRef domainRef, CFStringRef protocolSpecificData, CFStringRef portRef, CFStringRef* serviceKeyRef ) { char mode = 'A'; *serviceKeyRef = NULL; while (!mRunLoopRef) { DBGLOG("DNSRegistrationThread::PerformRegistration, waiting for mRunLoopRef\n"); SmartSleep(500000); } CFStringRef modDomainRef = NULL; CFStringRef modTypeRef = NULL; if ( CFStringCompare( domainRef, kEmptySAFE_CFSTR, 0 ) != kCFCompareEqualTo && !CFStringHasSuffix( domainRef, kDotSAFE_CFSTR ) ) { // we need to pass fully qualified domains (i.e. local. not local) modDomainRef = CFStringCreateMutableCopy( NULL, 0, domainRef ); CFStringAppendCString( (CFMutableStringRef)modDomainRef, ".", kCFStringEncodingUTF8 ); } if ( !CFStringHasSuffix( typeRef, kDotUnderscoreTCPSAFE_CFSTR ) ) { // need to convert this to the appropriate DNS style. I.E. _afp._tcp. not afp modTypeRef = CFStringCreateMutableCopy( NULL, 0, kUnderscoreSAFE_CFSTR ); CFStringAppend( (CFMutableStringRef)modTypeRef, typeRef ); CFStringAppend( (CFMutableStringRef)modTypeRef, kDotUnderscoreTCPSAFE_CFSTR ); } CFStreamError error = {(CFStreamErrorDomain)0, 0}; CFNetServiceRef entity = NULL; UInt32 port = CFStringGetIntValue( portRef ); CFMutableStringRef serviceKey = CFStringCreateMutable( NULL, 0 ); if ( serviceKey ) { // we are just going to make the key be the name.type.location CFStringAppend( serviceKey, nameRef ); CFStringAppend( serviceKey, kDotSAFE_CFSTR ); CFStringAppend( serviceKey, (modTypeRef)?modTypeRef:typeRef ); CFStringAppend( serviceKey, (modDomainRef)?modDomainRef:domainRef ); } DNSRegData* regData = NULL; if ( (regData = (DNSRegData*)::CFDictionaryGetValue( mRegisteredServicesTable, serviceKey )) != NULL ) { #define USE_REF_COUNT_FOR_DUP_REGISTRATIONS #ifdef USE_REF_COUNT_FOR_DUP_REGISTRATIONS regData->fCount++; // we will just bump the counter, if we don't do this, then multiple registrations be ignored // and the first deregistration will deregister all previous registrations char serviceKeyStr[1024] = {0,}; CFStringGetCString( serviceKey, serviceKeyStr, sizeof(serviceKeyStr), kCFStringEncodingUTF8 ); DBGLOG( "DNSRegistrationThread::PerformRegistration, service: %s (0x%x) is now registered %ld times\n", serviceKeyStr, regData->fCFNetServiceRef, regData->fCount ); #endif } else { DBGLOG("DNSRegistrationThread::PerformRegistration, port is %ld\n", port ); if ( GetParentPlugin()->GetComputerNameString() && CFStringCompare( GetParentPlugin()->GetComputerNameString(), nameRef, 0 ) == kCFCompareEqualTo ) nameRef = kEmptySAFE_CFSTR; // use default entity = CFNetServiceCreate(NULL, (modDomainRef)?modDomainRef:domainRef, (modTypeRef)?modTypeRef:typeRef, nameRef, port); if ( protocolSpecificData ) { CFNetServiceSetProtocolSpecificInformation( entity, protocolSpecificData ); } mode = 'A'; { CFNetServiceClientContext c = {0, NULL, NULL, NULL, CopyRegistrationDescription}; if ( !CFNetServiceSetClient( entity, RegisterEntityCallBack, &c) ) syslog( LOG_ERR, "DS Rendezvous was unable to register a service with CFNetService!\n" ); CFNetServiceScheduleWithRunLoop(entity, mRunLoopRef, kCFRunLoopDefaultMode); } if (CFNetServiceRegister(entity, &error)) { CFRunLoopWakeUp( mRunLoopRef ); DBGLOG("CFNetServiceRegister returned TRUE!\n"); } else DBGLOG("CFNetServiceRegister returned FALSE (%d, %ld).\n", error.domain, error.error); if ( !error.error && serviceKey ) { *serviceKeyRef = serviceKey; CFRetain( *serviceKeyRef ); regData = (DNSRegData*)malloc(sizeof(DNSRegData)); regData->fCount = 1; regData->fCFNetServiceRef = entity; ::CFDictionaryAddValue( mRegisteredServicesTable, serviceKey, (const void*)regData ); char serviceKeyStr[1024] = {0,}; CFStringGetCString( serviceKey, serviceKeyStr, sizeof(serviceKeyStr), kCFStringEncodingUTF8 ); DBGLOG( "DNSRegistrationThread::PerformRegistration, registering with CFNetService: %s (0x%x)\n", serviceKeyStr, regData->fCFNetServiceRef ); } } if ( serviceKey ) CFRelease( serviceKey ); if ( modDomainRef ) CFRelease( modDomainRef ); if ( modTypeRef ) CFRelease( modTypeRef ); return (tDirStatus)error.error; } tDirStatus DNSRegistrationThread::PerformDeregistration( CFDictionaryRef service ) { tDirStatus status = eDSRecordNotFound; if ( !service ) return status; CFStringRef modDomainRef = NULL; CFStringRef modTypeRef = NULL; CFStringRef domainRef = NULL; CFStringRef nameOfService = NULL; /* Service's name (must be unique per domain (should be UTF8) */ CFStringRef typeOfService = NULL; /* Service Type (i.e. afp, lpr etc) */ nameOfService = (CFStringRef)::CFDictionaryGetValue( service, kDSNAttrRecordNameSAFE_CFSTR ); if ( !nameOfService ) nameOfService = kEmptySAFE_CFSTR; // just deregister Copmuter Name domainRef = (CFStringRef)::CFDictionaryGetValue( service, kDS1AttrLocationSAFE_CFSTR ); if ( !domainRef ) domainRef = kEmptySAFE_CFSTR; // just deregister local typeOfService = (CFStringRef)::CFDictionaryGetValue( service, kDS1AttrServiceTypeSAFE_CFSTR ); if ( !typeOfService ) typeOfService = (CFStringRef)::CFDictionaryGetValue( service, kDSNAttrRecordTypeSAFE_CFSTR ); if ( CFStringCompare( domainRef, kEmptySAFE_CFSTR, 0 ) != kCFCompareEqualTo && !CFStringHasSuffix( domainRef, kDotSAFE_CFSTR ) ) { // we need to pass fully qualified domains (i.e. local. not local) modDomainRef = CFStringCreateMutableCopy( NULL, 0, domainRef ); CFStringAppendCString( (CFMutableStringRef)modDomainRef, ".", kCFStringEncodingUTF8 ); } if ( !CFStringHasSuffix( typeOfService, kDotUnderscoreTCPSAFE_CFSTR ) ) { // need to convert this to the appropriate DNS style. I.E. _afp._tcp. not afp modTypeRef = CFStringCreateMutableCopy( NULL, 0, kUnderscoreSAFE_CFSTR ); CFStringAppend( (CFMutableStringRef)modTypeRef, typeOfService ); CFStringAppend( (CFMutableStringRef)modTypeRef, kDotUnderscoreTCPSAFE_CFSTR ); } CFMutableStringRef serviceKey = CFStringCreateMutable( NULL, 0 ); if ( serviceKey ) { // we are just going to make the key be the name._type._tcp.location. CFStringAppend( serviceKey, nameOfService ); CFStringAppend( serviceKey, kDotSAFE_CFSTR ); CFStringAppend( serviceKey, (modTypeRef)?modTypeRef:typeOfService ); CFStringAppend( serviceKey, (modDomainRef)?modDomainRef:domainRef ); status = PerformDeregistration( serviceKey ); CFRelease( serviceKey ); } if ( modTypeRef != NULL ) { CFRelease( modTypeRef ); modTypeRef = NULL; } if ( modDomainRef != NULL ) { CFRelease( modDomainRef ); modDomainRef = NULL; } return status; } tDirStatus DNSRegistrationThread::PerformDeregistration( CFStringRef serviceKey ) { DNSRegData* regData = NULL; CFNetServiceRef entity = NULL; tDirStatus status = eDSRecordNotFound; if ( serviceKey && (regData = (DNSRegData*)::CFDictionaryGetValue( mRegisteredServicesTable, serviceKey )) != NULL ) { { char serviceKeyStr[1024] = {0,}; CFStringGetCString( serviceKey, serviceKeyStr, sizeof(serviceKeyStr), kCFStringEncodingUTF8 ); DBGLOG( "DNSRegistrationThread::PerformDeregistration, deregistering CFNetService: %s (0x%x)\n", serviceKeyStr, regData->fCFNetServiceRef ); } entity = regData->fCFNetServiceRef; if ( entity && regData->fCount == 1 ) { ::CFDictionaryRemoveValue( mRegisteredServicesTable, serviceKey ); CFNetServiceUnscheduleFromRunLoop( entity, mRunLoopRef, kCFRunLoopDefaultMode ); // need to unschedule from run loop if ( !CFNetServiceSetClient( entity, NULL, NULL ) ) syslog( LOG_ERR, "DS Rendezvous was unable to unregister a service with CFNetService!\n" ); CFNetServiceCancel( entity ); CFRelease( entity ); free( regData ); status = eDSNoErr; } else { regData->fCount--; // decrement this DBGLOG( "DNSRegistrationThread::PerformDeregistration, service is now registered %ld times\n", regData->fCount ); } } return status; } boolean_t SystemConfigurationNameChangedCallBack(SCDynamicStoreRef session, void *callback_argument) { DNSRegistrationThread* regThread = (DNSRegistrationThread*)callback_argument; DBGLOG( "SystemConfigurationNameChangedCallBack called\n" ); regThread->PerformDeregistration( regThread->GetOurSpecialRegKey() ); // deregister old service, since the name isn't valid regThread->RegisterHostedServices(); // register with current name return true; } static void RegisterEntityCallBack(CFNetServiceRef theEntity, CFStreamError* error, void* info) { DBGLOG( "Registration is finished error: (%d, %ld).\n", error->domain, error->error); } CFStringRef CopyCancelRegDescription( const void* info ) { DBGLOG( "CopyCancelRegDescription called\n" ); CFNetServiceRef theEntity = (CFNetServiceRef)info; CFStringRef description = CFNetServiceGetName(theEntity); CFRetain( description ); return description; } CFStringRef CopyRegistrationDescription( const void* info ) { CFStringRef description = kDNSSCDynamicStoreKeySAFE_CFSTR; DBGLOG( "CopyRegistrationDescription called\n" ); CFRetain( description ); return description; } CFStringRef CreateComputerNameEthernetString( CFStringRef computerName ) { CFMutableStringRef modString = NULL; if ( computerName ) { CFStringRef macAddress = CreateMacAddressString(); modString = CFStringCreateMutableCopy( NULL, 0, computerName ); CFStringAppend( modString, kSpaceLeftBracketSAFE_CFSTR ); if ( macAddress ) CFStringAppend( modString, macAddress ); else CFStringAppend( modString, kZeroedMACAddressSAFE_CFSTR ); CFStringAppend( modString, kRightBracketSAFE_CFSTR ); if ( macAddress ) CFRelease( macAddress ); if ( getenv( "NSLDEBUG" ) ) { DBGLOG( "DNSRegistrationThread::CreateComputerNameEthernetString created new composite name\n" ); CFShow( modString ); } } return modString; } CFStringRef CreateMacAddressString( void ) { LinkAddresses_t * link_addrs; char* macAddrCString = NULL; CFStringRef macAddrStringRef = NULL; link_addrs = LinkAddresses_create(); if (link_addrs) { int i; for (i = 0; i < link_addrs->count; i++) { struct sockaddr_dl * sdl = link_addrs->list[i]; macAddrCString = sockaddr_dl_create_macaddr_string( sdl, "en0" ); if ( macAddrCString ) break; } LinkAddresses_free(&link_addrs); } if ( macAddrCString ) { macAddrStringRef = CFStringCreateWithCString( NULL, macAddrCString, kCFStringEncodingUTF8 ); free( macAddrCString ); } return macAddrStringRef; }