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