Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Pluggable authentication modules

  • Loading branch information...
commit e29af14d7e2c64a15b20ef4aadfcb38f73701162 1 parent be5eab5
@robbiehanson authored
Showing with 1,937 additions and 1,137 deletions.
  1. +45 −0 Authentication/Anonymous/XMPPAnonymousAuthentication.h
  2. +131 −0 Authentication/Anonymous/XMPPAnonymousAuthentication.m
  3. +22 −0 Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.h
  4. +162 −0 Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m
  5. +22 −0 Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.h
  6. +156 −0 Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m
  7. +22 −0 Authentication/Digest-MD5/XMPPDigestMD5Authentication.h
  8. +289 −0 Authentication/Digest-MD5/XMPPDigestMD5Authentication.m
  9. +22 −0 Authentication/Plain/XMPPPlainAuthentication.h
  10. +103 −0 Authentication/Plain/XMPPPlainAuthentication.m
  11. +56 −0 Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.h
  12. +319 −0 Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m
  13. +96 −0 Authentication/XMPPSASLAuthentication.h
  14. +20 −0 Core/XMPP.h
  15. +31 −18 Core/XMPPInternal.h
  16. +5 −1 Core/XMPPLogging.h
  17. +64 −37 Core/XMPPStream.h
  18. +163 −680 Core/XMPPStream.m
  19. +0 −43 Utilities/XMPPDigestAuthentication.h
  20. +0 −160 Utilities/XMPPDigestAuthentication.m
  21. +0 −15 Utilities/XMPPSASLAuthentication.h
  22. +0 −32 Utilities/XMPPXFacebookPlatformAuthentication.h
  23. +0 −115 Utilities/XMPPXFacebookPlatformAuthentication.m
  24. +15 −5 Xcode/DesktopXMPP/RosterController.m
  25. +96 −14 Xcode/DesktopXMPP/XMPPStream.xcodeproj/project.pbxproj
  26. +1 −1  Xcode/iPhoneXMPP/Classes/XMPPFramework.h
  27. +97 −16 Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj
