/* * Copyright (c) 2010, 2011, 2012, Jonathan Schleifer * Copyright (c) 2011, 2012, Florian Zeitz * * https://webkeks.org/git/?p=objxmpp.git * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice is present in all copies. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define XMPP_CONNECTION_M #include #include #include #import #import #import #import #import "XMPPConnection.h" #import "XMPPCallback.h" #import "XMPPSRVLookup.h" #import "XMPPEXTERNALAuth.h" #import "XMPPSCRAMAuth.h" #import "XMPPPLAINAuth.h" #import "XMPPStanza.h" #import "XMPPJID.h" #import "XMPPIQ.h" #import "XMPPMessage.h" #import "XMPPPresence.h" #import "XMPPMulticastDelegate.h" #import "XMPPExceptions.h" #import "XMPPXMLElementBuilder.h" #import "namespaces.h" #import #define BUFFER_LENGTH 512 @implementation XMPPConnection + connection { return [[[self alloc] init] autorelease]; } - init { self = [super init]; @try { port = 5222; encrypted = NO; streamOpen = NO; delegates = [[XMPPMulticastDelegate alloc] init]; callbacks = [[OFMutableDictionary alloc] init]; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [sock release]; [parser release]; [elementBuilder release]; [username release]; [password release]; [privateKeyFile release]; [certificateFile release]; [server release]; [domain release]; [resource release]; [JID release]; [delegates release]; [callbacks release]; [authModule release]; [super dealloc]; } - (void)setUsername: (OFString*)username_ { OFString *old = username; char *node; Stringprep_rc rc; if ((rc = stringprep_profile([username_ UTF8String], &node, "SASLprep", 0)) != STRINGPREP_OK) @throw [XMPPStringPrepFailedException exceptionWithClass: [self class] connection: self profile: @"SASLprep" string: username_]; @try { username = [[OFString alloc] initWithUTF8String: node]; } @finally { free(node); } [old release]; } - (OFString*)username { return [[username copy] autorelease]; } - (void)setResource: (OFString*)resource_ { OFString *old = resource; char *res; Stringprep_rc rc; if ((rc = stringprep_profile([resource_ UTF8String], &res, "Resourceprep", 0)) != STRINGPREP_OK) @throw [XMPPStringPrepFailedException exceptionWithClass: [self class] connection: self profile: @"Resourceprep" string: resource_]; @try { resource = [[OFString alloc] initWithUTF8String: res]; } @finally { free(res); } [old release]; } - (OFString*)resource { return [[resource copy] autorelease]; } - (void)setServer: (OFString*)server_ { OFString *old = server; server = [self XMPP_IDNAToASCII: server_]; [old release]; } - (OFString*)server { return [[server copy] autorelease]; } - (void)setDomain: (OFString*)domain_ { OFString *oldDomain = domain; OFString *oldDomainToASCII = domainToASCII; char *srv; Stringprep_rc rc; if ((rc = stringprep_profile([domain_ UTF8String], &srv, "Nameprep", 0)) != STRINGPREP_OK) @throw [XMPPStringPrepFailedException exceptionWithClass: [self class] connection: self profile: @"Nameprep" string: domain_]; @try { domain = [[OFString alloc] initWithUTF8String: srv]; } @finally { free(srv); } [oldDomain release]; domainToASCII = [self XMPP_IDNAToASCII: domain]; [oldDomainToASCII release]; } - (OFString*)domain { return [[domain copy] autorelease]; } - (void)setPassword: (OFString*)password_ { OFString *old = password; char *pass; Stringprep_rc rc; if ((rc = stringprep_profile([password_ UTF8String], &pass, "SASLprep", 0)) != STRINGPREP_OK) @throw [XMPPStringPrepFailedException exceptionWithClass: [self class] connection: self profile: @"SASLprep" string: password_]; @try { password = [[OFString alloc] initWithUTF8String: pass]; } @finally { free(pass); } [old release]; } - (OFString*)password { return [[password copy] autorelease]; } - (void)setPrivateKeyFile: (OFString*)file { OF_SETTER(privateKeyFile, file, YES, YES) } - (OFString*)privateKeyFile { OF_GETTER(privateKeyFile, YES) } - (void)setCertificateFile: (OFString*)file { OF_SETTER(certificateFile, file, YES, YES) } - (OFString*)certificateFile { OF_GETTER(certificateFile, YES) } - (void)connect { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; XMPPSRVEntry *candidate = nil; XMPPSRVLookup *SRVLookup = nil; OFEnumerator *enumerator; if (sock != nil) @throw [OFAlreadyConnectedException exceptionWithClass: [self class]]; sock = [[OFTCPSocket alloc] init]; if (server) [sock connectToHost: [self XMPP_IDNAToASCII: server] port: port]; else { @try { SRVLookup = [XMPPSRVLookup lookupWithDomain: domainToASCII]; } @catch (id e) { } enumerator = [SRVLookup objectEnumerator]; /* Iterate over SRV records, if any */ if ((candidate = [enumerator nextObject]) != nil) { do { @try { [sock connectToHost: [candidate target] port: [candidate port]]; break; } @catch (OFAddressTranslationFailedException *e) { } @catch (OFConnectionFailedException *e) { } } while ((candidate = [enumerator nextObject]) != nil); } else /* No SRV records -> fall back to A / AAAA record */ [sock connectToHost: domainToASCII port: port]; } [self XMPP_startStream]; [pool release]; } - (void)handleConnection { char *buffer = [self allocMemoryWithSize: BUFFER_LENGTH]; [sock asyncReadIntoBuffer: buffer length: BUFFER_LENGTH target: self selector: @selector(stream:didReadIntoBuffer:length: exception:)]; } - (BOOL)XMPP_parseBuffer: (const void*)buffer length: (size_t)length { if ([sock isAtEndOfStream]) { [delegates broadcastSelector: @selector(connectionWasClosed:) withObject: self]; return NO; } @try { [parser parseBuffer: buffer length: length]; } @catch (OFMalformedXMLException *e) { [self XMPP_sendStreamError: @"bad-format" text: nil]; [self close]; return NO; } return YES; } - (void)parseBuffer: (const void*)buffer length: (size_t)length { [self XMPP_parseBuffer: buffer length: length]; [oldParser release]; [oldElementBuilder release]; oldParser = nil; oldElementBuilder = nil; } - (BOOL)stream: (OFStream*)stream didReadIntoBuffer: (char*)buffer length: (size_t)length exception: (OFException*)exception { if (exception != nil) { [delegates broadcastSelector: @selector(connection: didThrowException::) withObject: self withObject: exception]; [self close]; return NO; } @try { if (![self XMPP_parseBuffer: buffer length: length]) return NO; } @catch (id e) { [delegates broadcastSelector: @selector(connection: didThrowException::) withObject: self withObject: e]; [self close]; return NO; } if (oldParser != nil || oldElementBuilder != nil) { [oldParser release]; [oldElementBuilder release]; oldParser = nil; oldElementBuilder = nil; [sock asyncReadIntoBuffer: buffer length: BUFFER_LENGTH target: self selector: @selector(stream:didReadIntoBuffer: length:exception:)]; return NO; } return YES; } - (OFTCPSocket*)socket { return [[sock retain] autorelease]; } - (BOOL)encryptionRequired { return encryptionRequired; } - (void)setEncryptionRequired: (BOOL)required { encryptionRequired = required; } - (BOOL)encrypted { return encrypted; } - (BOOL)streamOpen { return streamOpen; } - (BOOL)supportsRosterVersioning { return supportsRosterVersioning; } - (BOOL)supportsStreamManagement { return supportsStreamManagement; } - (BOOL)checkCertificateAndGetReason: (OFString**)reason { X509Certificate *cert; OFDictionary *SANs; BOOL serviceSpecific = NO; @try { [sock verifyPeerCertificate]; } @catch (SSLInvalidCertificateException *e) { if (reason != NULL) *reason = [[[e reason] copy] autorelease]; return NO; } cert = [sock peerCertificate]; SANs = [cert subjectAlternativeName]; if ([[SANs objectForKey: @"otherName"] objectForKey: OID_SRVName] != nil || [SANs objectForKey: @"dNSName"] != nil || [SANs objectForKey: @"uniformResourceIdentifier"] != nil) serviceSpecific = YES; if ([cert hasSRVNameMatchingDomain: domainToASCII service: @"xmpp-client"] || [cert hasDNSNameMatchingDomain: domainToASCII]) return YES; if (!serviceSpecific && [cert hasCommonNameMatchingDomain: domainToASCII]) return YES; return NO; } - (void)sendStanza: (OFXMLElement*)element { [delegates broadcastSelector: @selector(connection:didSendElement:) withObject: self withObject: element]; [sock writeString: [element XMLString]]; } - (void)sendIQ: (XMPPIQ*)iq callbackTarget: (id)target selector: (SEL)selector { OFAutoreleasePool *pool; XMPPCallback *callback; if (![iq ID]) [iq setID: [self generateStanzaID]]; pool = [[OFAutoreleasePool alloc] init]; callback = [XMPPCallback callbackWithTarget: target selector: selector]; [callbacks setObject: callback forKey: [iq ID]]; [pool release]; [self sendStanza: iq]; } #ifdef OF_HAVE_BLOCKS - (void)sendIQ: (XMPPIQ*)iq callbackBlock: (xmpp_callback_block_t)block { OFAutoreleasePool *pool; XMPPCallback *callback; if (![iq ID]) [iq setID: [self generateStanzaID]]; pool = [[OFAutoreleasePool alloc] init]; callback = [XMPPCallback callbackWithBlock: block]; [callbacks setObject: callback forKey: [iq ID]]; [pool release]; [self sendStanza: iq]; } #endif - (OFString*)generateStanzaID { return [OFString stringWithFormat: @"objxmpp_%u", lastID++]; } - (void)parser: (OFXMLParser*)p didStartElement: (OFString*)name withPrefix: (OFString*)prefix namespace: (OFString*)ns attributes: (OFArray*)attributes { OFEnumerator *enumerator; OFXMLAttribute *attribute; if (![name isEqual: @"stream"]) { // No dedicated stream error for this, may not even be XMPP [self close]; [sock close]; return; } if (![prefix isEqual: @"stream"]) { [self XMPP_sendStreamError: @"bad-namespace-prefix" text: nil]; return; } if (![ns isEqual: XMPP_NS_STREAM]) { [self XMPP_sendStreamError: @"invalid-namespace" text: nil]; return; } enumerator = [attributes objectEnumerator]; while ((attribute = [enumerator nextObject]) != nil) { if ([[attribute name] isEqual: @"from"] && ![[attribute stringValue] isEqual: domain]) { [self XMPP_sendStreamError: @"invalid-from" text: nil]; return; } if ([[attribute name] isEqual: @"version"] && ![[attribute stringValue] isEqual: @"1.0"]) { [self XMPP_sendStreamError: @"unsupported-version" text: nil]; return; } } [parser setDelegate: elementBuilder]; } - (void)elementBuilder: (OFXMLElementBuilder*)builder didBuildElement: (OFXMLElement*)element { /* Ignore whitespace elements */ if ([element name] == nil) return; [element setDefaultNamespace: XMPP_NS_CLIENT]; [element setPrefix: @"stream" forNamespace: XMPP_NS_STREAM]; [delegates broadcastSelector: @selector(connection:didReceiveElement:) withObject: self withObject: element]; if ([[element namespace] isEqual: XMPP_NS_CLIENT]) [self XMPP_handleStanza: element]; if ([[element namespace] isEqual: XMPP_NS_STREAM]) [self XMPP_handleStream: element]; if ([[element namespace] isEqual: XMPP_NS_STARTTLS]) [self XMPP_handleTLS: element]; if ([[element namespace] isEqual: XMPP_NS_SASL]) [self XMPP_handleSASL: element]; } - (void)elementBuilder: (OFXMLElementBuilder*)builder didNotExpectCloseTag: (OFString*)name withPrefix: (OFString*)prefix namespace: (OFString*)ns { if (![name isEqual: @"stream"] || ![prefix isEqual: @"stream"] || ![ns isEqual: XMPP_NS_STREAM]) @throw [OFMalformedXMLException exceptionWithClass: [builder class] parser: nil]; else { [self close]; } } - (void)XMPP_startStream { OFString *langString = @""; /* Make sure we don't get any old events */ [parser setDelegate: nil]; [elementBuilder setDelegate: nil]; /* * We can't release them now, as we are currently inside them. Release * them the next time the parser returns. */ oldParser = parser; oldElementBuilder = elementBuilder; parser = [[OFXMLParser alloc] init]; [parser setDelegate: self]; elementBuilder = [[XMPPXMLElementBuilder alloc] init]; [elementBuilder setDelegate: self]; if (language != nil) langString = [OFString stringWithFormat: @"xml:lang='%@' ", language]; [sock writeFormat: @"\n" @"", domain, langString]; streamOpen = YES; } - (void)close { if (streamOpen) [sock writeString: @""]; [oldParser release]; oldParser = nil; [oldElementBuilder release]; oldElementBuilder = nil; [authModule release]; authModule = nil; [sock release]; sock = nil; [JID release]; JID = nil; streamOpen = needsSession = encrypted = NO; supportsRosterVersioning = supportsStreamManagement = NO; lastID = 0; } - (void)XMPP_handleStanza: (OFXMLElement*)element { if ([[element name] isEqual: @"iq"]) { [self XMPP_handleIQ: [XMPPIQ stanzaWithElement: element]]; return; } if ([[element name] isEqual: @"message"]) { [self XMPP_handleMessage: [XMPPMessage stanzaWithElement: element]]; return; } if ([[element name] isEqual: @"presence"]) { [self XMPP_handlePresence: [XMPPPresence stanzaWithElement: element]]; return; } [self XMPP_sendStreamError: @"unsupported-stanza-type" text: nil]; } - (void)XMPP_handleStream: (OFXMLElement*)element { if ([[element name] isEqual: @"features"]) { [self XMPP_handleFeatures: element]; return; } if ([[element name] isEqual: @"error"]) { OFString *condition, *reason; [self close]; [sock close]; // Remote has already closed his stream if ([element elementForName: @"bad-format" namespace: XMPP_NS_XMPP_STREAM]) condition = @"bad-format"; else if ([element elementForName: @"bad-namespace-prefix" namespace: XMPP_NS_XMPP_STREAM]) condition = @"bad-namespace-prefix"; else if ([element elementForName: @"conflict" namespace: XMPP_NS_XMPP_STREAM]) condition = @"conflict"; else if ([element elementForName: @"connection-timeout" namespace: XMPP_NS_XMPP_STREAM]) condition = @"connection-timeout"; else if ([element elementForName: @"host-gone" namespace: XMPP_NS_XMPP_STREAM]) condition = @"host-gone"; else if ([element elementForName: @"host-unknown" namespace: XMPP_NS_XMPP_STREAM]) condition = @"host-unknown"; else if ([element elementForName: @"improper-addressing" namespace: XMPP_NS_XMPP_STREAM]) condition = @"improper-addressing"; else if ([element elementForName: @"internal-server-error" namespace: XMPP_NS_XMPP_STREAM]) condition = @"internal-server-error"; else if ([element elementForName: @"invalid-from" namespace: XMPP_NS_XMPP_STREAM]) condition = @"invalid-from"; else if ([element elementForName: @"invalid-namespace" namespace: XMPP_NS_XMPP_STREAM]) condition = @"invalid-namespace"; else if ([element elementForName: @"invalid-xml" namespace: XMPP_NS_XMPP_STREAM]) condition = @"invalid-xml"; else if ([element elementForName: @"not-authorized" namespace: XMPP_NS_XMPP_STREAM]) condition = @"not-authorized"; else if ([element elementForName: @"not-well-formed" namespace: XMPP_NS_XMPP_STREAM]) condition = @"not-well-formed"; else if ([element elementForName: @"policy-violation" namespace: XMPP_NS_XMPP_STREAM]) condition = @"policy-violation"; else if ([element elementForName: @"remote-connection-failed" namespace: XMPP_NS_XMPP_STREAM]) condition = @"remote-connection-failed"; else if ([element elementForName: @"reset" namespace: XMPP_NS_XMPP_STREAM]) condition = @"reset"; else if ([element elementForName: @"resource-constraint" namespace: XMPP_NS_XMPP_STREAM]) condition = @"resource-constraint"; else if ([element elementForName: @"restricted-xml" namespace: XMPP_NS_XMPP_STREAM]) condition = @"restricted-xml"; else if ([element elementForName: @"see-other-host" namespace: XMPP_NS_XMPP_STREAM]) condition = @"see-other-host"; else if ([element elementForName: @"system-shutdown" namespace: XMPP_NS_XMPP_STREAM]) condition = @"system-shutdown"; else if ([element elementForName: @"undefined-condition" namespace: XMPP_NS_XMPP_STREAM]) condition = @"undefined-condition"; else if ([element elementForName: @"unsupported-encoding" namespace: XMPP_NS_XMPP_STREAM]) condition = @"unsupported-encoding"; else if ([element elementForName: @"unsupported-feature" namespace: XMPP_NS_XMPP_STREAM]) condition = @"unsupported-feature"; else if ([element elementForName: @"unsupported-stanza-type" namespace: XMPP_NS_XMPP_STREAM]) condition = @"unsupported-stanza-type"; else if ([element elementForName: @"unsupported-version" namespace: XMPP_NS_XMPP_STREAM]) condition = @"unsupported-version"; else condition = @"undefined"; reason = [[element elementForName: @"text" namespace: XMPP_NS_XMPP_STREAM] stringValue]; @throw [XMPPStreamErrorException exceptionWithClass: [self class] connection: self condition: condition reason: reason]; return; } assert(0); } - (void)XMPP_handleTLS: (OFXMLElement*)element { if ([[element name] isEqual: @"proceed"]) { /* FIXME: Catch errors here */ SSLSocket *newSock; [delegates broadcastSelector: @selector( connectionWillUpgradeToTLS:) withObject: self]; newSock = [[SSLSocket alloc] initWithSocket: sock privateKeyFile: privateKeyFile certificateFile: certificateFile]; [sock release]; sock = newSock; encrypted = YES; [delegates broadcastSelector: @selector( connectionDidUpgradeToTLS:) withObject: self]; /* Stream restart */ [self XMPP_startStream]; return; } if ([[element name] isEqual: @"failure"]) /* TODO: Find/create an exception to throw here */ @throw [OFException exceptionWithClass: [self class]]; assert(0); } - (void)XMPP_handleSASL: (OFXMLElement*)element { if ([[element name] isEqual: @"challenge"]) { OFXMLElement *responseTag; OFDataArray *challenge = [OFDataArray dataArrayWithBase64EncodedString: [element stringValue]]; OFDataArray *response = [authModule continueWithData: challenge]; responseTag = [OFXMLElement elementWithName: @"response" namespace: XMPP_NS_SASL]; if (response) { if ([response count] == 0) [responseTag setStringValue: @"="]; else [responseTag setStringValue: [response stringByBase64Encoding]]; } [self sendStanza: responseTag]; return; } if ([[element name] isEqual: @"success"]) { [authModule continueWithData: [OFDataArray dataArrayWithBase64EncodedString: [element stringValue]]]; [delegates broadcastSelector: @selector( connectionWasAuthenticated:) withObject: self]; /* Stream restart */ [self XMPP_startStream]; return; } if ([[element name] isEqual: @"failure"]) { of_log(@"Auth failed!"); // FIXME: Do more parsing/handling @throw [XMPPAuthFailedException exceptionWithClass: [self class] connection: self reason: [element XMLString]]; } assert(0); } - (void)XMPP_handleIQ: (XMPPIQ*)iq { BOOL handled = NO; XMPPCallback *callback; if ((callback = [callbacks objectForKey: [iq ID]])) { [callback runWithIQ: iq connection: self]; [callbacks removeObjectForKey: [iq ID]]; return; } handled = [delegates broadcastSelector: @selector( connection:didReceiveIQ:) withObject: self withObject: iq]; if (!handled && ![[iq type] isEqual: @"error"] && ![[iq type] isEqual: @"result"]) { [self sendStanza: [iq errorIQWithType: @"cancel" condition: @"service-unavailable"]]; } } - (void)XMPP_handleMessage: (XMPPMessage*)message { [delegates broadcastSelector: @selector(connection:didReceiveMessage:) withObject: self withObject: message]; } - (void)XMPP_handlePresence: (XMPPPresence*)presence { [delegates broadcastSelector: @selector(connection:didReceivePresence:) withObject: self withObject: presence]; } - (void)XMPP_handleFeatures: (OFXMLElement*)element { OFXMLElement *starttls = [element elementForName: @"starttls" namespace: XMPP_NS_STARTTLS]; OFXMLElement *bind = [element elementForName: @"bind" namespace: XMPP_NS_BIND]; OFXMLElement *session = [element elementForName: @"session" namespace: XMPP_NS_SESSION]; OFXMLElement *mechs = [element elementForName: @"mechanisms" namespace: XMPP_NS_SASL]; OFMutableSet *mechanisms = [OFMutableSet set]; if (starttls != nil) { [self sendStanza: [OFXMLElement elementWithName: @"starttls" namespace: XMPP_NS_STARTTLS]]; return; } if (encryptionRequired && !encrypted) /* TODO: Find/create an exception to throw here */ @throw [OFException exceptionWithClass: [self class]]; if ([element elementForName: @"ver" namespace: XMPP_NS_ROSTERVER] != nil) supportsRosterVersioning = YES; if ([element elementForName: @"sm" namespace: XMPP_NS_SM] != nil) supportsStreamManagement = YES; if (mechs != nil) { OFEnumerator *enumerator; OFXMLElement *mech; enumerator = [[mechs children] objectEnumerator]; while ((mech = [enumerator nextObject]) != nil) [mechanisms addObject: [mech stringValue]]; if (privateKeyFile && certificateFile && [mechanisms containsObject: @"EXTERNAL"]) { authModule = [[XMPPEXTERNALAuth alloc] init]; [self XMPP_sendAuth: @"EXTERNAL"]; return; } if ([mechanisms containsObject: @"SCRAM-SHA-1-PLUS"]) { authModule = [[XMPPSCRAMAuth alloc] initWithAuthcid: username password: password connection: self hash: [OFSHA1Hash class] plusAvailable: YES]; [self XMPP_sendAuth: @"SCRAM-SHA-1-PLUS"]; return; } if ([mechanisms containsObject: @"SCRAM-SHA-1"]) { authModule = [[XMPPSCRAMAuth alloc] initWithAuthcid: username password: password connection: self hash: [OFSHA1Hash class] plusAvailable: NO]; [self XMPP_sendAuth: @"SCRAM-SHA-1"]; return; } if ([mechanisms containsObject: @"PLAIN"] && encrypted) { authModule = [[XMPPPLAINAuth alloc] initWithAuthcid: username password: password]; [self XMPP_sendAuth: @"PLAIN"]; return; } assert(0); } if (session != nil) needsSession = YES; if (bind != nil) { [self XMPP_sendResourceBind]; return; } assert(0); } - (void)XMPP_sendAuth: (OFString*)authName { OFXMLElement *authTag; OFDataArray *initialMessage = [authModule initialMessage]; authTag = [OFXMLElement elementWithName: @"auth" namespace: XMPP_NS_SASL]; [authTag addAttributeWithName: @"mechanism" stringValue: authName]; if (initialMessage) { if ([initialMessage count] == 0) [authTag setStringValue: @"="]; else [authTag setStringValue: [initialMessage stringByBase64Encoding]]; } [self sendStanza: authTag]; } - (void)XMPP_sendResourceBind { XMPPIQ *iq; OFXMLElement *bind; iq = [XMPPIQ IQWithType: @"set" ID: [self generateStanzaID]]; bind = [OFXMLElement elementWithName: @"bind" namespace: XMPP_NS_BIND]; if (resource != nil) [bind addChild: [OFXMLElement elementWithName: @"resource" namespace: XMPP_NS_BIND stringValue: resource]]; [iq addChild: bind]; [self sendIQ: iq callbackTarget: self selector: @selector(XMPP_handleResourceBindForConnection: IQ:)]; } - (void)XMPP_sendStreamError: (OFString*)condition text: (OFString*)text { OFXMLElement *error = [OFXMLElement elementWithName: @"error" namespace: XMPP_NS_STREAM]; [error setPrefix: @"stream" forNamespace: XMPP_NS_STREAM]; [error addChild: [OFXMLElement elementWithName: condition namespace: XMPP_NS_XMPP_STREAM]]; if (text) [error addChild: [OFXMLElement elementWithName: @"text" namespace: XMPP_NS_XMPP_STREAM stringValue: text]]; [parser setDelegate: nil]; [self sendStanza: error]; [self close]; } - (void)XMPP_handleResourceBindForConnection: (XMPPConnection*)connection IQ: (XMPPIQ*)iq { OFXMLElement *bindElement; OFXMLElement *jidElement; assert([[iq type] isEqual: @"result"]); bindElement = [iq elementForName: @"bind" namespace: XMPP_NS_BIND]; assert(bindElement != nil); jidElement = [bindElement elementForName: @"jid" namespace: XMPP_NS_BIND]; JID = [[XMPPJID alloc] initWithString: [jidElement stringValue]]; if (needsSession) { [self XMPP_sendSession]; return; } [delegates broadcastSelector: @selector(connection:wasBoundToJID:) withObject: self withObject: JID]; } - (void)XMPP_sendSession { XMPPIQ *iq; iq = [XMPPIQ IQWithType: @"set" ID: [self generateStanzaID]]; [iq addChild: [OFXMLElement elementWithName: @"session" namespace: XMPP_NS_SESSION]]; [self sendIQ: iq callbackTarget: self selector: @selector(XMPP_handleSessionForConnection:IQ:)]; } - (void)XMPP_handleSessionForConnection: (XMPPConnection*)connection IQ: (XMPPIQ*)iq { if (![[iq type] isEqual: @"result"]) assert(0); [delegates broadcastSelector: @selector(connection:wasBoundToJID:) withObject: self withObject: JID]; } - (OFString*)XMPP_IDNAToASCII: (OFString*)domain_ { OFString *ret; char *cDomain; Idna_rc rc; if ((rc = idna_to_ascii_8z([domain_ UTF8String], &cDomain, IDNA_USE_STD3_ASCII_RULES)) != IDNA_SUCCESS) @throw [XMPPIDNATranslationFailedException exceptionWithClass: [self class] connection: self operation: @"ToASCII" string: domain_]; @try { ret = [[OFString alloc] initWithUTF8String: cDomain]; } @finally { free(cDomain); } return ret; } - (XMPPJID*)JID { return [[JID copy] autorelease]; } - (void)setPort: (uint16_t)port_ { port = port_; } - (uint16_t)port { return port; } - (void)setDataStorage: (id )dataStorage_ { if (streamOpen) @throw [OFInvalidArgumentException exceptionWithClass: [self class]]; dataStorage = dataStorage_; } - (id )dataStorage { return dataStorage; } - (void)setLanguage: (OFString*)language_ { OF_SETTER(language, language_, YES, YES) } - (OFString*)language { OF_GETTER(language, YES) } - (void)addDelegate: (id )delegate { [delegates addDelegate: delegate]; } - (void)removeDelegate: (id )delegate { [delegates removeDelegate: delegate]; } - (XMPPMulticastDelegate*)XMPP_delegates { return delegates; } @end