PageRenderTime 45ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/mordor/http/auth.cpp

http://github.com/mozy/mordor
C++ | 264 lines | 235 code | 14 blank | 15 comment | 71 complexity | 5f2900afa550cec90f30b9dec0fdec37 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. // Copyright (c) 2009 - Mozy, Inc.
  2. #include "auth.h"
  3. #include "basic.h"
  4. #include "client.h"
  5. #include "digest.h"
  6. #include "mordor/socket.h"
  7. #ifdef WINDOWS
  8. #include "negotiate.h"
  9. #elif defined (OSX)
  10. #include "mordor/util.h"
  11. #include <Security/SecItem.h>
  12. #include <Security/SecKeychain.h>
  13. #include <Security/SecKeychainItem.h>
  14. #include <Security/SecKeychainSearch.h>
  15. #endif
  16. namespace Mordor {
  17. namespace HTTP {
  18. static void authorize(const ChallengeList *challenge, AuthParams &authorization,
  19. const URI &uri, const std::string &method, const std::string &scheme,
  20. const std::string &realm, const std::string &username,
  21. const std::string &password)
  22. {
  23. if (stricmp(scheme.c_str(), "Basic") == 0) {
  24. BasicAuth::authorize(authorization, username, password);
  25. } else if (stricmp(scheme.c_str(), "Digest") == 0) {
  26. MORDOR_ASSERT(challenge);
  27. DigestAuth::authorize(
  28. challengeForSchemeAndRealm(*challenge, "Digest", realm),
  29. authorization, uri, method, username, password);
  30. }
  31. }
  32. ClientRequest::ptr
  33. AuthRequestBroker::request(Request &requestHeaders, bool forceNewConnection,
  34. boost::function<void (ClientRequest::ptr)> bodyDg)
  35. {
  36. ClientRequest::ptr priorRequest;
  37. std::string scheme, realm, username, password;
  38. size_t attempts = 0, proxyAttempts = 0;
  39. #ifdef WINDOWS
  40. boost::scoped_ptr<NegotiateAuth> negotiateAuth, negotiateProxyAuth;
  41. #endif
  42. while (true) {
  43. #ifdef WINDOWS
  44. // Reset Negotiate auth sequence if the server didn't continue the
  45. // handshake
  46. if (negotiateProxyAuth) {
  47. const ChallengeList &challenge =
  48. priorRequest->response().response.proxyAuthenticate;
  49. if (!isAcceptable(challenge, scheme) ||
  50. !negotiateProxyAuth->authorize(
  51. challengeForSchemeAndRealm(challenge, scheme),
  52. requestHeaders.request.proxyAuthorization,
  53. requestHeaders.requestLine.uri))
  54. negotiateProxyAuth.reset();
  55. }
  56. if (negotiateAuth) {
  57. const ChallengeList &challenge =
  58. priorRequest->response().response.wwwAuthenticate;
  59. if (!isAcceptable(challenge, scheme) ||
  60. !negotiateAuth->authorize(
  61. challengeForSchemeAndRealm(challenge, scheme),
  62. requestHeaders.request.authorization,
  63. requestHeaders.requestLine.uri))
  64. negotiateAuth.reset();
  65. }
  66. // Negotiate auth is a multi-request transaction; if we're in the
  67. // middle of one, just do the next step, and skip asking for
  68. // credentials again
  69. if (!negotiateAuth && !negotiateProxyAuth) {
  70. #endif
  71. // If this is the first try, or the last one failed UNAUTHORIZED,
  72. // ask for credentials, and use them if we got them
  73. if ((!priorRequest ||
  74. priorRequest->response().status.status == UNAUTHORIZED) &&
  75. m_getCredentialsDg &&
  76. m_getCredentialsDg(requestHeaders.requestLine.uri, priorRequest,
  77. scheme, realm, username, password, attempts++)) {
  78. #ifdef WINDOWS
  79. MORDOR_ASSERT(
  80. stricmp(scheme.c_str(), "Negotiate") == 0 ||
  81. stricmp(scheme.c_str(), "NTLM") == 0 ||
  82. stricmp(scheme.c_str(), "Digest") == 0 ||
  83. stricmp(scheme.c_str(), "Basic") == 0);
  84. #else
  85. MORDOR_ASSERT(
  86. stricmp(scheme.c_str(), "Digest") == 0 ||
  87. stricmp(scheme.c_str(), "Basic") == 0);
  88. #endif
  89. #ifdef WINDOWS
  90. if (scheme == "Negotiate" || scheme == "NTLM") {
  91. negotiateAuth.reset(new NegotiateAuth(username, password));
  92. negotiateAuth->authorize(
  93. challengeForSchemeAndRealm(priorRequest->response().response.wwwAuthenticate, scheme),
  94. requestHeaders.request.authorization,
  95. requestHeaders.requestLine.uri);
  96. } else
  97. #endif
  98. authorize(priorRequest ?
  99. &priorRequest->response().response.wwwAuthenticate : NULL,
  100. requestHeaders.request.authorization,
  101. requestHeaders.requestLine.uri,
  102. requestHeaders.requestLine.method,
  103. scheme, realm, username, password);
  104. } else if (priorRequest &&
  105. priorRequest->response().status.status == UNAUTHORIZED) {
  106. // caller didn't want to retry
  107. return priorRequest;
  108. }
  109. // If this is the first try, or the last one failed (for a proxy)
  110. // ask for credentials, and use them if we got them
  111. if ((!priorRequest ||
  112. priorRequest->response().status.status ==
  113. PROXY_AUTHENTICATION_REQUIRED) &&
  114. m_getProxyCredentialsDg &&
  115. m_getProxyCredentialsDg(requestHeaders.requestLine.uri,
  116. priorRequest, scheme, realm, username, password, proxyAttempts++)) {
  117. #ifdef WINDOWS
  118. MORDOR_ASSERT(
  119. stricmp(scheme.c_str(), "Negotiate") == 0 ||
  120. stricmp(scheme.c_str(), "NTLM") == 0 ||
  121. stricmp(scheme.c_str(), "Digest") == 0 ||
  122. stricmp(scheme.c_str(), "Basic") == 0);
  123. #else
  124. MORDOR_ASSERT(
  125. stricmp(scheme.c_str(), "Digest") == 0 ||
  126. stricmp(scheme.c_str(), "Basic") == 0);
  127. #endif
  128. #ifdef WINDOWS
  129. if (scheme == "Negotiate" || scheme == "NTLM") {
  130. negotiateProxyAuth.reset(new NegotiateAuth(username, password));
  131. negotiateProxyAuth->authorize(
  132. challengeForSchemeAndRealm(priorRequest->response().response.proxyAuthenticate, scheme),
  133. requestHeaders.request.proxyAuthorization,
  134. requestHeaders.requestLine.uri);
  135. } else
  136. #endif
  137. authorize(priorRequest ?
  138. &priorRequest->response().response.proxyAuthenticate : NULL,
  139. requestHeaders.request.proxyAuthorization,
  140. requestHeaders.requestLine.uri,
  141. requestHeaders.requestLine.method,
  142. scheme, realm, username, password);
  143. } else if (priorRequest &&
  144. priorRequest->response().status.status ==
  145. PROXY_AUTHENTICATION_REQUIRED) {
  146. // Caller didn't want to retry
  147. return priorRequest;
  148. }
  149. #ifdef WINDOWS
  150. }
  151. #endif
  152. if (priorRequest) {
  153. priorRequest->finish();
  154. } else {
  155. // We're passed our pre-emptive authentication, regardless of what
  156. // actually happened
  157. attempts = 1;
  158. proxyAttempts = 1;
  159. }
  160. priorRequest = parent()->request(requestHeaders, forceNewConnection,
  161. bodyDg);
  162. const ChallengeList *challengeList = NULL;
  163. if (priorRequest->response().status.status == UNAUTHORIZED)
  164. challengeList = &priorRequest->response().response.wwwAuthenticate;
  165. if (priorRequest->response().status.status == PROXY_AUTHENTICATION_REQUIRED)
  166. challengeList = &priorRequest->response().response.proxyAuthenticate;
  167. if (challengeList &&
  168. (isAcceptable(*challengeList, "Basic") ||
  169. isAcceptable(*challengeList, "Digest")
  170. #ifdef WINDOWS
  171. || isAcceptable(*challengeList, "Negotiate") ||
  172. isAcceptable(*challengeList, "NTLM")
  173. #endif
  174. ))
  175. continue;
  176. return priorRequest;
  177. }
  178. }
  179. #ifdef OSX
  180. bool getCredentialsFromKeychain(const URI &uri, ClientRequest::ptr priorRequest,
  181. std::string &scheme, std::string &realm, std::string &username,
  182. std::string &password, size_t attempts)
  183. {
  184. if (attempts != 1)
  185. return false;
  186. bool proxy =
  187. priorRequest->response().status.status == PROXY_AUTHENTICATION_REQUIRED;
  188. const ChallengeList &challengeList = proxy ?
  189. priorRequest->response().response.proxyAuthenticate :
  190. priorRequest->response().response.wwwAuthenticate;
  191. if (isAcceptable(challengeList, "Basic"))
  192. scheme = "Basic";
  193. else if (isAcceptable(challengeList, "Digest"))
  194. scheme = "Digest";
  195. else
  196. return false;
  197. ScopedCFRef<CFMutableDictionaryRef> query = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
  198. const std::string host = uri.authority.host();
  199. CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
  200. ScopedCFRef<CFStringRef> server = CFStringCreateWithCStringNoCopy(NULL, host.c_str(), kCFStringEncodingUTF8, kCFAllocatorNull);
  201. CFDictionaryAddValue(query, kSecAttrServer, server);
  202. if (uri.authority.portDefined()) {
  203. int port = uri.authority.port();
  204. ScopedCFRef<CFNumberRef> cfPort = CFNumberCreate(NULL, kCFNumberIntType, &port);
  205. CFDictionaryAddValue(query, kSecAttrPort, cfPort);
  206. }
  207. CFTypeRef protocol = NULL;
  208. if (proxy && priorRequest->request().requestLine.method == CONNECT)
  209. protocol = kSecAttrProtocolHTTPSProxy;
  210. else if (proxy)
  211. protocol = kSecAttrProtocolHTTPProxy;
  212. else if (uri.scheme() == "https")
  213. protocol = kSecAttrProtocolHTTPS;
  214. else if (uri.scheme() == "http")
  215. protocol = kSecAttrProtocolHTTP;
  216. else
  217. MORDOR_NOTREACHED();
  218. CFDictionaryAddValue(query, kSecAttrProtocol, protocol);
  219. CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
  220. ScopedCFRef<SecKeychainItemRef> item;
  221. OSStatus status = SecItemCopyMatching(query, (CFTypeRef*)&item);
  222. if (status != errSecSuccess)
  223. return false;
  224. // SecItemCopyMatching() just returns one record by default.
  225. MORDOR_ASSERT(CFGetTypeID(item) == SecKeychainItemGetTypeID());
  226. SecKeychainAttributeInfo info;
  227. SecKeychainAttrType tag = kSecAccountItemAttr;
  228. CSSM_DB_ATTRIBUTE_FORMAT format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
  229. info.count = 1;
  230. info.tag = (UInt32 *)&tag;
  231. info.format = (UInt32 *)&format;
  232. SecKeychainAttributeList *attrs = NULL;
  233. UInt32 passwordLength = 0;
  234. void *passwordBytes = NULL;
  235. status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrs,
  236. &passwordLength, &passwordBytes);
  237. if (status != errSecSuccess)
  238. return false;
  239. try {
  240. username.assign((const char *)attrs->attr[0].data, attrs->attr[0].length);
  241. password.assign((const char *)passwordBytes, passwordLength);
  242. } catch (...) {
  243. SecKeychainItemFreeAttributesAndData(attrs, passwordBytes);
  244. throw;
  245. }
  246. SecKeychainItemFreeAttributesAndData(attrs, passwordBytes);
  247. return true;
  248. }
  249. #endif
  250. }}