Plugins/Bonjour/libezv/Private Classes/AWEzvContactManagerRendezvous.m
author Evan Schoenberg
Sat Oct 31 12:31:33 2009 -0500 (2009-10-31)
changeset 2706 13898cc883cd
parent 2093 d470a05e220b
permissions -rw-r--r--
If Bonjour disconnects but the user opens a contact window in the moment before the contact disappears from the list, it wass possible to attempt to open with a nil avInstanceName. Furthermore, if Bonjour disconnects, attempting to send a message to a still-open Bonjour chat could try to reference avInstanceName which would previously be nil after disconnect.

There's no reason to be so aggressive with having avInstanceName only be set while logged in; it's a single string, and it doesn't generally change. We'll create it in init and destroy it in dealloc for AWEzvContactManager.

Fixes #11799 fully.
     1 /*
     2  * Project:     Libezv
     3  * File:        AWEzvContactManagerRendezvous.m
     4  *
     5  * Version:     1.0
     6  * Author:      Andrew Wellington <proton[at]wiretapped.net>
     7  *
     8  * License:
     9  * Copyright (C) 2004-2007 Andrew Wellington.
    10  * All rights reserved.
    11  * 
    12  * Redistribution and use in source and binary forms, with or without
    13  * modification, are permitted provided that the following conditions are met:
    14  * 
    15  * 1. Redistributions of source code must retain the above copyright notice,
    16  * this list of conditions and the following disclaimer.
    17  * 2. Redistributions in binary form must reproduce the above copyright notice,
    18  * this list of conditions and the following disclaimer in the documentation
    19  * and/or other materials provided with the distribution.
    20  * 
    21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
    22  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    23  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
    24  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
    27  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    28  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
    29  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
    30  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    31  */
    32 
    33 #import "AWEzvContactManager.h"
    34 #import "AWEzvContactManagerRendezvous.h"
    35 #import "AWEzv.h"
    36 #import "AWEzvPrivate.h"
    37 #import "AWEzvContact.h"
    38 #import "AWEzvContactPrivate.h"
    39 #import "AWEzvRendezvousData.h"
    40 
    41 #import "AWEzvSupportRoutines.h"
    42 
    43 #import <dns_sd.h>
    44 
    45 #import <openssl/sha.h>
    46 
    47 /* One of the stupidest things I've ever met. Doing DNS lookups using the standard
    48  * functions does not for mDNS records work unless you're in BIND 8 compatibility
    49  * mode. And of course how do you get data from say a NULL record for iChat stuff?
    50  * With the standard DNS functions. So we have to use BIND 8 mode. Which means we
    51  * have to implement our own DNS packet parser. What were people thinking here?
    52  */
    53 #define BIND_8_COMPAT 1
    54 #import <sys/types.h>
    55 #import <sys/socket.h>
    56 #import <netinet/in.h>
    57 #import <arpa/nameser.h>
    58 #import <arpa/inet.h>
    59 #import <netdb.h>
    60 #import <resolv.h>
    61 #import <errno.h>
    62 #import <ctype.h>
    63 #import <string.h>
    64 #import <stdlib.h>
    65 
    66 #import <SystemConfiguration/SystemConfiguration.h>
    67 // The ServiceController manages cleanup of DNSServiceRef & runloop info for an outstanding request
    68 @interface ServiceController : NSObject
    69 {
    70 	DNSServiceRef			fServiceRef;
    71 	CFSocketRef				fSocketRef;
    72 	CFRunLoopSourceRef		fRunloopSrc;
    73 	AWEzvContactManager		*contactManager;
    74 }
    75 
    76 - (id)initWithServiceRef:(DNSServiceRef)ref forContactManager:(AWEzvContactManager *)inContactManager;
    77 - (boolean_t)addToCurrentRunLoop;
    78 - (void)breakdownServiceController;
    79 - (DNSServiceRef)serviceRef;
    80 @property (readonly, nonatomic) AWEzvContactManager *contactManager;
    81 
    82 @end // interface ServiceController
    83 
    84 /* C-helper function prototypes */
    85 void register_reply ( 
    86     DNSServiceRef sdRef, 
    87     DNSServiceFlags flags, 
    88     DNSServiceErrorType errorCode, 
    89     const char *name, 
    90     const char *regtype, 
    91     const char *domain, 
    92     void *context );
    93 
    94 static void ProcessSockData (
    95    CFSocketRef s,
    96    CFSocketCallBackType callbackType,
    97    CFDataRef address,
    98    const void *data,
    99    void *info
   100 );
   101 
   102 void handle_av_browse_reply ( 
   103     DNSServiceRef sdRef, 
   104     DNSServiceFlags flags, 
   105     uint32_t interfaceIndex, 
   106     DNSServiceErrorType errorCode, 
   107     const char *serviceName, 
   108     const char *regtype, 
   109     const char *replyDomain, 
   110     void *context );
   111 
   112 void resolve_reply ( 
   113     DNSServiceRef sdRef, 
   114     DNSServiceFlags flags, 
   115     uint32_t interfaceIndex, 
   116     DNSServiceErrorType errorCode, 
   117     const char *fullname, 
   118     const char *hosttarget, 
   119     uint16_t port, 
   120     uint16_t txtLen, 
   121     const unsigned char *txtRecord, 
   122     void *context );
   123 
   124 void AddressQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, 
   125 								DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, 
   126 								uint16_t rdlen, const void *rdata, uint32_t ttl, void *context );
   127 								
   128 void ImageQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, 
   129 								DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, 
   130 								uint16_t rdlen, const void *rdata, uint32_t ttl, void *context );	
   131 								
   132 void image_register_reply ( 
   133     DNSServiceRef sdRef, 
   134     DNSRecordRef RecordRef, 
   135     DNSServiceFlags flags, 
   136     DNSServiceErrorType errorCode, 
   137     void *context );							
   138 
   139 @implementation AWEzvContactManager (Rendezvous)
   140 #pragma mark Announcing Functions
   141 - (void) login {
   142 	regCount = 0;
   143 
   144 	/* create data structure we'll advertise with */
   145 	userAnnounceData = [[AWEzvRendezvousData alloc] init];
   146 	/* set field contents of the data */
   147 	[userAnnounceData setField:@"1st" content:[client name]];
   148 	[userAnnounceData setField:@"email" content:@""];
   149 	[userAnnounceData setField:@"ext" content:@""];
   150 	[userAnnounceData setField:@"jid" content:@""];
   151 	[userAnnounceData setField:@"last" content:@""];
   152 	[userAnnounceData setField:@"msg" content:@""];
   153 	[userAnnounceData setField:@"nick" content:@""];
   154 	[userAnnounceData setField:@"node" content:@""];
   155 	[userAnnounceData setField:@"AIM" content:@""];
   156 	[userAnnounceData setField:@"email" content:@""];
   157 	[userAnnounceData setField:@"port.p2pj" content:[NSString stringWithFormat:@"%u", port]];
   158 	[userAnnounceData setField:@"txtvers" content:@"1"];
   159 	[userAnnounceData setField:@"version" content:@"1"];
   160 
   161 	[self setStatus:[client status] withMessage:nil];
   162 	
   163     /* register service with mDNSResponder */
   164 
   165 	DNSServiceRef servRef;
   166 	DNSServiceErrorType dnsError;
   167 
   168 	TXTRecordRef txtRecord;
   169 	txtRecord = [userAnnounceData dataAsTXTRecordRef];
   170 
   171 	dnsError = DNSServiceRegister(
   172 			/* uninitialized service discovery reference */ &servRef, 
   173 		    /* flags indicating how to handle name conflicts */ /* kDNSServiceFlagsNoAutoRename */ 0, 
   174 		    /* interface on which to register, 0 for all available */ 0, 
   175 		    /* service's name, may be null */ [avInstanceName UTF8String],
   176 		    /* service registration type */ "_presence._tcp", 
   177 		    /* domain, may be NULL */ NULL,
   178 		    /* SRV target host name, may be NULL */ NULL,
   179 		    /* port number in network byte order */ htons(port), 
   180 		    /* length of txt record in bytes, 0 for NULL txt record */ TXTRecordGetLength(&txtRecord) , 
   181 		    /* txt record properly formatted, may be NULL */ TXTRecordGetBytesPtr(&txtRecord) ,
   182 		    /* call back function, may be NULL */ register_reply,
   183 			/* application context pointer, may be null */ self);
   184 
   185 	if (dnsError == kDNSServiceErr_NoError) {		
   186 		fDomainBrowser = [[ServiceController alloc] initWithServiceRef:servRef forContactManager:self];
   187 		[fDomainBrowser addToCurrentRunLoop];
   188 		avDNSReference = servRef;
   189 	} else {
   190 		[[client client] reportError:@"Could not register DNS service: _presence._tcp" ofLevel:AWEzvConnectionError];
   191 		[self disconnect];
   192 	}
   193 	TXTRecordDeallocate(&txtRecord);
   194 }
   195 
   196 /* this is used for a clean logout */
   197 - (void) logout {
   198     [self disconnect];
   199 }
   200 
   201 /* this causes an actual disconnect */
   202 - (void) disconnect {
   203 	AILogWithSignature(@"Disconnecting");
   204 
   205 	[fServiceBrowser release]; fServiceBrowser = nil;
   206 
   207 	/* Remove Resolvers, this also deallocates the DNSServiceReferences */
   208 	if (fDomainBrowser != nil) {
   209 		AILogWithSignature(@"Releasing %@",fDomainBrowser);
   210 		[fDomainBrowser release]; fDomainBrowser = nil;
   211 
   212 		avDNSReference = nil;
   213 		imageServiceRef = nil;
   214 	}
   215 
   216 	[self setConnected:NO];
   217 }
   218 
   219 - (void) setConnected:(BOOL)connected {
   220 	if (isConnected != connected) {
   221 		isConnected = connected;
   222 		if (connected)
   223 			[[client client] reportLoggedIn];
   224 		else
   225 			[[client client] reportLoggedOut];
   226 	}
   227 }
   228 
   229 - (void)setStatus:(AWEzvStatus)status withMessage:(NSString *)message {
   230 	NSString *statusString; /* string for use in Rendezous field */
   231 	/* work out the string for rendezvous */
   232 	switch (status) {
   233 		case AWEzvIdle:
   234 			statusString = @"away";
   235 	    	break;
   236 		case AWEzvAway:
   237 			statusString = @"dnd";
   238 			break;
   239 		case AWEzvOnline:
   240 			statusString = @"avail";
   241 			break;
   242 		default:
   243 	    	/* if something weird, default to available */
   244 			statusString = @"avail";
   245 	}
   246 
   247 	/* add it to our data */
   248 	[userAnnounceData setField:@"status" content:statusString];
   249 
   250 	/* now set the message */
   251 	if ([message length]) {
   252 		[userAnnounceData setField:@"msg" content:message];
   253 	} else {
   254 		[userAnnounceData deleteField:@"msg"];
   255 	}
   256 
   257 	/* check for idle */
   258 	if ([client idleTime]) {
   259 		[userAnnounceData setField:@"away" content:[NSString stringWithFormat:@"%f", [[client idleTime] timeIntervalSinceReferenceDate]]];
   260 	} else {
   261 		[userAnnounceData deleteField:@"away"];
   262 	}
   263 
   264 	/* announce to network */
   265 	if (isConnected == YES)
   266 		[self updateAnnounceInfo];
   267 }
   268 
   269 /* udpates information announced over network for user */
   270 - (void) updateAnnounceInfo {
   271 	DNSServiceErrorType updateError;
   272 	TXTRecordRef txtRecord;
   273 
   274 	if (!isConnected)
   275 		return;
   276 
   277 	if (avDNSReference == NULL) {
   278 		[[client client] reportError:@"avDNSReference is null when trying to update the TXT Record" ofLevel:AWEzvWarning];
   279 		return;
   280 	}
   281 
   282 	txtRecord = [userAnnounceData dataAsTXTRecordRef];
   283 	AILogWithSignature(@"%@", [userAnnounceData dictionary]);
   284 	updateError = DNSServiceUpdateRecord (
   285 		/* serviceRef */ avDNSReference,
   286 		/* recordRef, may be NULL */ NULL,
   287 		/* Flags, currently ignored */ 0,
   288 		/* length */ TXTRecordGetLength(&txtRecord),
   289 		/* data */ TXTRecordGetBytesPtr(&txtRecord),
   290 		/* time to live */ 0 );
   291 	if (updateError != kDNSServiceErr_NoError) {		
   292 		[[client client] reportError:@"Error updating TXT Record" ofLevel:AWEzvConnectionError];
   293 		[self disconnect];
   294 	}
   295 	
   296 	TXTRecordDeallocate(&txtRecord);
   297 }
   298 
   299 - (void) updatedName {
   300 	[userAnnounceData setField:@"1st" content:[client name]];
   301 	[self updateAnnounceInfo];
   302 }
   303 
   304 - (void) updatedStatus {
   305 	[self setStatus:[client status] withMessage:[userAnnounceData getField:@"msg"]];
   306 }
   307 
   308 - (void)setImageData:(NSData *)JPEGData {
   309 	DNSServiceErrorType error;
   310 	SHA_CTX ctx;
   311 	unsigned char digest[20];
   312 
   313 	if (avDNSReference == NULL) {
   314 		[[client client] reportError:@"Error setting image data" ofLevel:AWEzvWarning];
   315 		return;
   316 	}
   317 
   318 	if (JPEGData == nil) {
   319 		/* no image so remove record and update txt records*/
   320 		error = DNSServiceRemoveRecord ( 
   321 		    /* service reference */ avDNSReference, 
   322 		    /* record reference */ imageRef, 
   323 		    /* flags, ignored */ 0);
   324 		if (error == kDNSServiceErr_NoError) {
   325 			imageRef = nil;
   326 			[userAnnounceData deleteField:@"phsh"];
   327 			[self updateAnnounceInfo];
   328 			return;
   329 		}
   330 	}
   331 
   332 	if (imageRef != nil && JPEGData != nil) {
   333 		/* Remove the old reference before updating the image. 
   334 		 * This works around a bug experienced when updating the record to use an image that occupied more space
   335 		 **/
   336 		error = DNSServiceRemoveRecord ( 
   337 		    /* service reference */ /*imageServiceRef*/avDNSReference, 
   338 		    /* record reference */ imageRef, 
   339 			/* flags, ignored */ 1);
   340 		if (error != kDNSServiceErr_NoError) {
   341 			[[client client] reportError:@"Error removing old image before setting new image" ofLevel:AWEzvWarning];
   342 			return;
   343 		} else {
   344 			[userAnnounceData deleteField:@"phsh"];
   345 			imageRef = nil;
   346 		}
   347 	}
   348 
   349 	error = DNSServiceAddRecord (/*service reference */ avDNSReference, 
   350 	                             /*record reference */ &imageRef, 
   351 	                             /* flags, ignored */ 0, 
   352 	                             /* type */ kDNSServiceType_NULL, 
   353 	                             /* length */ [JPEGData length], 
   354 	                             /* data */ [JPEGData bytes], 
   355 	                             /* time to live; 0=default */ 0);
   356 
   357 	if (error == kDNSServiceErr_NoError) {
   358 		/* Let's create the hash */
   359 		SHA1_Init(&ctx);
   360 		SHA1_Update(&ctx, [JPEGData bytes], (unsigned long)[JPEGData length]);
   361 		SHA1_Final(digest, &ctx);
   362 		imagehash = [[NSData dataWithBytes:digest length:20] retain];
   363 		AILogWithSignature(@"Will update with hash %@; length is %u", imagehash, [JPEGData length]);
   364 		[self updatePHSH];
   365 	} else {
   366 		[[client client] reportError:@"Error adding image record" ofLevel:AWEzvWarning];
   367 	}
   368 }
   369 
   370 - (void) updatePHSH {
   371 	if (imagehash != nil) {
   372 		[userAnnounceData setField:@"phsh" content:[imagehash autorelease]];
   373 		/* announce to network */
   374 		[self updateAnnounceInfo];
   375 	} else {
   376 		[userAnnounceData deleteField:@"phsh"];
   377 	}
   378 }
   379 
   380 #pragma mark Browsing Functions
   381 /* start browsing the network for new rendezvous clients */
   382 - (void) startBrowsing {
   383 	[fServiceBrowser release]; fServiceBrowser = nil;
   384 
   385 	/* destroy old contact dictionary if one exists */
   386 	[contacts release];
   387 
   388 	/* allocate new contact dictionary */
   389 	contacts = [[NSMutableDictionary alloc] init];
   390 
   391 	/* create AV browser */
   392 	DNSServiceRef browsRef;
   393 	DNSServiceErrorType avBrowseError;
   394 
   395 	avBrowseError = DNSServiceBrowse (/* uninitialized DNSServiceRef */ &browsRef,
   396 	                                  /* flags, currently unused */ 0,
   397 	                                  /* interface index, 0 for all available */ 0,
   398 	                                  /* registration type */ "_presence._tcp",
   399 	                                  /* domain, may be null for default */ NULL,
   400 	                                  /* callBack function */ handle_av_browse_reply,
   401 	                                  /* context, may be null */ self);
   402 
   403 	if (avBrowseError == kDNSServiceErr_NoError) {
   404 		fServiceBrowser = [[ServiceController alloc] initWithServiceRef:browsRef forContactManager:self];
   405 		[fServiceBrowser addToCurrentRunLoop];
   406 	} else {
   407 		[[client client] reportError:@"Could not browse for _presence._tcp instances" ofLevel:AWEzvConnectionError];
   408 		[self disconnect];
   409 	}
   410 }
   411 
   412 /* stop looking for new rendezvous clients */
   413 - (void)stopBrowsing{
   414 	AILogWithSignature(@"fServiceBrowser is %@ (retain count %i)", fServiceBrowser, [fServiceBrowser retainCount]);
   415 	[fServiceBrowser release]; fServiceBrowser = nil;
   416 }
   417 
   418 /* handle a message from our browser */
   419 - (void)browseResultwithFlags:(DNSServiceFlags)flags
   420 	onInterface:(uint32_t) interfaceIndex
   421 	name:(const char *)replyName
   422 	type:(const char *)replyType
   423 	domain:(const char *)replyDomain
   424 	av:(BOOL) av {
   425 	
   426 	AWEzvContact *contact;
   427 	if (!replyName)
   428 		return;
   429 
   430 	NSString *replyNameString = [NSString stringWithUTF8String:replyName];
   431 	if (!replyNameString)
   432 		return;
   433 	if (flags == (kDNSServiceFlagsAdd) || flags == (kDNSServiceFlagsMoreComing | kDNSServiceFlagsAdd)) {
   434 		/* Add this contact */
   435 		/* initialise contact */
   436 		contact = [[AWEzvContact alloc] init];
   437 		contact.uniqueID = replyNameString;
   438 		contact.manager = self;
   439 		/* save contact in dictionary */
   440 		[contacts setObject:contact forKey:replyNameString];
   441 		[contact autorelease];
   442 
   443 		/* and resolve contact */
   444 		DNSServiceRef resolveRef;
   445 		DNSServiceErrorType resolveRefError;
   446 
   447 		resolveRefError = DNSServiceResolve (
   448 			/* serviceref uninitialized */ &resolveRef,
   449 			/* flags, currently ignored */ 0,
   450 			/* interfaceIndex */ 0/*interfaceIndex*/,
   451 			/* full name */ replyName,
   452 			/* registration type */ "_presence._tcp" /* replyType */,
   453 			/* domain */ replyDomain,
   454 			/* callback */ resolve_reply,
   455 			/* contxt, may be NULL */ contact);
   456 
   457 		if (resolveRefError == kDNSServiceErr_NoError) {
   458 			ServiceController *serviceResolver = [[ServiceController alloc] initWithServiceRef:resolveRef forContactManager:self];
   459 			[contact setResolveServiceController:serviceResolver];
   460 			[[contact resolveServiceController] addToCurrentRunLoop];
   461 			[serviceResolver release];
   462 
   463 		} else {
   464 			[[client client] reportError:@"Could not search for TXT records" ofLevel:AWEzvConnectionError];
   465 			[self disconnect];
   466 		}
   467 	} else {
   468 		/* delete the contact */
   469 		contact = [contacts objectForKey:replyNameString];
   470 		if (!contact)
   471 			return;
   472 		[[client client] userLoggedOut:contact];
   473 		/* remove the contact from our data structures */
   474 		[contacts removeObjectForKey:replyNameString];
   475 		return;
   476 	}
   477 }
   478 - (void)findAddressForContact:(AWEzvContact *)contact
   479 	withHost:(NSString *)host
   480 	withInterface:(uint32_t)interface{
   481 
   482 	/* Now we need to query the record for the ip address */
   483 
   484 	DNSServiceErrorType err;
   485 	DNSServiceRef		serviceRef;
   486 
   487 	err = DNSServiceQueryRecord( &serviceRef, (DNSServiceFlags) 0, interface, [host UTF8String], 
   488 	                             kDNSServiceType_A, kDNSServiceClass_IN, AddressQueryRecordReply, contact);
   489 	if ( err == kDNSServiceErr_NoError) {
   490 		ServiceController *temp = [[ServiceController alloc] initWithServiceRef:serviceRef forContactManager:self];
   491 		[contact setAddressServiceController:temp];
   492 		[[contact addressServiceController] addToCurrentRunLoop];
   493 		[temp release];
   494 	} else {
   495 		[[client client] reportError:@"Error finding adress for contact" ofLevel:AWEzvError];
   496 	}
   497 }
   498 
   499 - (void)updateAddressForContact:(AWEzvContact *)contact
   500 	addr:(const void *)buff 
   501 	addrLen:(uint16_t)addrLen 
   502 	host:(const char*) host 
   503 	interfaceIndex:(uint32_t)interface 
   504 	more:(boolean_t)moreToCome{
   505 
   506 	/* check that contact exists in dictionary */
   507 	if ([contacts objectForKey:contact.uniqueID] == nil) {
   508 		NSString *uniqueID = contact.uniqueID;
   509 		/* So they haven't been seen before... not to worry we'll add them */
   510 		if (contact.uniqueID != nil) {
   511 			contact = [[AWEzvContact alloc] init];
   512 			contact.uniqueID = uniqueID;
   513 			contact.manager = self;
   514 			/* save contact in dictionary */
   515 			[contacts setObject:contact forKey:contact.uniqueID];
   516 			[contact autorelease];
   517 
   518 		} else {
   519 			[[client client] reportError:@"Contact to update not in dictionary and has bad identifier" ofLevel:AWEzvError];
   520 		}
   521 	}
   522 
   523 	NSString *ipAddr;			/* ip address of contact */
   524 	NSRange	range;				/* just a range... */
   525     
   526 	char addrBuff[256];
   527 	
   528 	inet_ntop( AF_INET, buff, addrBuff, sizeof addrBuff);
   529 
   530 	ipAddr = [NSString stringWithCString:addrBuff encoding:NSUTF8StringEncoding];
   531 	range = [ipAddr rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@":"]];
   532 	if (range.location == NSNotFound) {
   533 		contact.ipAddr = ipAddr;
   534 	}
   535 
   536 	if (!contact.ipAddr || !contact.ipAddr.length) {
   537 		[[client client] reportError:@"ip address not set" ofLevel:AWEzvError];
   538 		[contact setStatus: AWEzvUndefined];
   539 	}
   540 
   541 	if (!moreToCome) {
   542 		[contact setAddressServiceController: nil];
   543 	}
   544 }
   545 
   546 - (void)updateImageForContact:(AWEzvContact *)contact
   547 	data:(const void *)data 
   548 	dataLen:(uint16_t)dataLen
   549 	more:(boolean_t)moreToCome{
   550 
   551 	if (!moreToCome) {
   552 		[contact setImageServiceController: nil];
   553 	}
   554 	AILogWithSignature(@"%@ -> %@ (%i)", [NSData dataWithBytes:data length:dataLen], [[[NSImage alloc] initWithData:[NSData dataWithBytes:data length:dataLen]] autorelease], dataLen);
   555 	if (dataLen != 0 ) {
   556 		/* We have an image */
   557 		/* parse raw Data */
   558 		[contact setContactImageData:[NSData dataWithBytes:data length:dataLen]];
   559 	    [[client client] userChangedImage:contact];
   560 
   561 	} else {
   562 		[contact setImageHash: NULL];
   563 		[[client client] reportError:@"Error retrieving picture" ofLevel:AWEzvError];
   564 	}
   565 }
   566 - (void)updateContact:(AWEzvContact *)contact
   567 	withData:(AWEzvRendezvousData *)rendezvousData
   568 	withHost:(NSString *)host
   569 	withInterface:(uint32_t)interface
   570 	withPort:(uint16_t)recPort
   571 	av:(BOOL)av {
   572 
   573 	NSString		*nick = nil;			/* nickname for contact */
   574 	NSMutableString	*mutableNick = nil;		/* nickname we can modify */
   575 	AWEzvRendezvousData	*oldrendezvous;			/* old rendezvous data for user */ /* XXX not used */
   576 	NSNumber           *idleTime = nil;			/* idle time */
   577 
   578 	/* check that contact exists in dictionary */
   579 	if ([contacts objectForKey:contact.uniqueID] == nil) {
   580 		NSString *uniqueID = contact.uniqueID;
   581 		/* So they haven't been seen before... not to worry we'll add them */
   582 		if (contact.uniqueID != nil) {
   583 			contact = [[AWEzvContact alloc] init];
   584 			contact.uniqueID = uniqueID;
   585 			contact.manager = self;
   586 			/* save contact in dictionary */
   587 			[contacts setObject:contact forKey:contact.uniqueID];
   588 			[contact autorelease];
   589 		} else {
   590 			[[client client] reportError:@"Contact to update not in dictionary and has bad identifier" ofLevel:AWEzvError];
   591 		}
   592 	}
   593 
   594 	if ([rendezvousData getField:@"slumming"] != nil)
   595 		// We don't want to live in a slum
   596 		return;
   597 
   598 	if ([contact rendezvous] != nil) {
   599 		oldrendezvous = [contact rendezvous];
   600 		/* check serials */
   601 		if ([contact serial] > [contact serial]) {
   602 			/* AWEzvLog(@"Rendezvous update for %@ with lower serial, updating anyway", contact.uniqueID); */
   603 			/* we'll update anyway, and hopefully we'll be back in sync with the network */
   604 		}
   605 	}
   606 	[contact setRendezvous:rendezvousData];
   607 	
   608 	/* now we can update the contact */
   609 	/* get the nickname */
   610 	if ([rendezvousData getField:@"1st"] != nil)
   611 	nick = [rendezvousData getField:@"1st"];
   612 	if ([rendezvousData getField:@"last"] != nil)
   613 	if (nick == nil) {
   614 		nick = [rendezvousData getField:@"last"];
   615 	} else {
   616 		mutableNick = [[nick mutableCopy] autorelease];
   617 		[mutableNick appendString:@" "];
   618 		[mutableNick appendString:[rendezvousData getField:@"last"]];
   619 		nick = [[mutableNick copy] autorelease];
   620 	} else if (nick == nil)
   621 	    nick = @"Unnamed contact";
   622 
   623 	[contact setName:nick];
   624 
   625 	/* now get the status */
   626 	if ([rendezvousData getField:@"status"] == nil) {
   627 		[contact setStatus: AWEzvOnline];
   628 	} else {
   629 		if ([[rendezvousData getField:@"status"] isEqualToString:@"avail"])
   630 			[contact setStatus: AWEzvOnline];
   631 		else if ([[rendezvousData getField:@"status"] isEqualToString:@"dnd"])
   632 			[contact setStatus: AWEzvAway];
   633 		else if ([[rendezvousData getField:@"status"] isEqualToString:@"away"])
   634 			[contact setStatus: AWEzvIdle];
   635 		else
   636 			[contact setStatus: AWEzvOnline];
   637 	}
   638 	
   639 	/* Set idle time */
   640 	if ([rendezvousData getField:@"away"])
   641 		idleTime = [NSNumber numberWithLong:strtol([[rendezvousData getField:@"away"] UTF8String], NULL, 0)];
   642 	if (idleTime)
   643 		[contact setIdleSinceDate: [NSDate dateWithTimeIntervalSinceReferenceDate:[idleTime doubleValue]]];
   644 	
   645 	/* Update Buddy Icon */
   646 	if ([rendezvousData getField:@"phsh"] != nil) {
   647 		/* We should check to see if this is a new phsh */
   648 		NSString *hash = [contact imageHash];
   649 		NSString *newHash = [rendezvousData getField:@"phsh"];
   650 		AILogWithSignature(@"received image hash %@ for %@", newHash, contact);
   651 		if (hash == NULL || [newHash compare: hash] != NSOrderedSame) {
   652 			[contact setImageHash: newHash];	
   653 			/* The two hashes are different or there was no image before so there is an image to be downloaded */
   654 			/* Download the image using DNSServiceQueryRecord */
   655 			DNSServiceErrorType err;
   656 			DNSServiceRef		serviceRef;
   657 
   658 			NSString *dnsname = [NSString stringWithFormat:@"%@%s", contact.uniqueID,"._presence._tcp.local."];
   659 			err = DNSServiceQueryRecord( &serviceRef, (DNSServiceFlags) 0, interface, [dnsname UTF8String], 
   660 			                            kDNSServiceType_NULL, kDNSServiceClass_IN, ImageQueryRecordReply, contact);
   661 			if ( err == kDNSServiceErr_NoError) {
   662 				ServiceController *temp = [[ServiceController alloc] initWithServiceRef:serviceRef forContactManager:self];
   663 				AILogWithSignature(@"requesting image with %@", temp);
   664 				[contact setImageServiceController:temp];
   665 				[[contact imageServiceController] addToCurrentRunLoop];
   666 				[temp release];
   667 			} else {
   668 				[contact setImageHash: NULL];
   669 				[[client client] reportError:@"Error finding image for contact" ofLevel:AWEzvError];
   670 			}
   671 		}
   672 	} else {
   673 		[contact setContactImageData:nil];
   674 		[[client client] userChangedImage:contact];
   675 	}
   676 
   677 	/* now set the port */
   678 	if (recPort == 0) {
   679 		/* Couldn't find port from browse result so use port specified by txt records */
   680 		if ([rendezvousData getField:@"port.p2pj"] == nil) {
   681 			[[client client] reportError:@"Invalid rendezvous announcement for contact: no port specified" ofLevel:AWEzvError];
   682 			return;
   683 		}
   684 		[contact setPort:[[rendezvousData getField:@"port.p2pj"] intValue]];
   685 	} else {
   686 		/* Correctly use port specified by SRV record */
   687 		[contact setPort:recPort];
   688 	}
   689 	/* and notify of new user */
   690 	[[client client] userChangedState:contact];
   691 }
   692 
   693 - (NSString *)myInstanceName{
   694 	return avInstanceName;
   695 }
   696 
   697 - (void)setInstanceName:(NSString *)newName{
   698 	if (avInstanceName != newName) {
   699 		[avInstanceName release];
   700 		avInstanceName = [newName retain];
   701 	}
   702 }
   703 
   704 - (void) regCallBack:(int)errorCode {
   705 	/* Recover if there was an error */
   706     if (errorCode != kDNSServiceErr_NoError) {
   707 		switch (errorCode) {
   708 #warning Localize and report through the connection error system
   709 			case kDNSServiceErr_Unknown:
   710 				[[[self client] client] reportError:@"Unknown error in Bonjour Registration"
   711 						        ofLevel:AWEzvConnectionError];
   712 				break;
   713 			case kDNSServiceErr_NameConflict:
   714 				[[[self client] client] reportError:@"A user with your Bonjour data is already online"
   715 						        ofLevel:AWEzvConnectionError];
   716 				break;
   717 			default:
   718 				[[[self client] client] reportError:@"An internal error occurred"
   719 						        ofLevel:AWEzvConnectionError];
   720 				AWEzvLog(@"Internal error: rendezvous code %d", errorCode);
   721 				break;
   722 		}
   723 		/* kill connections */
   724 		[self disconnect];
   725 	} else {
   726 		[self setConnected:YES];
   727 		[self startBrowsing];
   728 	}
   729 
   730 }
   731 
   732 - (void)contactWillDeallocate:(AWEzvContact *)contact
   733 {
   734 	[[client client] userLoggedOut:contact];
   735 }
   736 
   737 - (void)serviceControllerReceivedFatalError:(ServiceController *)serviceController
   738 {
   739 	[[[self client] client] reportError:@"An unrecoverable connection error occurred"
   740 						        ofLevel:AWEzvConnectionError];
   741 	[self disconnect];
   742 }
   743 
   744 @end
   745 
   746 #pragma mark mDNS callbacks
   747 
   748 #pragma mark mDNS Register Callbacks
   749 void register_reply (DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context ) {
   750 	AWEzvContactManager *self = context;
   751 	[self setInstanceName:[NSString stringWithUTF8String:name]];
   752 	[self regCallBack:errorCode];
   753 }
   754 
   755 void image_register_reply ( 
   756 	DNSServiceRef sdRef, 
   757 	DNSRecordRef RecordRef, 
   758 	DNSServiceFlags flags, 
   759 	DNSServiceErrorType errorCode, 
   760 	void *context ) {
   761 	if (errorCode != kDNSServiceErr_NoError) {
   762 		AWEzvLog(@"error %d registering image record", errorCode);
   763 	} else {
   764 		AWEzvContactManager *self = context;
   765 		[self updatePHSH];
   766 	}
   767 }
   768 #pragma mark mDNS Browse Callback
   769 
   770 /*!
   771  * @brief DNSServiceBrowse callback
   772  *
   773  * This may be called multiple times for a single use of DNSServiceBrowse().
   774  */
   775 void handle_av_browse_reply (DNSServiceRef sdRef,
   776 		DNSServiceFlags flags,
   777 		uint32_t interfaceIndex,
   778 		DNSServiceErrorType errorCode,
   779 		const char *serviceName,
   780 		const char *regtype,
   781 		const char *replyDomain,
   782 		void *context ) {
   783 	/* Received a browser reply from DNSServiceBrowse for av, now must handle processing the list of results */
   784 	if (errorCode == kDNSServiceErr_NoError) {
   785 		AWEzvContactManager *self = context;
   786 	    if (![[self myInstanceName] isEqualToString:[NSString stringWithUTF8String:serviceName]])
   787 			[self browseResultwithFlags:flags onInterface:interfaceIndex name:serviceName type:regtype domain:replyDomain av:YES];
   788 	} else {
   789 		AWEzvLog(@"Error browsing");
   790 	}
   791 }
   792 
   793 #pragma mark mDNS Resolve Callback
   794 /*!
   795 * @brief DNSServiceResolve callback
   796  *
   797  * This may be called multiple times for a single use of DNSServiceResolve().
   798  */
   799 void resolve_reply (DNSServiceRef sdRef, 
   800 					DNSServiceFlags flags, 
   801 					uint32_t interfaceIndex, 
   802 					DNSServiceErrorType errorCode, 
   803 					const char *fullname, 
   804 					const char *hosttarget, 
   805 					uint16_t port, 
   806 					uint16_t txtLen, 
   807 					const unsigned char *txtRecord, 
   808 					void *context)
   809 {
   810 	if (errorCode == kDNSServiceErr_NoError) {
   811 		/* use TXTRecord methods to resolve this */
   812 		AWEzvContact	*contact = context;
   813 		AWEzvContactManager *self = [contact manager];
   814 		//AWEzvLog(@"Would update contact");
   815 		AWEzvRendezvousData *data;
   816 		data = [[[AWEzvRendezvousData alloc] initWithTXTRecordRef:txtRecord length:txtLen] autorelease];
   817 		[self findAddressForContact:contact withHost:[NSString stringWithUTF8String:hosttarget] withInterface:interfaceIndex];
   818 		[self updateContact:contact withData:data withHost:[NSString stringWithUTF8String:hosttarget] withInterface:interfaceIndex withPort:ntohs(port) av:YES];
   819 
   820 	} else {
   821 		AWEzvLog(@"Error resolving records");
   822 	}	
   823 }
   824 
   825 #pragma mark mDNS Address Callback
   826 
   827 void AddressQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, 
   828 								DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, 
   829 								uint16_t rdlen, const void *rdata, uint32_t ttl, void *context )
   830 // DNSServiceQueryRecord callback used to look up IP addresses.
   831 {
   832 	AWEzvContact	*contact = context;
   833 	AWEzvContactManager *self = [contact manager];
   834 
   835 	[self updateAddressForContact:contact addr:rdata addrLen:rdlen host:fullname interfaceIndex:interfaceIndex 
   836 						more:((flags & kDNSServiceFlagsMoreComing) != 0)];
   837 
   838 }
   839 
   840 #pragma mark mDNS Image Callback
   841 void ImageQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, 
   842 								DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, 
   843 								uint16_t rdlen, const void *rdata, uint32_t ttl, void *context )
   844 // DNSServiceQueryRecord callback used to look up buddy icon.
   845 {
   846 	AWEzvContact	*contact = context;
   847 	AWEzvContactManager *self = [contact manager];
   848 	if (errorCode == kDNSServiceErr_NoError) {
   849 		if (flags & kDNSServiceFlagsAdd)
   850 			[self updateImageForContact:contact data:rdata dataLen:rdlen more:((flags & kDNSServiceFlagsMoreComing) != 0)];
   851 	}
   852 }
   853 
   854 #pragma mark Service Controller
   855 
   856 /* ServiceController was taken from Apple's DNSServiceBrowser.m */
   857 @implementation ServiceController : NSObject
   858 
   859 #pragma mark CFSocket Callback
   860 /* This code was taken from Apple's DNSServiceBrowser.m */
   861 static void	ProcessSockData( CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
   862 // CFRunloop callback that notifies dns_sd when new data appears on a DNSServiceRef's socket.
   863 {
   864 	ServiceController *self = (ServiceController *)info;
   865 	AILogWithSignature(@"Processing result for %@", self);
   866 
   867 	DNSServiceErrorType err = DNSServiceProcessResult([self serviceRef]);
   868 	if (err != kDNSServiceErr_NoError) {
   869 		if ((err == kDNSServiceErr_Unknown) && !data) {
   870 			//Try to accept(2) a connection. May be the cause of a hang on Tiger; see #7887.
   871 			int socketFD = CFSocketGetNative(s);
   872 			int childFD = accept(socketFD, /*addr*/ NULL, /*addrlen*/ NULL);
   873 			AILog(@"%@: Service ref %p received an unknown error with no data; perhaps mDNSResponder crashed? Result of calling accept(2) on fd %d is %d; will disconnect with error",
   874 				  self, [self serviceRef], socketFD, childFD);
   875 			//We don't actually *want* a connection, so close the socket immediately.
   876 			if (childFD > -1) close(childFD);
   877 
   878 			[self retain];
   879 			[[self contactManager] serviceControllerReceivedFatalError:self];
   880 			[self breakdownServiceController];
   881 			[self release];
   882 
   883 		} else {
   884 			AILog(@"DNSServiceProcessResult() for socket descriptor %d returned an error! %d with CFSocketCallBackType %d and data %s\n",
   885 				  DNSServiceRefSockFD(info), err, type, data);
   886 		}
   887 	}
   888 }
   889 
   890 - (id) initWithServiceRef:(DNSServiceRef) ref forContactManager:(AWEzvContactManager *)inContactManager
   891 {
   892 	[super init];
   893 	fServiceRef = ref;
   894 	contactManager = [inContactManager retain];
   895 
   896 	return self;
   897 }
   898 
   899 - (boolean_t) addToCurrentRunLoop
   900 /* Add the service to the current runloop. Returns non-zero on success. */
   901 {
   902 	CFSocketContext			ctx = { 1, self, NULL, NULL, NULL };
   903 
   904 	fSocketRef = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(fServiceRef),
   905 										kCFSocketReadCallBack, ProcessSockData, &ctx);
   906 	if (fSocketRef != NULL)
   907 		fRunloopSrc = CFSocketCreateRunLoopSource(kCFAllocatorDefault, fSocketRef, 1);
   908 	if (fRunloopSrc != NULL) {
   909 		AILogWithSignature(@"Adding run loop source %p from run loop %p", fRunloopSrc, CFRunLoopGetCurrent());
   910 		CFRunLoopAddSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
   911 	} else
   912 		AILog(@"%@: Could not listen to runloop socket", self);
   913 
   914 	return (fRunloopSrc != NULL);
   915 }
   916 
   917 - (DNSServiceRef) serviceRef
   918 {
   919 	return fServiceRef;
   920 }
   921 
   922 - (AWEzvContactManager *)contactManager
   923 {
   924 	return contactManager;
   925 }
   926 
   927 - (void) dealloc
   928 /* Remove service from runloop, deallocate service and associated resources */
   929 {
   930 	AILogWithSignature(@"%@", self);
   931 
   932 	[self breakdownServiceController];
   933 
   934 	[super dealloc];
   935 }
   936 
   937 - (void)breakdownServiceController
   938 {
   939 	AILogWithSignature(@"%@", self);
   940 
   941 	if (fSocketRef != NULL) {
   942 		CFSocketInvalidate(fSocketRef);		// Note: Also closes the underlying socket
   943 		CFRelease(fSocketRef);
   944 		fSocketRef = NULL;
   945 	}
   946 
   947 	if (fRunloopSrc != NULL) {
   948 		AILogWithSignature(@"Removing run loop source %p from run loop %p", fRunloopSrc, CFRunLoopGetCurrent());
   949 		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
   950 		CFRelease(fRunloopSrc);
   951 		fRunloopSrc = NULL;
   952 	}
   953 
   954 	if (fServiceRef) {
   955 		AILogWithSignature(@"Deallocating DNSServiceRef %p", fServiceRef);
   956 
   957 		DNSServiceRefDeallocate(fServiceRef);
   958 		fServiceRef = NULL;
   959 	}
   960 
   961 	[contactManager release]; contactManager = nil;
   962 }
   963 
   964 @end // implementation ServiceController