View
45 Authentication/Anonymous/XMPPAnonymousAuthentication.h
@@ -0,0 +1,45 @@
+#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
+#import "XMPP.h"
+
+
+@interface XMPPAnonymousAuthentication : NSObject <XMPPSASLAuthentication>
+
+- (id)initWithStream:(XMPPStream *)stream;
+
+// This class implements the XMPPSASLAuthentication protocol.
+//
+// See XMPPSASLAuthentication.h for more information.
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPStream (XMPPAnonymousAuthentication)
+
+/**
+ * Returns whether or not the server support anonymous authentication.
+ *
+ * This information is available after the stream is connected.
+ * In other words, after the delegate has received xmppStreamDidConnect: notification.
+**/
+- (BOOL)supportsAnonymousAuthentication;
+
+/**
+ * This method attempts to start the anonymous authentication process.
+ *
+ * This method is asynchronous.
+ *
+ * If there is something immediately wrong,
+ * such as the stream is not connected or doesn't support anonymous authentication,
+ * the method will return NO and set the error.
+ * Otherwise the delegate callbacks are used to communicate auth success or failure.
+ *
+ * @see xmppStreamDidAuthenticate:
+ * @see xmppStream:didNotAuthenticate:
+**/
+- (BOOL)authenticateAnonymously:(NSError **)errPtr;
+
+@end
View
131 Authentication/Anonymous/XMPPAnonymousAuthentication.m
@@ -0,0 +1,131 @@
+#import "XMPPAnonymousAuthentication.h"
+#import "XMPP.h"
+#import "XMPPLogging.h"
+#import "XMPPInternal.h"
+#import "NSXMLElement+XMPP.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+/**
+ * Seeing a return statements within an inner block
+ * can sometimes be mistaken for a return point of the enclosing method.
+ * This makes inline blocks a bit easier to read.
+**/
+#define return_from_block return
+
+
+@implementation XMPPAnonymousAuthentication
+{
+ #if __has_feature(objc_arc_weak)
+ __weak XMPPStream *xmppStream;
+ #else
+ __unsafe_unretained XMPPStream *xmppStream;
+ #endif
+}
+
++ (NSString *)mechanismName
+{
+ return @"ANONYMOUS";
+}
+
+- (id)initWithStream:(XMPPStream *)stream
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ }
+ return self;
+}
+
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
+{
+ return [self initWithStream:stream];
+}
+
+- (BOOL)start:(NSError **)errPtr
+{
+ // <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS" />
+
+ NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
+ [auth addAttributeWithName:@"mechanism" stringValue:@"ANONYMOUS"];
+
+ [xmppStream sendAuthElement:auth];
+
+ return YES;
+}
+
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
+{
+ // We're expecting a success response.
+ // If we get anything else we can safely assume it's the equivalent of a failure response.
+
+ if ([[authResponse name] isEqualToString:@"success"])
+ {
+ return XMPP_AUTH_SUCCESS;
+ }
+ else
+ {
+ return XMPP_AUTH_FAIL;
+ }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPStream (XMPPAnonymousAuthentication)
+
+- (BOOL)supportsAnonymousAuthentication
+{
+ return [self supportsAuthenticationMechanism:[XMPPAnonymousAuthentication mechanismName]];
+}
+
+- (BOOL)authenticateAnonymously:(NSError **)errPtr
+{
+ XMPPLogTrace();
+
+ __block BOOL result = YES;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if ([self supportsAnonymousAuthentication])
+ {
+ XMPPAnonymousAuthentication *anonymousAuth = [[XMPPAnonymousAuthentication alloc] initWithStream:self];
+
+ result = [self authenticate:anonymousAuth error:&err];
+ }
+ else
+ {
+ NSString *errMsg = @"The server does not support anonymous authentication.";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];
+
+ result = NO;
+ }
+ }};
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+@end
View
22 Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.h
@@ -0,0 +1,22 @@
+#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
+#import "XMPPStream.h"
+
+
+@interface XMPPDeprecatedDigestAuthentication : NSObject <XMPPSASLAuthentication>
+
+// This class implements the XMPPSASLAuthentication protocol.
+//
+// See XMPPSASLAuthentication.h for more information.
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPStream (XMPPDeprecatedDigestAuthentication)
+
+- (BOOL)supportsDeprecatedDigestAuthentication;
+
+@end
View
162 Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m
@@ -0,0 +1,162 @@
+#import "XMPPDeprecatedDigestAuthentication.h"
+#import "XMPP.h"
+#import "XMPPInternal.h"
+#import "XMPPLogging.h"
+#import "NSData+XMPP.h"
+#import "NSXMLElement+XMPP.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+
+@implementation XMPPDeprecatedDigestAuthentication
+{
+ #if __has_feature(objc_arc_weak)
+ __weak XMPPStream *xmppStream;
+ #else
+ __unsafe_unretained XMPPStream *xmppStream;
+ #endif
+
+ NSString *password;
+}
+
++ (NSString *)mechanismName
+{
+ // This deprecated method isn't listed in the normal mechanisms list
+ return nil;
+}
+
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ password = inPassword;
+ }
+ return self;
+}
+
+- (BOOL)start:(NSError **)errPtr
+{
+ XMPPLogTrace();
+
+ // The server does not appear to support SASL authentication (at least any type we can use)
+ // So we'll revert back to the old fashioned jabber:iq:auth mechanism
+
+ XMPPJID *myJID = xmppStream.myJID;
+
+ NSString *username = [myJID user];
+ NSString *resource = [myJID resource];
+
+ if ([resource length] == 0)
+ {
+ // If resource is nil or empty, we need to auto-create one
+
+ resource = [XMPPStream generateUUID];
+ }
+
+ NSString *rootID = [[[xmppStream rootElement] attributeForName:@"id"] stringValue];
+ NSString *digestStr = [NSString stringWithFormat:@"%@%@", rootID, password];
+
+ NSString *digest = [[[digestStr dataUsingEncoding:NSUTF8StringEncoding] sha1Digest] hexStringValue];
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"];
+ [query addChild:[NSXMLElement elementWithName:@"username" stringValue:username]];
+ [query addChild:[NSXMLElement elementWithName:@"resource" stringValue:resource]];
+ [query addChild:[NSXMLElement elementWithName:@"digest" stringValue:digest]];
+
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"set"];
+ [iq addChild:query];
+
+ [xmppStream sendAuthElement:iq];
+
+ return YES;
+}
+
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
+{
+ XMPPLogTrace();
+
+ // We used the old fashioned jabber:iq:auth mechanism
+
+ if ([[authResponse attributeStringValueForName:@"type"] isEqualToString:@"error"])
+ {
+ return XMPP_AUTH_FAIL;
+ }
+ else
+ {
+ return XMPP_AUTH_SUCCESS;
+ }
+}
+
+- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication
+{
+ return NO;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPStream (XMPPDeprecatedDigestAuthentication)
+
+/**
+ * This method only applies to servers that don't support XMPP version 1.0, as defined in RFC 3920.
+ * With these servers, we attempt to discover supported authentication modes via the jabber:iq:auth namespace.
+**/
+- (BOOL)supportsDeprecatedDigestAuthentication
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // The root element can be properly queried for authentication mechanisms anytime after the
+ // stream:features are received, and TLS has been setup (if required)
+ if (state >= STATE_XMPP_POST_NEGOTIATION)
+ {
+ // Search for an iq element within the rootElement.
+ // Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
+ // if we simply used the elementForName method.
+
+ NSXMLElement *iq = nil;
+
+ NSUInteger i, count = [rootElement childCount];
+ for (i = 0; i < count; i++)
+ {
+ NSXMLNode *childNode = [rootElement childAtIndex:i];
+
+ if ([childNode kind] == NSXMLElementKind)
+ {
+ if ([[childNode name] isEqualToString:@"iq"])
+ {
+ iq = (NSXMLElement *)childNode;
+ }
+ }
+ }
+
+ NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:auth"];
+ NSXMLElement *digest = [query elementForName:@"digest"];
+
+ result = (digest != nil);
+ }
+ }};
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ return result;
+}
+
+@end
View
22 Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.h
@@ -0,0 +1,22 @@
+#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
+#import "XMPPStream.h"
+
+
+@interface XMPPDeprecatedPlainAuthentication : NSObject <XMPPSASLAuthentication>
+
+// This class implements the XMPPSASLAuthentication protocol.
+//
+// See XMPPSASLAuthentication.h for more information.
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPStream (XMPPDeprecatedPlainAuthentication)
+
+- (BOOL)supportsDeprecatedPlainAuthentication;
+
+@end
View
156 Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m
@@ -0,0 +1,156 @@
+#import "XMPPDeprecatedPlainAuthentication.h"
+#import "XMPP.h"
+#import "XMPPInternal.h"
+#import "XMPPLogging.h"
+#import "NSXMLElement+XMPP.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+
+@implementation XMPPDeprecatedPlainAuthentication
+{
+ #if __has_feature(objc_arc_weak)
+ __weak XMPPStream *xmppStream;
+ #else
+ __unsafe_unretained XMPPStream *xmppStream;
+ #endif
+
+ NSString *password;
+}
+
++ (NSString *)mechanismName
+{
+ // This deprecated method isn't listed in the normal mechanisms list
+ return nil;
+}
+
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ password = inPassword;
+ }
+ return self;
+}
+
+- (BOOL)start:(NSError **)errPtr
+{
+ XMPPLogTrace();
+
+ // The server does not appear to support SASL authentication (at least any type we can use)
+ // So we'll revert back to the old fashioned jabber:iq:auth mechanism
+
+ XMPPJID *myJID = xmppStream.myJID;
+
+ NSString *username = [myJID user];
+ NSString *resource = [myJID resource];
+
+ if ([resource length] == 0)
+ {
+ // If resource is nil or empty, we need to auto-create one
+
+ resource = [XMPPStream generateUUID];
+ }
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"];
+ [query addChild:[NSXMLElement elementWithName:@"username" stringValue:username]];
+ [query addChild:[NSXMLElement elementWithName:@"resource" stringValue:resource]];
+ [query addChild:[NSXMLElement elementWithName:@"password" stringValue:password]];
+
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"set"];
+ [iq addChild:query];
+
+ [xmppStream sendAuthElement:iq];
+
+ return YES;
+}
+
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
+{
+ XMPPLogTrace();
+
+ // We used the old fashioned jabber:iq:auth mechanism
+
+ if ([[authResponse attributeStringValueForName:@"type"] isEqualToString:@"error"])
+ {
+ return XMPP_AUTH_FAIL;
+ }
+ else
+ {
+ return XMPP_AUTH_SUCCESS;
+ }
+}
+
+- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication
+{
+ return NO;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPStream (XMPPDeprecatedPlainAuthentication)
+
+/**
+ * This method only applies to servers that don't support XMPP version 1.0, as defined in RFC 3920.
+ * With these servers, we attempt to discover supported authentication modes via the jabber:iq:auth namespace.
+**/
+- (BOOL)supportsDeprecatedPlainAuthentication
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // The root element can be properly queried for authentication mechanisms anytime after the
+ // stream:features are received, and TLS has been setup (if required)
+ if (state >= STATE_XMPP_POST_NEGOTIATION)
+ {
+ // Search for an iq element within the rootElement.
+ // Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
+ // if we simply used the elementForName method.
+
+ NSXMLElement *iq = nil;
+
+ NSUInteger i, count = [rootElement childCount];
+ for (i = 0; i < count; i++)
+ {
+ NSXMLNode *childNode = [rootElement childAtIndex:i];
+
+ if ([childNode kind] == NSXMLElementKind)
+ {
+ if ([[childNode name] isEqualToString:@"iq"])
+ {
+ iq = (NSXMLElement *)childNode;
+ }
+ }
+ }
+
+ NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:auth"];
+ NSXMLElement *plain = [query elementForName:@"password"];
+
+ result = (plain != nil);
+ }
+ }};
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ return result;
+}
+
+@end
View
22 Authentication/Digest-MD5/XMPPDigestMD5Authentication.h
@@ -0,0 +1,22 @@
+#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
+#import "XMPPStream.h"
+
+
+@interface XMPPDigestMD5Authentication : NSObject <XMPPSASLAuthentication>
+
+// This class implements the XMPPSASLAuthentication protocol.
+//
+// See XMPPSASLAuthentication.h for more information.
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPStream (XMPPDigestMD5Authentication)
+
+- (BOOL)supportsDigestMD5Authentication;
+
+@end
View
289 Authentication/Digest-MD5/XMPPDigestMD5Authentication.m
@@ -0,0 +1,289 @@
+#import "XMPPDigestMD5Authentication.h"
+#import "XMPP.h"
+#import "XMPPLogging.h"
+#import "XMPPInternal.h"
+#import "NSData+XMPP.h"
+#import "NSXMLElement+XMPP.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+@interface XMPPDigestMD5Authentication ()
+{
+ #if __has_feature(objc_arc_weak)
+ __weak XMPPStream *xmppStream;
+ #else
+ __unsafe_unretained XMPPStream *xmppStream;
+ #endif
+
+ BOOL awaitingChallenge;
+
+ NSString *realm;
+ NSString *nonce;
+ NSString *qop;
+ NSString *cnonce;
+ NSString *digestURI;
+ NSString *username;
+ NSString *password;
+}
+
+- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
+- (NSString *)base64EncodedFullResponse;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPDigestMD5Authentication
+
++ (NSString *)mechanismName
+{
+ return @"DIGEST-MD5";
+}
+
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ password = inPassword;
+ }
+ return self;
+}
+
+- (BOOL)start:(NSError **)errPtr
+{
+ XMPPLogTrace();
+
+ // <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="DIGEST-MD5" />
+
+ NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
+ [auth addAttributeWithName:@"mechanism" stringValue:@"DIGEST-MD5"];
+
+ [xmppStream sendAuthElement:auth];
+ awaitingChallenge = YES;
+
+ return YES;
+}
+
+- (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
+{
+ XMPPLogTrace();
+
+ // We're expecting a challenge response.
+ // If we get anything else we're going to assume it's some kind of failure response.
+
+ if (![[authResponse name] isEqualToString:@"challenge"])
+ {
+ return XMPP_AUTH_FAIL;
+ }
+
+ // Extract components from incoming challenge
+
+ NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
+
+ realm = [auth objectForKey:@"realm"];
+ nonce = [auth objectForKey:@"nonce"];
+ qop = [auth objectForKey:@"qop"];
+
+ // Fill out all the other variables
+ //
+ // Sometimes the realm isn't specified.
+ // In this case I believe the realm is implied as the virtual host name.
+
+ XMPPJID *myJID = xmppStream.myJID;
+
+ NSString *virtualHostName = [myJID domain];
+ NSString *serverHostName = xmppStream.hostName;
+
+ if (realm == nil)
+ {
+ if ([virtualHostName length] > 0)
+ realm = virtualHostName;
+ else
+ realm = serverHostName;
+ }
+
+ if ([virtualHostName length] > 0)
+ digestURI = [NSString stringWithFormat:@"xmpp/%@", virtualHostName];
+ else
+ digestURI = [NSString stringWithFormat:@"xmpp/%@", serverHostName];
+
+ cnonce = [XMPPStream generateUUID];
+ username = [myJID user];
+
+ // Create and send challenge response element
+
+ NSXMLElement *response = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
+ [response setStringValue:[self base64EncodedFullResponse]];
+
+ [xmppStream sendAuthElement:response];
+ awaitingChallenge = NO;
+
+ return XMPP_AUTH_CONTINUE;
+}
+
+- (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse
+{
+ XMPPLogTrace();
+
+ if ([[authResponse name] isEqualToString:@"challenge"])
+ {
+ NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
+ NSString *rspauth = [auth objectForKey:@"rspauth"];
+
+ if (rspauth == nil)
+ {
+ // We're getting another challenge?
+ // Not sure what this could possibly be, so for now we'll assume it's a failure.
+
+ return XMPP_AUTH_FAIL;
+ }
+ else
+ {
+ // We received another challenge, but it's really just an rspauth
+ // This is supposed to be included in the success element (according to the updated RFC)
+ // but many implementations incorrectly send it inside a second challenge request.
+ //
+ // Create and send empty challenge response element.
+
+ NSXMLElement *response =
+ [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
+
+ [xmppStream sendAuthElement:response];
+
+ return XMPP_AUTH_CONTINUE;
+ }
+ }
+ else if ([[authResponse name] isEqualToString:@"success"])
+ {
+ return XMPP_AUTH_SUCCESS;
+ }
+ else
+ {
+ return XMPP_AUTH_FAIL;
+ }
+}
+
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)auth
+{
+ XMPPLogTrace();
+
+ if (awaitingChallenge)
+ {
+ return [self handleAuth1:auth];
+ }
+ else
+ {
+ return [self handleAuth2:auth];
+ }
+}
+
+- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge
+{
+ // The value of the challenge stanza is base 64 encoded.
+ // Once "decoded", it's just a string of key=value pairs separated by commas.
+
+ NSData *base64Data = [[challenge stringValue] dataUsingEncoding:NSASCIIStringEncoding];
+ NSData *decodedData = [base64Data base64Decoded];
+
+ NSString *authStr = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
+
+ XMPPLogVerbose(@"%@: Decoded challenge: %@", THIS_FILE, authStr);
+
+ NSArray *components = [authStr componentsSeparatedByString:@","];
+ NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:5];
+
+ for (NSString *component in components)
+ {
+ NSRange separator = [component rangeOfString:@"="];
+ if (separator.location != NSNotFound)
+ {
+ NSMutableString *key = [[component substringToIndex:separator.location] mutableCopy];
+ NSMutableString *value = [[component substringFromIndex:separator.location+1] mutableCopy];
+
+ if(key) CFStringTrimWhitespace((__bridge CFMutableStringRef)key);
+ if(value) CFStringTrimWhitespace((__bridge CFMutableStringRef)value);
+
+ if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""] && [value length] > 2)
+ {
+ // Strip quotes from value
+ [value deleteCharactersInRange:NSMakeRange(0, 1)];
+ [value deleteCharactersInRange:NSMakeRange([value length]-1, 1)];
+ }
+
+ [auth setObject:value forKey:key];
+ }
+ }
+
+ return auth;
+}
+
+- (NSString *)response
+{
+ NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", username, realm, password];
+ NSString *HA2str = [NSString stringWithFormat:@"AUTHENTICATE:%@", digestURI];
+
+ NSData *HA1dataA = [[HA1str dataUsingEncoding:NSUTF8StringEncoding] md5Digest];
+ NSData *HA1dataB = [[NSString stringWithFormat:@":%@:%@", nonce, cnonce] dataUsingEncoding:NSUTF8StringEncoding];
+
+ NSMutableData *HA1data = [NSMutableData dataWithCapacity:([HA1dataA length] + [HA1dataB length])];
+ [HA1data appendData:HA1dataA];
+ [HA1data appendData:HA1dataB];
+
+ NSString *HA1 = [[HA1data md5Digest] hexStringValue];
+
+ NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+
+ NSString *responseStr = [NSString stringWithFormat:@"%@:%@:00000001:%@:auth:%@",
+ HA1, nonce, cnonce, HA2];
+
+ NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+
+ return response;
+}
+
+- (NSString *)base64EncodedFullResponse
+{
+ NSMutableString *buffer = [NSMutableString stringWithCapacity:100];
+ [buffer appendFormat:@"username=\"%@\",", username];
+ [buffer appendFormat:@"realm=\"%@\",", realm];
+ [buffer appendFormat:@"nonce=\"%@\",", nonce];
+ [buffer appendFormat:@"cnonce=\"%@\",", cnonce];
+ [buffer appendFormat:@"nc=00000001,"];
+ [buffer appendFormat:@"qop=auth,"];
+ [buffer appendFormat:@"digest-uri=\"%@\",", digestURI];
+ [buffer appendFormat:@"response=%@,", [self response]];
+ [buffer appendFormat:@"charset=utf-8"];
+
+ XMPPLogSend(@"decoded response: %@", buffer);
+
+ NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding];
+
+ return [utf8data base64Encoded];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPStream (XMPPDigestMD5Authentication)
+
+- (BOOL)supportsDigestMD5Authentication
+{
+ return [self supportsAuthenticationMechanism:[XMPPDigestMD5Authentication mechanismName]];
+}
+
+@end
View
22 Authentication/Plain/XMPPPlainAuthentication.h
@@ -0,0 +1,22 @@
+#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
+#import "XMPPStream.h"
+
+
+@interface XMPPPlainAuthentication : NSObject <XMPPSASLAuthentication>
+
+// This class implements the XMPPSASLAuthentication protocol.
+//
+// See XMPPSASLAuthentication.h for more information.
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPStream (XMPPPlainAuthentication)
+
+- (BOOL)supportsPlainAuthentication;
+
+@end
View
103 Authentication/Plain/XMPPPlainAuthentication.m
@@ -0,0 +1,103 @@
+#import "XMPPPlainAuthentication.h"
+#import "XMPP.h"
+#import "XMPPLogging.h"
+#import "XMPPInternal.h"
+#import "NSData+XMPP.h"
+#import "NSXMLElement+XMPP.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+
+@implementation XMPPPlainAuthentication
+{
+ #if __has_feature(objc_arc_weak)
+ __weak XMPPStream *xmppStream;
+ #else
+ __unsafe_unretained XMPPStream *xmppStream;
+ #endif
+
+ NSString *password;
+}
+
++ (NSString *)mechanismName
+{
+ return @"PLAIN";
+}
+
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ password = inPassword;
+ }
+ return self;
+}
+
+- (BOOL)start:(NSError **)errPtr
+{
+ XMPPLogTrace();
+
+ // From RFC 4616 - PLAIN SASL Mechanism:
+ // [authzid] UTF8NUL authcid UTF8NUL passwd
+ //
+ // authzid: authorization identity
+ // authcid: authentication identity (username)
+ // passwd : password for authcid
+
+ NSString *username = [xmppStream.myJID user];
+
+ NSString *payload = [NSString stringWithFormat:@"%C%@%C%@", 0, username, 0, password];
+ NSString *base64 = [[payload dataUsingEncoding:NSUTF8StringEncoding] base64Encoded];
+
+ // <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">Base-64-Info</auth>
+
+ NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
+ [auth addAttributeWithName:@"mechanism" stringValue:@"PLAIN"];
+ [auth setStringValue:base64];
+
+ [xmppStream sendAuthElement:auth];
+
+ return YES;
+}
+
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
+{
+ XMPPLogTrace();
+
+ // We're expecting a success response.
+ // If we get anything else we can safely assume it's the equivalent of a failure response.
+
+ if ([[authResponse name] isEqualToString:@"success"])
+ {
+ return XMPP_AUTH_SUCCESS;
+ }
+ else
+ {
+ return XMPP_AUTH_FAIL;
+ }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPStream (XMPPPlainAuthentication)
+
+- (BOOL)supportsPlainAuthentication
+{
+ return [self supportsAuthenticationMechanism:[XMPPPlainAuthentication mechanismName]];
+}
+
+@end
View
56 Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.h
@@ -0,0 +1,56 @@
+#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
+#import "XMPPStream.h"
+
+
+@interface XMPPXFacebookPlatformAuthentication : NSObject <XMPPSASLAuthentication>
+
+/**
+ * You should use this init method (as opposed the one defined in the XMPPSASLAuthentication protocol).
+**/
+- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)appId accessToken:(NSString *)accessToken;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPStream (XMPPXFacebookPlatformAuthentication)
+
+/**
+ * Facebook Chat X-FACEBOOK-PLATFORM SASL authentication initialization.
+ * This is a convienence init method to help configure Facebook Chat.
+**/
+- (id)initWithFacebookAppId:(NSString *)fbAppId;
+
+/**
+ * The appId can be passed to custom authentication classes.
+ * For example, the appId is used for Facebook Chat X-FACEBOOK-PLATFORM SASL authentication.
+**/
+@property (readwrite, copy) NSString *facebookAppId;
+
+/**
+ * Returns whether or not the server supports X-FACEBOOK-PLATFORM authentication.
+ *
+ * This information is available after the stream is connected.
+ * In other words, after the delegate has received xmppStreamDidConnect: notification.
+**/
+- (BOOL)supportsXFacebookPlatformAuthentication;
+
+/**
+ * This method attempts to start the facebook oauth authentication process.
+ *
+ * This method is asynchronous.
+ *
+ * If there is something immediately wrong,
+ * such as the stream is not connected or doesn't have a set appId or accessToken,
+ * the method will return NO and set the error.
+ * Otherwise the delegate callbacks are used to communicate auth success or failure.
+ *
+ * @see xmppStreamDidAuthenticate:
+ * @see xmppStream:didNotAuthenticate:
+ **/
+- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr;
+
+@end
View
319 Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m
@@ -0,0 +1,319 @@
+#import "XMPPXFacebookPlatformAuthentication.h"
+#import "XMPP.h"
+#import "XMPPLogging.h"
+#import "XMPPInternal.h"
+#import "NSData+XMPP.h"
+
+#import <objc/runtime.h>
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+static NSString *const XMPPFacebookChatHostName = @"chat.facebook.com";
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPXFacebookPlatformAuthentication
+{
+ #if __has_feature(objc_arc_weak)
+ __weak XMPPStream *xmppStream;
+ #else
+ __unsafe_unretained XMPPStream *xmppStream;
+ #endif
+
+ BOOL awaitingChallenge;
+
+ NSString *appId;
+ NSString *accessToken;
+ NSString *nonce;
+ NSString *method;
+}
+
++ (NSString *)mechanismName
+{
+ return @"X-FACEBOOK-PLATFORM";
+}
+
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ }
+ return self;
+}
+
+- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)inAppId accessToken:(NSString *)inAccessToken
+{
+ if ((self = [super init]))
+ {
+ xmppStream = stream;
+ appId = inAppId;
+ accessToken = inAccessToken;
+ }
+ return self;
+}
+
+- (BOOL)start:(NSError **)errPtr
+{
+ if (!appId || !accessToken)
+ {
+ NSString *errMsg = @"Missing facebook appId and/or accessToken.";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
+
+ if (errPtr) *errPtr = err;
+ return NO;
+ }
+
+ // <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="X-FACEBOOK-PLATFORM" />
+
+ NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
+ [auth addAttributeWithName:@"mechanism" stringValue:@"X-FACEBOOK-PLATFORM"];
+
+ [xmppStream sendAuthElement:auth];
+ awaitingChallenge = YES;
+
+ return YES;
+}
+
+- (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
+{
+ XMPPLogTrace();
+
+ // We're expecting a challenge response.
+ // If we get anything else we're going to assume it's some kind of failure response.
+
+ if (![[authResponse name] isEqualToString:@"challenge"])
+ {
+ return XMPP_AUTH_FAIL;
+ }
+
+ // Extract components from incoming challenge
+
+ NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
+
+ nonce = [auth objectForKey:@"nonce"];
+ method = [auth objectForKey:@"method"];
+
+ // Create and send challenge response element
+
+ NSXMLElement *response = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
+ [response setStringValue:[self base64EncodedFullResponse]];
+
+ [xmppStream sendAuthElement:response];
+ awaitingChallenge = NO;
+
+ return XMPP_AUTH_CONTINUE;
+}
+
+- (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse
+{
+ XMPPLogTrace();
+
+ // We're expecting a success response.
+ // If we get anything else we can safely assume it's the equivalent of a failure response.
+
+ if ([[authResponse name] isEqualToString:@"success"])
+ {
+ return XMPP_AUTH_SUCCESS;
+ }
+ else
+ {
+ return XMPP_AUTH_FAIL;
+ }
+}
+
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
+{
+ if (awaitingChallenge)
+ {
+ return [self handleAuth1:authResponse];
+ }
+ else
+ {
+ return [self handleAuth2:authResponse];
+ }
+}
+
+- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge
+{
+ // The value of the challenge stanza is base 64 encoded.
+ // Once "decoded", it's just a string of key=value pairs separated by ampersands.
+
+ NSData *base64Data = [[challenge stringValue] dataUsingEncoding:NSASCIIStringEncoding];
+ NSData *decodedData = [base64Data base64Decoded];
+
+ NSString *authStr = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
+
+ XMPPLogVerbose(@"%@: decoded challenge: %@", THIS_FILE, authStr);
+
+ NSArray *components = [authStr componentsSeparatedByString:@"&"];
+ NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:3];
+
+ for (NSString *component in components)
+ {
+ NSRange separator = [component rangeOfString:@"="];
+ if (separator.location != NSNotFound)
+ {
+ NSString *key = [[component substringToIndex:separator.location]
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+
+ NSString *value = [[component substringFromIndex:separator.location+1]
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+
+ if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""] && [value length] > 2)
+ {
+ // Strip quotes from value
+ value = [value substringWithRange:NSMakeRange(1,([value length]-2))];
+ }
+
+ [auth setObject:value forKey:key];
+ }
+ }
+
+ return auth;
+}
+
+- (NSString *)base64EncodedFullResponse
+{
+ if (!appId || !accessToken || !method || !nonce)
+ {
+ return nil;
+ }
+
+ srand([[NSDate date] timeIntervalSince1970]);
+
+ NSMutableString *buffer = [NSMutableString stringWithCapacity:250];
+ [buffer appendFormat:@"method=%@&", method];
+ [buffer appendFormat:@"nonce=%@&", nonce];
+ [buffer appendFormat:@"access_token=%@&", accessToken];
+ [buffer appendFormat:@"api_key=%@&", appId];
+ [buffer appendFormat:@"call_id=%d&", rand()];
+ [buffer appendFormat:@"v=%@",@"1.0"];
+
+ XMPPLogVerbose(@"XMPPXFacebookPlatformAuthentication: response for facebook: %@", buffer);
+
+ NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding];
+
+ return [utf8data base64Encoded];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPStream (XMPPXFacebookPlatformAuthentication)
+
+- (id)initWithFacebookAppId:(NSString *)fbAppId
+{
+ if ((self = [self init])) // Note: Using [self init], NOT [super init]
+ {
+ self.facebookAppId = fbAppId;
+ myJID_setByClient = [XMPPJID jidWithString:XMPPFacebookChatHostName];
+
+ // As of October 8, 2011, Facebook doesn't have their XMPP SRV records set.
+ // And, as per the XMPP specification, we MUST check the XMPP SRV records for an IP address,
+ // before falling back to a traditional A record lookup.
+ //
+ // So we're setting the hostname as a minor optimization to avoid the SRV timeout delay.
+
+ hostName = XMPPFacebookChatHostName;
+ }
+ return self;
+}
+
+- (NSString *)facebookAppId
+{
+ __block NSString *result = nil;
+
+ dispatch_block_t block = ^{
+ result = objc_getAssociatedObject(self, "facebookAppId");
+ };
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ return result;
+}
+
+- (void)setFacebookAppId:(NSString *)inFacebookAppId
+{
+ NSString *newFacebookAppId = [inFacebookAppId copy];
+
+ dispatch_block_t block = ^{
+ objc_setAssociatedObject(self, @"facebookAppId", newFacebookAppId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ };
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_async(xmppQueue, block);
+}
+
+- (BOOL)supportsXFacebookPlatformAuthentication
+{
+ return [self supportsAuthenticationMechanism:[XMPPXFacebookPlatformAuthentication mechanismName]];
+}
+
+/**
+ * This method attempts to connect to the Facebook Chat servers
+ * using the Facebook OAuth token returned by the Facebook OAuth 2.0 authentication process.
+**/
+- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr
+{
+ XMPPLogTrace();
+
+ __block BOOL result = YES;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if ([self supportsXFacebookPlatformAuthentication])
+ {
+ XMPPXFacebookPlatformAuthentication *facebookAuth =
+ [[XMPPXFacebookPlatformAuthentication alloc] initWithStream:self
+ appId:nil
+ accessToken:accessToken];
+
+ result = [self authenticate:facebookAuth error:&err];
+ }
+ else
+ {
+ NSString *errMsg = @"The server does not support X-FACEBOOK-PLATFORM authentication.";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];
+
+ result = NO;
+ }
+ }};
+
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+@end
View
96 Authentication/XMPPSASLAuthentication.h
@@ -0,0 +1,96 @@
+#import <Foundation/Foundation.h>
+#if TARGET_OS_IPHONE
+ #import "DDXML.h"
+#endif
+
+@class XMPPStream;
+
+
+enum XMPPHandleAuthResponse
+{
+ XMPP_AUTH_FAIL, // Authentication failed.
+ // The delegate will be informed via xmppStream:didNotAuthenticate:
+
+ XMPP_AUTH_SUCCESS, // Authentication succeeded.
+ // The delegate will be informed via xmppStreamDidAuthenticate:
+
+ XMPP_AUTH_CONTINUE, // The authentication process is still ongoing.
+
+};
+typedef enum XMPPHandleAuthResponse XMPPHandleAuthResponse;
+
+
+@protocol XMPPSASLAuthentication <NSObject>
+@required
+
+/**
+ * Returns the associated mechanism name.
+ *
+ * An xmpp server sends a list of supported authentication mechanisms during the xmpp handshake.
+ * The list looks something like this:
+ *
+ * <stream:features>
+ * <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
+ * <mechanism>DIGEST-MD5</mechanism>
+ * <mechanism>X-FACEBOOK-PLATFORM</mechanism>
+ * <mechanism>X-YOUR-CUSTOM-AUTH-SCHEME</mechanism>
+ * </mechanisms>
+ * </stream:features>
+ *
+ * The mechanismName returned should match the value inside the <mechanism>HERE</mechanism>.
+**/
++ (NSString *)mechanismName;
+
+/**
+ * Standard init method.
+ *
+ * The XMPPStream class natively supports the standard authentication scheme (auth with password).
+ * If that method is used, then xmppStream will automatically create an authentication instance via this method.
+ * Which authentication class it chooses is based on the configured authentication priorities,
+ * and the auth mechanisms supported by the server.
+ *
+ * Not all authentication mechanisms will use this init method.
+ * For example:
+ * - they require an appId and authToken
+ * - they require a userName (not related to JID), privilegeLevel, and password
+ * - they require an eyeScan and voiceFingerprint
+ *
+ * In this case, the authentication mechanism class should provide it's own custom init method.
+ * However it should still implement this method, and then use the start method to notify of errors.
+**/
+- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password;
+
+/**
+ * Attempts to start the authentication process.
+ * The auth mechanism should send whatever stanzas are needed to begin the authentication process.
+ *
+ * If it isn't possible to start the authentication process (perhaps due to missing information),
+ * this method should return NO and set an appropriate error message.
+ * For example: "X-Custom-Platform authentication requires authToken"
+ * Otherwise this method should return YES.
+ *
+ * This method is called by automatically XMPPStream (via the authenticate: method).
+ * You should NOT invoke this method manually.
+**/
+- (BOOL)start:(NSError **)errPtr;
+
+/**
+ * After the authentication process has started, all incoming xmpp stanzas are routed to this method.
+ * The authentication mechanism should process the stanza as appropriate, and return the coresponding result.
+ * If the authentication is not yet complete, it should return XMPP_AUTH_CONTINUE,
+ * meaning the xmpp stream will continue to forward all incoming xmpp stanzas to this method.
+ *
+ * This method is called by automatically XMPPStream (via the authenticate: method).
+ * You should NOT invoke this method manually.
+**/
+- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)auth;
+
+@optional
+
+/**
+ * Optionally implement this method to override the default behavior.
+ * The default value is YES.
+**/
+- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication;
+
+@end
View
20 Core/XMPP.h
@@ -1,3 +1,7 @@
+//
+// Core classes
+//
+
#import "XMPPJID.h"
#import "XMPPStream.h"
#import "XMPPElement.h"
@@ -6,4 +10,20 @@
#import "XMPPPresence.h"
#import "XMPPModule.h"
+//
+// Authentication
+//
+
+#import "XMPPSASLAuthentication.h"
+#import "XMPPDigestMD5Authentication.h"
+#import "XMPPPlainAuthentication.h"
+#import "XMPPXFacebookPlatformAuthentication.h"
+#import "XMPPAnonymousAuthentication.h"
+#import "XMPPDeprecatedPlainAuthentication.h"
+#import "XMPPDeprecatedDigestAuthentication.h"
+
+//
+// Categories
+//
+
#import "NSXMLElement+XMPP.h"
View
49 Core/XMPPInternal.h
@@ -1,8 +1,9 @@
//
-// The following is for XMPPStream,
-// and any classes that extend XMPPStream such as XMPPFacebookStream.
+// This file is for XMPPStream and various internal components.
//
+#import "XMPPSASLAuthentication.h"
+
// Define the various timeouts (in seconds) for retreiving various parts of the XML stream
#define TIMEOUT_XMPP_WRITE -1
#define TIMEOUT_XMPP_READ_START 10
@@ -16,7 +17,8 @@
#define TAG_XMPP_WRITE_RECEIPT 202
// Define the various states we'll use to track our progress
-enum {
+enum XMPPStreamState
+{
STATE_XMPP_DISCONNECTED,
STATE_XMPP_RESOLVING_SRV,
STATE_XMPP_CONNECTING,
@@ -26,24 +28,35 @@ enum {
STATE_XMPP_STARTTLS_2,
STATE_XMPP_POST_NEGOTIATION,
STATE_XMPP_REGISTERING,
- STATE_XMPP_AUTH_1,
- STATE_XMPP_AUTH_2,
- STATE_XMPP_AUTH_3,
+ STATE_XMPP_AUTH,
STATE_XMPP_BINDING,
STATE_XMPP_START_SESSION,
STATE_XMPP_CONNECTED,
};
+typedef enum XMPPStreamState XMPPStreamState;
-//
-// It is recommended that storage classes cache a stream's myJID.
-// This prevents them from constantly querying the property from the xmppStream instance,
-// as doing so goes through xmppStream's dispatch queue.
-// Caching the stream's myJID frees the dispatch queue to handle xmpp processing tasks.
-//
-// The object of the notification will be the XMPPStream instance.
-//
-// Note: We're not using the typical MulticastDelegate paradigm for this task as
-// storage classes are not typically added as a delegate of the xmppStream.
-//
-
+/**
+ * It is recommended that storage classes cache a stream's myJID.
+ * This prevents them from constantly querying the property from the xmppStream instance,
+ * as doing so goes through xmppStream's dispatch queue.
+ * Caching the stream's myJID frees the dispatch queue to handle xmpp processing tasks.
+ *
+ * The object of the notification will be the XMPPStream instance.
+ *
+ * Note: We're not using the typical MulticastDelegate paradigm for this task as
+ * storage classes are not typically added as a delegate of the xmppStream.
+**/
extern NSString *const XMPPStreamDidChangeMyJIDNotification;
+
+@interface XMPPStream (Internal)
+
+@property (readonly) XMPPStreamState state;
+
+/**
+ * This method is for use by xmpp authentication mechanism classes.
+ * They should send elements using this method instead of the public sendElement classes,
+ * as those methods don't send the elements while authentication is in progress.
+**/
+- (void)sendAuthElement:(NSXMLElement *)element;
+
+@end
View
6 Core/XMPPLogging.h
@@ -101,7 +101,11 @@
// We follow the default configuration,
// but we reserve a special macro to easily disable asynchronous logging for debugging purposes.
-#define XMPP_LOG_ASYNC_ENABLED YES
+#if DEBUG
+#define XMPP_LOG_ASYNC_ENABLED NO
+#else
+#define XMPP_LOG_ASYNC_ENABLED YES
+#endif
#define XMPP_LOG_ASYNC_ERROR ( NO && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_WARN (YES && XMPP_LOG_ASYNC_ENABLED)
View
101 Core/XMPPStream.h
@@ -1,6 +1,8 @@
#import <Foundation/Foundation.h>
+#import "XMPPSASLAuthentication.h"
#import "GCDAsyncSocket.h"
#import "GCDMulticastDelegate.h"
+
#if TARGET_OS_IPHONE
#import "DDXML.h"
#endif
@@ -61,10 +63,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
NSString *hostName;
UInt16 hostPort;
- NSString *tempPassword;
- BOOL isAccessToken;
-
- NSString *appId;
+ id <XMPPSASLAuthentication> auth;
XMPPJID *myJID_setByClient;
XMPPJID *myJID_setByServer;
@@ -108,12 +107,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
- (id)initP2PFrom:(XMPPJID *)myJID;
/**
- * Facebook Chat X-FACEBOOK-PLATFORM SASL authentication initialization.
- * This is a convienence init method to help configure Facebook Chat.
- **/
-- (id)initWithFacebookAppId:(NSString *)fbAppId;
-
-/**
* XMPPStream uses a multicast delegate.
* This allows one to add multiple delegates to a single XMPPStream instance,
* which makes it easier to separate various components and extensions.
@@ -130,12 +123,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
- * The appId can be passed to custom authentication classes.
- * For example, the appId is used for Facebook Chat X-FACEBOOK-PLATFORM SASL authentication.
-**/
-@property (readwrite,copy) NSString *appId;
-
-/**
* The server's hostname that should be used to make the TCP connection.
* This may be a domain name (e.g. "deusty.com") or an IP address (e.g. "70.85.193.226").
*
@@ -421,36 +408,76 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
- * Authentication.
+ * Returns the server's list of supported authentication mechanisms.
+ * Each item in the array will be of type NSString.
+ *
+ * For example, if the server supplied this stanza within it's reported stream:features:
+ *
+ * <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
+ * <mechanism>DIGEST-MD5</mechanism>
+ * <mechanism>PLAIN</mechanism>
+ * </mechanisms>
+ *
+ * Then this method would return [@"DIGEST-MD5", @"PLAIN"].
+**/
+- (NSArray *)supportedAuthenticationMechanisms;
+
+/**
+ * Returns whether or not the given authentication mechanism name was specified in the
+ * server's list of supported authentication mechanisms.
+ *
+ * Note: The authentication classes often provide a category on XMPPStream, adding useful methods.
+ *
+ * @see XMPPPlainAuthentication - supportsPlainAuthentication
+ * @see XMPPDigestMD5Authentication - supportsDigestMD5Authentication
+ * @see XMPPXFacebookPlatformAuthentication - supportsXFacebookPlatformAuthentication
+ * @see XMPPDeprecatedPlainAuthentication - supportsDeprecatedPlainAuthentication
+ * @see XMPPDeprecatedDigestAuthentication - supportsDeprecatedDigestAuthentication
+**/
+- (BOOL)supportsAuthenticationMechanism:(NSString *)mechanism;
+
+/**
+ * This is the root authentication method.
+ * All other authentication methods go through this one.
*
- * The authenticateWithPassword:error: and authenticateWithFacebookAccessToken:error: methods are asynchronous.
- * Each will return immediately, and the delegate methods are used to determine success.
- * See the xmppStreamDidAuthenticate: and xmppStream:didNotAuthenticate: methods.
+ * This method attempts to start the authentication process given the auth instance.
+ * That is, this method will invoke start: on the given auth instance.
+ * If it returns YES, then the stream will enter into authentication mode.
+ * It will then continually invoke the handleAuth: method on the given instance until authentication is complete.
+ *
+ * This method is asynchronous.
*
* If there is something immediately wrong, such as the stream is not connected,
* the method will return NO and set the error.
+ * Otherwise the delegate callbacks are used to communicate auth success or failure.
*
- * The errPtr parameter is optional - you may pass nil.
+ * @see xmppStreamDidAuthenticate:
+ * @see xmppStream:didNotAuthenticate:
*
- * The authenticateWithPassword:error: method will choose the most secure protocol to send the password.
+ * @see authenticateWithPassword:error:
*
- * Security Note:
- * Care should be taken if sending passwords in the clear is not acceptable.
- * You may use the supportsXAuthentication methods below to determine
- * if an acceptable authentication protocol is supported.
+ * Note: The security process is abstracted in order to provide flexibility,
+ * and allow developers to easily implement their own custom authentication protocols.
+ * The authentication classes often provide a category on XMPPStream, adding useful methods.
+ *
+ * @see XMPPXFacebookPlatformAuthentication - authenticateWithFacebookAccessToken:error:
+**/
+- (BOOL)authenticate:(id <XMPPSASLAuthentication>)auth error:(NSError **)errPtr;
+
+/**
+ * This method applies to standard password authentication schemes only.
+ * This is NOT the primary authentication method.
+ *
+ * @see authenticate:error:
+ *
+ * This method exists for backwards compatibility, and may disappear in future versions.
**/
-- (BOOL)isAuthenticated;
-- (BOOL)supportsAnonymousAuthentication;
-- (BOOL)supportsPlainAuthentication;
-- (BOOL)supportsDigestMD5Authentication;
-- (BOOL)supportsXFacebookPlatformAuthentication;
-- (BOOL)supportsDeprecatedPlainAuthentication;
-- (BOOL)supportsDeprecatedDigestAuthentication;
-- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr;
- (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr;
-- (BOOL)authenticateAnonymously:(NSError **)errPtr;
-- (void)handleAuth1:(NSXMLElement *)response;
+/**
+ * Returns whether or not the xmpp stream has successfully authenticated with the server.
+**/
+- (BOOL)isAuthenticated;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Server Info
@@ -463,7 +490,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
* If multiple <stream:features/> have been received during the course of stream negotiation,
* the root element contains only the most recent (current) version.
*
- * Note: The rootElement is "empty", in so much as it does not contain all the XML elements the stream has
+ * Note: The rootElement is "empty", in-so-far as it does not contain all the XML elements the stream has
* received during it's connection. This is done for performance reasons and for the obvious benefit
* of being more memory efficient.
**/
View
843 Core/XMPPStream.m
@@ -1,17 +1,9 @@
-#import "XMPPStream.h"
-#import "XMPPInternal.h"
+#import "XMPP.h"
#import "XMPPParser.h"
-#import "XMPPJID.h"
-#import "XMPPIQ.h"
-#import "XMPPMessage.h"
-#import "XMPPPresence.h"
-#import "XMPPModule.h"
#import "XMPPLogging.h"
-#import "NSData+XMPP.h"
-#import "NSXMLElement+XMPP.h"
-#import "XMPPDigestAuthentication.h"
-#import "XMPPXFacebookPlatformAuthentication.h"
+#import "XMPPInternal.h"
#import "XMPPSRVResolver.h"
+#import "NSData+XMPP.h"
#import "DDList.h"
#import <libkern/OSAtomic.h>
@@ -25,6 +17,13 @@
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
+// Log levels: off, error, warn, info, verbose
+#if DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_SEND_RECV; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
#if TARGET_OS_IPHONE
#define SOCKET_BUFFER_SIZE 512 // bytes
#else
@@ -38,19 +37,10 @@
**/
#define return_from_block return
-// Log levels: off, error, warn, info, verbose
-#if DEBUG
- static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_SEND_RECV; // | XMPP_LOG_FLAG_TRACE;
-#else
- static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
-#endif
-
NSString *const XMPPStreamErrorDomain = @"XMPPStreamErrorDomain";
NSString *const XMPPStreamDidChangeMyJIDNotification = @"XMPPStreamDidChangeMyJID";
-static NSString *const XMPPFacebookChatHostName = @"chat.facebook.com";
-
enum XMPPStreamFlags
{
@@ -177,29 +167,6 @@ - (id)initP2PFrom:(XMPPJID *)jid
return self;
}
-
-/**
- * Facebook Chat X-FACEBOOK-PLATFORM SASL authentication initialization.
- * This is a convienence init method to help configure Facebook Chat.
-**/
-- (id)initWithFacebookAppId:(NSString *)fbAppId
-{
- if ((self = [self init]))
- {
- appId = fbAppId;
- myJID_setByClient = [XMPPJID jidWithString:XMPPFacebookChatHostName];
-
- // As of October 8, 2011, Facebook doesn't have their XMPP SRV records set.
- // And, as per the XMPP specification, we MUST check the XMPP SRV records for an IP address,
- // before falling back to a traditional A record lookup.
- //
- // So we're setting the hostname as a minor optimization to avoid the SRV timeout delay.
-
- hostName = XMPPFacebookChatHostName;
- }
- return self;
-}
-
/**
* Standard deallocation method.
* Every object variable declared in the header file should be released here.
@@ -234,42 +201,20 @@ - (void)dealloc
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-- (NSString *)appId
-{
- if (dispatch_get_current_queue() == xmppQueue)
- {
- return appId;
- }
- else
- {
- __block NSString *result;
-
- dispatch_sync(xmppQueue, ^{
- result = appId;
- });
-
- return result;
- }
-}
-
-- (void)setAppId:(NSString *)newAppId
+- (XMPPStreamState)state
{
+ __block XMPPStreamState result = STATE_XMPP_DISCONNECTED;
+
+ dispatch_block_t block = ^{
+ result = (XMPPStreamState)state;
+ };
+
if (dispatch_get_current_queue() == xmppQueue)
- {
- if (appId != newAppId)
- {
- appId = [newAppId copy];
- }
- }
+ block();
else
- {
- NSString *newAppIdCopy = [newAppId copy];
-
- dispatch_async(xmppQueue, ^{
- appId = newAppIdCopy;
- });
-
- }
+ dispatch_sync(xmppQueue, block);
+
+ return result;
}
- (NSString *)hostName
@@ -1426,56 +1371,15 @@ - (BOOL)registerWithPassword:(NSString *)password error:(NSError **)errPtr
#pragma mark Authentication
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This method checks the stream features of the connected server to determine if SASL Anonymous Authentication (XEP-0175)
- * is supported. If we are not connected to a server, this method simply returns NO.
- **/
-- (BOOL)supportsAnonymousAuthentication
+- (NSArray *)supportedAuthenticationMechanisms
{
- __block BOOL result = NO;
+ __block NSMutableArray *result = [[NSMutableArray alloc] init];
dispatch_block_t block = ^{ @autoreleasepool {
// The root element can be properly queried for authentication mechanisms anytime after the
- // stream:features are received, and TLS has been setup (if required)
- if (state >= STATE_XMPP_POST_NEGOTIATION)
- {
- NSXMLElement *features = [rootElement elementForName:@"stream:features"];
- NSXMLElement *mech = [features elementForName:@"mechanisms" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
-
- NSArray *mechanisms = [mech elementsForName:@"mechanism"];
-
- for (NSXMLElement *mechanism in mechanisms)
- {
- if ([[mechanism stringValue] isEqualToString:@"ANONYMOUS"])
- {
- result = YES;
- break;
- }
- }
- }
- }};
-
- if (dispatch_get_current_queue() == xmppQueue)
- block();
- else
- dispatch_sync(xmppQueue, block);
-
- return result;
-}
-
-/**
- * This method checks the stream features of the connected server to determine if plain authentication is supported.
- * If we are not connected to a server, this method simply returns NO.
-**/
-- (BOOL)supportsPlainAuthentication
-{
- __block BOOL result = NO;
-
- dispatch_block_t block = ^{ @autoreleasepool {
+ // stream:features are received, and TLS has been setup (if required).
- // The root element can be properly queried for authentication mechanisms anytime after the
- //stream:features are received, and TLS has been setup (if required)
if (state >= STATE_XMPP_POST_NEGOTIATION)
{
NSXMLElement *features = [rootElement elementForName:@"stream:features"];
@@ -1485,11 +1389,7 @@ - (BOOL)supportsPlainAuthentication
for (NSXMLElement *mechanism in mechanisms)
{
- if ([[mechanism stringValue] isEqualToString:@"PLAIN"])
- {
- result = YES;
- break;
- }
+ [result addObject:[mechanism stringValue]];
}
}
}};
@@ -1503,58 +1403,20 @@ - (BOOL)supportsPlainAuthentication
}
/**
- * This method checks the stream features of the connected server to determine if digest authentication is supported.
- * If we are not connected to a server, this method simply returns NO.
+ * This method checks the stream features of the connected server to determine
+ * if the given authentication mechanism is supported.
*
- * This is the preferred authentication technique, and will be used if the server supports it.
+ * If we are not connected to a server, this method simply returns NO.
**/
-- (BOOL)supportsDigestMD5Authentication
+- (BOOL)supportsAuthenticationMechanism:(NSString *)mechanismType
{
__block BOOL result = NO;
dispatch_block_t block = ^{ @autoreleasepool {
// The root element can be properly queried for authentication mechanisms anytime after the
- // stream:features are received, and TLS has been setup (if required)
- if (state >= STATE_XMPP_POST_NEGOTIATION)
- {
- NSXMLElement *features = [rootElement elementForName:@"stream:features"];
- NSXMLElement *mech = [features elementForName:@"mechanisms" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
-
- NSArray *mechanisms = [mech elementsForName:@"mechanism"];
-
- for (NSXMLElement *mechanism in mechanisms)
- {
- if ([[mechanism stringValue] isEqualToString:@"DIGEST-MD5"])
- {
- result = YES;
- break;
- }
- }
- }
- }};
-
- if (dispatch_get_current_queue() == xmppQueue)
- block();
- else
- dispatch_sync(xmppQueue, block);
-
- return result;
-}
-
-/**
- * This method checks the stream features of the connected server to
- * determine if X-FACEBOOK-PLATFORM authentication is supported.
- * If we are not connected to a server, this method simply returns NO.
- **/
-- (BOOL)supportsXFacebookPlatformAuthentication
-{
- __block BOOL result = NO;
-
- dispatch_block_t block = ^{ @autoreleasepool {
+ // stream:features are received, and TLS has been setup (if required).
- // The root element can be properly queried for authentication mechanisms anytime after the
- // stream:features are received, and TLS has been setup (if required)
if (state >= STATE_XMPP_POST_NEGOTIATION)
{
NSXMLElement *features = [rootElement elementForName:@"stream:features"];
@@ -1564,7 +1426,7 @@ - (BOOL)supportsXFacebookPlatformAuthentication
for (NSXMLElement *mechanism in mechanisms)
{
- if ([[mechanism stringValue] isEqualToString:@"X-FACEBOOK-PLATFORM"])
+ if ([[mechanism stringValue] isEqualToString:mechanismType])
{
result = YES;
break;
@@ -1581,162 +1443,55 @@ - (BOOL)supportsXFacebookPlatformAuthentication
return result;
}
-/**
- * This method only applies to servers that don't support XMPP version 1.0, as defined in RFC 3920.
- * With these servers, we attempt to discover supported authentication modes via the jabber:iq:auth namespace.
-**/
-- (BOOL)supportsDeprecatedPlainAuthentication
+- (BOOL)authenticate:(id <XMPPSASLAuthentication>)inAuth error:(NSError **)errPtr
{
- __block BOOL result = NO;
-
- dispatch_block_t block = ^{ @autoreleasepool {
-
- // The root element can be properly queried for authentication mechanisms anytime after the
- // stream:features are received, and TLS has been setup (if required)
- if (state >= STATE_XMPP_POST_NEGOTIATION)
- {
- // Search for an iq element within the rootElement.
- // Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
- // if we simply used the elementForName method.
-
- NSXMLElement *iq = nil;
-
- NSUInteger i, count = [rootElement childCount];
- for (i = 0; i < count; i++)
- {
- NSXMLNode *childNode = [rootElement childAtIndex:i];
-
- if ([childNode kind] == NSXMLElementKind)
- {
- if ([[childNode name] isEqualToString:@"iq"])
- {
- iq = (NSXMLElement *)childNode;
- }
- }
- }
-
- NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:auth"];
- NSXMLElement *plain = [query elementForName:@"password"];
-
- result = (plain != nil);
- }
- }};
-
- if (dispatch_get_current_queue() == xmppQueue)
- block();
- else
- dispatch_sync(xmppQueue, block);
-
- return result;
-}
-
-- (void)sendSASLRequestForMechanism:(NSString *)mechanism
-{
- NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
-
XMPPLogTrace();
- NSString *authFrmt = @"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='%@'/>";
- NSString *auth = [NSString stringWithFormat:authFrmt, mechanism];
-
- NSData *outgoingData = [auth dataUsingEncoding:NSUTF8StringEncoding];
-
- XMPPLogSend(@"SEND: %@", auth);
- numberOfBytesSent += [outgoingData length];
-
- [asyncSocket writeData:outgoingData
- withTimeout:TIMEOUT_XMPP_WRITE
- tag:TAG_XMPP_WRITE_STREAM];
-}
-
-/**
- * This method only applies to servers that don't support XMPP version 1.0, as defined in RFC 3920.
- * With these servers, we attempt to discover supported authentication modes via the jabber:iq:auth namespace.
-**/
-- (BOOL)supportsDeprecatedDigestAuthentication
-{
__block BOOL result = NO;
+ __block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
- // The root element can be properly queried for authentication mechanisms anytime after the
- // stream:features are received, and TLS has been setup (if required)
- if (state >= STATE_XMPP_POST_NEGOTIATION)
+ if (state != STATE_XMPP_CONNECTED)
{
- // Search for an iq element within the rootElement.
- // Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
- // if we simply used the elementForName method.
-
- NSXMLElement *iq = nil;
-
- NSUInteger i, count = [rootElement childCount];
- for (i = 0; i < count; i++)
- {
- NSXMLNode *childNode = [rootElement childAtIndex:i];
-
- if ([childNode kind] == NSXMLElementKind)
- {
- if ([[childNode name] isEqualToString:@"iq"])
- {
- iq = (NSXMLElement *)childNode;
- }
- }
- }
+ NSString *errMsg = @"Please wait until the stream is connected.";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
- NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:auth"];
- NSXMLElement *digest = [query elementForName:@"digest"];
+ err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
- result = (digest != nil);
+ result = NO;
+ return_from_block;
}
- }};
-
- if (dispatch_get_current_queue() == xmppQueue)
- block();
- else
- dispatch_sync(xmppQueue, block);
-
- return result;
-}
-
-/**
- * This method attempts to connect to the Facebook Chat servers
- * using the Facebook OAuth token returned by the Facebook OAuth 2.0 authentication process.
-**/
-- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr
-{
- XMPPLogTrace();
-
- __block BOOL result = YES;
- __block NSError *err = nil;
-
- dispatch_block_t block = ^{ @autoreleasepool {
-
- if (![self supportsXFacebookPlatformAuthentication])
+
+ if (myJID_setByClient == nil)
{
- NSString *errMsg = @"The server does not support X-FACEBOOK-PLATFORM authentication.";
+ NSString *errMsg = @"You must set myJID before calling authenticate:error:.";
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];
-
+
+ err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidProperty userInfo:info];
+
result = NO;
return_from_block;
}
-
- // Save authentication information
- isAccessToken = YES;
- tempPassword = [accessToken copy];
-
- NSError *authErr = nil;
- result = [self authenticateWithPassword:accessToken error:&authErr];
-
- if (!result)
+
+ // Change state.
+ // We do this now because when we invoke the start method below,
+ // it may in turn invoke our sendAuthElement method, which expects us to be in STATE_XMPP_AUTH.
+ state = STATE_XMPP_AUTH;
+
+ if ([inAuth start:&err])
{
- err = authErr;
+ auth = inAuth;
+ result = YES;
+ }
+ else
+ {
+ // Unable to start authentication for some reason.
+ // Revert back to connected state.
+ state = STATE_XMPP_CONNECTED;
}
-
}};
-
if (dispatch_get_current_queue() == xmppQueue)
block();
else
@@ -1749,9 +1504,12 @@ - (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSErr
}
/**
- * This method attempts to sign-in to the server using the configured myJID and given password.
- * This method also works with Facebook Chat and the Facebook user's password, but authenticating
- * with authenticateWithFacebookAccessToken:error: is preferred by Facebook.
+ * This method applies to standard password authentication schemes only.
+ * This is NOT the primary authentication method.
+ *
+ * @see authenticate:error:
+ *
+ * This method exists for backwards compatibility, and may disappear in future versions.
**/
- (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr
{
@@ -1775,7 +1533,7 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr
if (myJID_setByClient == nil)
{
- NSString *errMsg = @"You must set myJID before calling authenticateWithPassword:error:.";
+ NSString *errMsg = @"You must set myJID before calling authenticate:error:.";
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidProperty userInfo:info];
@@ -1784,176 +1542,44 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr
return_from_block;
}
- // Facebook authentication
- if (isAccessToken)
- {
- [self sendSASLRequestForMechanism:@"X-FACEBOOK-PLATFORM"];
-
- // Update state
- state = STATE_XMPP_AUTH_1;
- }
- else if ([self supportsDigestMD5Authentication])
+ // Choose the best authentication method.
+ //
+ // P.S. - This method is deprecated.
+
+ id <XMPPSASLAuthentication> someAuth = nil;
+
+ if ([self supportsDigestMD5Authentication])
{
- [self sendSASLRequestForMechanism:@"DIGEST-MD5"];
-
- // Save authentication information
- isAccessToken = NO;
- tempPassword = [password copy];
-
- // Update state
- state = STATE_XMPP_AUTH_1;
+ someAuth = [[XMPPDigestMD5Authentication alloc] initWithStream:self password:password];
+ result = [self authenticate:someAuth error:&err];
}
else if ([self supportsPlainAuthentication])
{
- // From RFC 4616 - PLAIN SASL Mechanism:
- // [authzid] UTF8NUL authcid UTF8NUL passwd
- //
- // authzid: authorization identity
- // authcid: authentication identity (username)
- // passwd : password for authcid
-
- NSString *username = [myJID_setByClient user];
-
- NSString *payload = [NSString stringWithFormat:@"%C%@%C%@", 0, username, 0, password];
- NSString *base64 = [[payload dataUsingEncoding:NSUTF8StringEncoding] base64Encoded];
-
- NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
- [auth addAttributeWithName:@"mechanism" stringValue:@"PLAIN"];
- [auth setStringValue:base64];
-
- NSString *outgoingStr = [auth compactXMLString];
- NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
-
- XMPPLogSend(@"SEND: %@", outgoingStr);
- numberOfBytesSent += [outgoingData length];
-
- [asyncSocket writeData:outgoingData
- withTimeout:TIMEOUT_XMPP_WRITE
- tag:TAG_XMPP_WRITE_STREAM];
-
- // Update state
- state = STATE_XMPP_AUTH_1;
+ someAuth = [[XMPPPlainAuthentication alloc] initWithStream:self password:password];
+ result = [self authenticate:someAuth error:&err];
}
- else
+ else if ([self supportsDeprecatedDigestAuthentication])
{
- // The server does not appear to support SASL authentication (at least any type we can use)
- // So we'll revert back to the old fashioned jabber:iq:auth mechanism
-
- NSString *username = [myJID_setByClient user];
- NSString *resource = [myJID_setByClient resource];
-
- if ([resource length] == 0)
- {
- // If resource is nil or empty, we need to auto-create one
-
- resource = [self generateUUID];
- }
-
- NSXMLElement *queryElement = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"];
- [queryElement addChild:[NSXMLElement elementWithName:@"username" stringValue:username]];
- [queryElement addChild:[NSXMLElement elementWithName:@"resource" stringValue:resource]];
-
- if ([self supportsDeprecatedDigestAuthentication])
- {
- NSString *rootID = [[[self rootElement] attributeForName:@"id"] stringValue];
- NSString *digestStr = [NSString stringWithFormat:@"%@%@", rootID, password];
- NSData *digestData = [digestStr dataUsingEncoding:NSUTF8StringEncoding];
-
- NSString *digest = [[digestData sha1Digest] hexStringValue];
-
- [queryElement addChild:[NSXMLElement elementWithName:@"digest" stringValue:digest]];
- }
- else
- {
- [queryElement addChild:[NSXMLElement elementWithName:@"password" stringValue:password]];
- }
-
- NSXMLElement *iqElement = [NSXMLElement elementWithName:@"iq"];
- [iqElement addAttributeWithName:@"type" stringValue:@"set"];
- [iqElement addChild:queryElement];
-
- NSString *outgoingStr = [iqElement compactXMLString];
- NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
-
- XMPPLogSend(@"SEND: %@", outgoingStr);
- numberOfBytesSent += [outgoingData length];
-
- [asyncSocket writeData:outgoingData
- withTimeout:TIMEOUT_XMPP_WRITE
- tag:TAG_XMPP_WRITE_STREAM];
-
- // Update state
- state = STATE_XMPP_AUTH_1;
+ someAuth = [[XMPPDeprecatedDigestAuthentication alloc] initWithStream:self password:password];
+ result = [self authenticate:someAuth error:&err];
}
-
- }};
-
-
- if (dispatch_get_current_queue() == xmppQueue)
- block();
- else
- dispatch_sync(xmppQueue, block);
-
- if (errPtr)
- *errPtr = err;
-
- return result;
-}
-
-/**
- * This method attempts to sign-in to the using SASL Anonymous Authentication (XEP-0175)
-**/
-- (BOOL)authenticateAnonymously:(NSError **)errPtr
-{
- XMPPLogTrace();
-
- __block BOOL result = YES;
- __block NSError *err = nil;
-
- dispatch_block_t block = ^{ @autoreleasepool {
-
- if (state != STATE_XMPP_CONNECTED)
+ else if ([self supportsDeprecatedPlainAuthentication])
{
- NSString *errMsg = @"Please wait until the stream is connected.";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
-
- result = NO;
- return_from_block;
+ someAuth = [[XMPPDeprecatedDigestAuthentication alloc] initWithStream:self password:password];
@creaven
creaven added a note

seems it should be XMPPDeprecatedPlainAuthentication instead of XMPPDeprecatedDigestAuthentication