httpauthentication.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2008, 2009 Andreas Hartmetz <ahartmetz@gmail.com> 00003 00004 This library is free software; you can redistribute it and/or 00005 modify it under the terms of the GNU Library General Public 00006 License as published by the Free Software Foundation; either 00007 version 2 of the License, or (at your option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include "httpauthentication.h" 00021 00022 #ifdef HAVE_LIBGSSAPI 00023 #ifdef GSSAPI_MIT 00024 #include <gssapi/gssapi.h> 00025 #else 00026 #include <gssapi.h> 00027 #endif /* GSSAPI_MIT */ 00028 00029 // Catch uncompatible crap (BR86019) 00030 #if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0) 00031 #include <gssapi/gssapi_generic.h> 00032 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name 00033 #endif 00034 00035 #endif /* HAVE_LIBGSSAPI */ 00036 00037 #include <krandom.h> 00038 #include <kdebug.h> 00039 #include <klocale.h> 00040 #include <kglobal.h> 00041 #include <kcodecs.h> 00042 #include <kconfiggroup.h> 00043 #include <kio/authinfo.h> 00044 #include <misc/kntlm/kntlm.h> 00045 00046 #include <QtNetwork/QHostInfo> 00047 #include <QtCore/QTextCodec> 00048 00049 00050 static bool isWhiteSpace(char ch) 00051 { 00052 return (ch == ' ' || ch == '\t' || ch == '\v' || ch == '\f'); 00053 } 00054 00055 static bool isWhiteSpaceOrComma(char ch) 00056 { 00057 return (ch == ',' || isWhiteSpace(ch)); 00058 } 00059 00060 static bool containsScheme(const char input[], int start, int end) 00061 { 00062 // skip any comma or white space 00063 while (start < end && isWhiteSpaceOrComma(input[start])) { 00064 start++; 00065 } 00066 00067 while (start < end) { 00068 if (isWhiteSpace(input[start])) { 00069 return true; 00070 } 00071 start++; 00072 } 00073 00074 return false; 00075 } 00076 00077 // keys on even indexes, values on odd indexes. Reduces code expansion for the templated 00078 // alternatives. 00079 // If "ba" starts with empty content it will be removed from ba to simplify later calls 00080 static QList<QByteArray> parseChallenge(QByteArray &ba, QByteArray *scheme, QByteArray* nextAuth = 0) 00081 { 00082 QList<QByteArray> values; 00083 const char *b = ba.constData(); 00084 int len = ba.count(); 00085 int start = 0, end = 0, pos = 0, pos2 = 0; 00086 00087 // parse scheme 00088 while (start < len && isWhiteSpaceOrComma(b[start])) { 00089 start++; 00090 } 00091 end = start; 00092 while (end < len && !isWhiteSpace(b[end])) { 00093 end++; 00094 } 00095 00096 // drop empty stuff from the given string, it would have to be skipped over and over again 00097 if (start != 0) { 00098 ba = ba.mid(start); 00099 end -= start; 00100 len -= start; 00101 start = 0; 00102 b = ba.constData(); 00103 } 00104 Q_ASSERT(scheme); 00105 *scheme = ba.left(end); 00106 00107 while (end < len) { 00108 start = end; 00109 while (end < len && b[end] != '=') { 00110 end++; 00111 } 00112 pos = end; // save the end position 00113 while (end - 1 > start && isWhiteSpace(b[end - 1])) { // trim whitespace 00114 end--; 00115 } 00116 pos2 = start; 00117 while (pos2 < end && isWhiteSpace(b[pos2])) { // skip whitespace 00118 pos2++; 00119 } 00120 if (containsScheme(b, start, end) || (b[pos2] == ',' && b[pos] != '=' && pos == len)) { 00121 if (nextAuth) { 00122 *nextAuth = QByteArray (b + start); 00123 } 00124 break; // break on start of next scheme. 00125 } 00126 while (start < len && isWhiteSpaceOrComma(b[start])) { 00127 start++; 00128 } 00129 values.append(QByteArray (b + start, end - start)); 00130 end = pos; // restore the end position 00131 if (end == len) { 00132 break; 00133 } 00134 00135 // parse value 00136 start = end + 1; //skip '=' 00137 while (start < len && isWhiteSpace(b[start])) { 00138 start++; 00139 } 00140 00141 if (b[start] == '"') { 00142 //quoted string 00143 bool hasBs = false; 00144 bool hasErr = false; 00145 end = ++start; 00146 while (end < len) { 00147 if (b[end] == '\\') { 00148 end++; 00149 if (end + 1 >= len) { 00150 hasErr = true; 00151 break; 00152 } else { 00153 hasBs = true; 00154 end++; 00155 } 00156 } else if (b[end] == '"') { 00157 break; 00158 } else { 00159 end++; 00160 } 00161 } 00162 if (hasErr || (end == len)) { 00163 // remove the key we already inserted 00164 kDebug(7113) << "error in quoted text for key" << values.last(); 00165 values.removeLast(); 00166 break; 00167 } 00168 QByteArray value = QByteArray(b + start, end - start); 00169 if (hasBs) { 00170 // skip over the next character, it might be an escaped backslash 00171 int i = -1; 00172 while ( (i = value.indexOf('\\', i + 1)) >= 0 ) { 00173 value.remove(i, 1); 00174 } 00175 } 00176 values.append(value); 00177 end++; 00178 } else { 00179 //unquoted string 00180 end = start; 00181 while (end < len && b[end] != ',' && !isWhiteSpace(b[end])) { 00182 end++; 00183 } 00184 values.append(QByteArray(b + start, end - start)); 00185 } 00186 00187 //the quoted string has ended, but only a comma ends a key-value pair 00188 while (end < len && isWhiteSpace(b[end])) { 00189 end++; 00190 } 00191 00192 // garbage, here should be end or field delimiter (comma) 00193 if (end < len && b[end] != ',') { 00194 kDebug(7113) << "unexpected character" << b[end] << "found in WWW-authentication header where token boundary (,) was expected"; 00195 break; 00196 } 00197 } 00198 // ensure every key has a value 00199 // WARNING: Do not remove the > 1 check or parsing a Type 1 NTLM 00200 // authentication challenge will surely fail. 00201 if (values.count() > 1 && values.count() % 2) { 00202 values.removeLast(); 00203 } 00204 return values; 00205 } 00206 00207 00208 static QByteArray valueForKey(const QList<QByteArray> &ba, const QByteArray &key) 00209 { 00210 for (int i = 0, count = ba.count(); (i + 1) < count; i += 2) { 00211 if (ba[i] == key) { 00212 return ba[i + 1]; 00213 } 00214 } 00215 return QByteArray(); 00216 } 00217 00218 KAbstractHttpAuthentication::KAbstractHttpAuthentication(KConfigGroup *config) 00219 :m_config(config), m_finalAuthStage(true) 00220 { 00221 reset(); 00222 } 00223 00224 KAbstractHttpAuthentication::~KAbstractHttpAuthentication() 00225 { 00226 } 00227 00228 QByteArray KAbstractHttpAuthentication::bestOffer(const QList<QByteArray> &offers) 00229 { 00230 // choose the most secure auth scheme offered 00231 QByteArray negotiateOffer; 00232 QByteArray digestOffer; 00233 QByteArray ntlmOffer; 00234 QByteArray basicOffer; 00235 Q_FOREACH (const QByteArray &offer, offers) { 00236 const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower(); 00237 #ifdef HAVE_LIBGSSAPI 00238 if (scheme == "negotiate") { // krazy:exclude=strings 00239 negotiateOffer = offer; 00240 } else 00241 #endif 00242 if (scheme == "digest") { // krazy:exclude=strings 00243 digestOffer = offer; 00244 } else if (scheme == "ntlm") { // krazy:exclude=strings 00245 ntlmOffer = offer; 00246 } else if (scheme == "basic") { // krazy:exclude=strings 00247 basicOffer = offer; 00248 } 00249 } 00250 00251 if (!negotiateOffer.isEmpty()) { 00252 return negotiateOffer; 00253 } 00254 00255 if (!digestOffer.isEmpty()) { 00256 return digestOffer; 00257 } 00258 00259 if (!ntlmOffer.isEmpty()) { 00260 return ntlmOffer; 00261 } 00262 00263 return basicOffer; //empty or not... 00264 } 00265 00266 00267 KAbstractHttpAuthentication *KAbstractHttpAuthentication::newAuth(const QByteArray &offer, KConfigGroup* config) 00268 { 00269 const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower(); 00270 #ifdef HAVE_LIBGSSAPI 00271 if (scheme == "negotiate") { // krazy:exclude=strings 00272 return new KHttpNegotiateAuthentication(config); 00273 } else 00274 #endif 00275 if (scheme == "digest") { // krazy:exclude=strings 00276 return new KHttpDigestAuthentication(); 00277 } else if (scheme == "ntlm") { // krazy:exclude=strings 00278 return new KHttpNtlmAuthentication(config); 00279 } else if (scheme == "basic") { // krazy:exclude=strings 00280 return new KHttpBasicAuthentication(); 00281 } 00282 return 0; 00283 } 00284 00285 QList< QByteArray > KAbstractHttpAuthentication::splitOffers(const QList< QByteArray >& offers) 00286 { 00287 // first detect if one entry may contain multiple offers 00288 QList<QByteArray> alloffers; 00289 foreach(QByteArray offer, offers) { 00290 QByteArray scheme, cont; 00291 00292 parseChallenge(offer, &scheme, &cont); 00293 00294 while (!cont.isEmpty()) { 00295 offer.chop(cont.length()); 00296 alloffers << offer; 00297 offer = cont; 00298 cont.clear(); 00299 parseChallenge(offer, &scheme, &cont); 00300 } 00301 alloffers << offer; 00302 } 00303 return alloffers; 00304 } 00305 00306 void KAbstractHttpAuthentication::reset() 00307 { 00308 m_scheme.clear(); 00309 m_challenge.clear(); 00310 m_challengeText.clear(); 00311 m_resource.clear(); 00312 m_httpMethod.clear(); 00313 m_isError = false; 00314 m_needCredentials = true; 00315 m_forceKeepAlive = false; 00316 m_forceDisconnect = false; 00317 m_keepPassword = false; 00318 m_headerFragment.clear(); 00319 m_username.clear(); 00320 m_password.clear(); 00321 m_config = 0; 00322 } 00323 00324 void KAbstractHttpAuthentication::setChallenge(const QByteArray &c, const KUrl &resource, 00325 const QByteArray &httpMethod) 00326 { 00327 reset(); 00328 m_challengeText = c.trimmed(); 00329 m_challenge = parseChallenge(m_challengeText, &m_scheme); 00330 Q_ASSERT(m_scheme.toLower() == scheme().toLower()); 00331 m_resource = resource; 00332 m_httpMethod = httpMethod.trimmed(); 00333 } 00334 00335 00336 QString KAbstractHttpAuthentication::realm() const 00337 { 00338 const QByteArray realm = valueForKey(m_challenge, "realm"); 00339 // TODO: Find out what this is supposed to address. The site mentioned below does not exist. 00340 if (KGlobal::locale()->language().contains(QLatin1String("ru"))) { 00341 //for sites like lib.homelinux.org 00342 return QTextCodec::codecForName("CP1251")->toUnicode(realm); 00343 } 00344 return QString::fromLatin1(realm.constData(), realm.length()); 00345 } 00346 00347 void KAbstractHttpAuthentication::authInfoBoilerplate(KIO::AuthInfo *a) const 00348 { 00349 a->url = m_resource; 00350 a->username = m_username; 00351 a->password = m_password; 00352 a->verifyPath = supportsPathMatching(); 00353 a->realmValue = realm(); 00354 a->digestInfo = QLatin1String(authDataToCache()); 00355 a->keepPassword = m_keepPassword; 00356 } 00357 00358 00359 void KAbstractHttpAuthentication::generateResponseCommon(const QString &user, const QString &password) 00360 { 00361 if (m_scheme.isEmpty() || m_httpMethod.isEmpty()) { 00362 m_isError = true; 00363 return; 00364 } 00365 00366 if (m_needCredentials) { 00367 m_username = user; 00368 m_password = password; 00369 } 00370 00371 m_isError = false; 00372 m_forceKeepAlive = false; 00373 m_forceDisconnect = false; 00374 } 00375 00376 00377 QByteArray KHttpBasicAuthentication::scheme() const 00378 { 00379 return "Basic"; 00380 } 00381 00382 00383 void KHttpBasicAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 00384 { 00385 authInfoBoilerplate(ai); 00386 } 00387 00388 void KHttpBasicAuthentication::generateResponse(const QString &user, const QString &password) 00389 { 00390 generateResponseCommon(user, password); 00391 if (m_isError) { 00392 return; 00393 } 00394 00395 m_headerFragment = "Basic "; 00396 m_headerFragment += QByteArray(m_username.toLatin1() + ':' + m_password.toLatin1()).toBase64(); 00397 m_headerFragment += "\r\n"; 00398 } 00399 00400 00401 QByteArray KHttpDigestAuthentication::scheme() const 00402 { 00403 return "Digest"; 00404 } 00405 00406 00407 void KHttpDigestAuthentication::setChallenge(const QByteArray &c, const KUrl &resource, 00408 const QByteArray &httpMethod) 00409 { 00410 QString oldUsername; 00411 QString oldPassword; 00412 if (valueForKey(m_challenge, "stale").toLower() == "true") { 00413 // stale nonce: the auth failure that triggered this round of authentication is an artifact 00414 // of digest authentication. the credentials are probably still good, so keep them. 00415 oldUsername = m_username; 00416 oldPassword = m_password; 00417 } 00418 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); 00419 if (!oldUsername.isEmpty() && !oldPassword.isEmpty()) { 00420 // keep credentials *and* don't ask for new ones 00421 m_needCredentials = false; 00422 m_username = oldUsername; 00423 m_password = oldPassword; 00424 } 00425 } 00426 00427 00428 void KHttpDigestAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 00429 { 00430 authInfoBoilerplate(ai); 00431 } 00432 00433 00434 struct DigestAuthInfo 00435 { 00436 QByteArray nc; 00437 QByteArray qop; 00438 QByteArray realm; 00439 QByteArray nonce; 00440 QByteArray method; 00441 QByteArray cnonce; 00442 QByteArray username; 00443 QByteArray password; 00444 KUrl::List digestURIs; 00445 QByteArray algorithm; 00446 QByteArray entityBody; 00447 }; 00448 00449 00450 //calculateResponse() from the original HTTPProtocol 00451 static QByteArray calculateResponse(const DigestAuthInfo &info, const KUrl &resource) 00452 { 00453 KMD5 md; 00454 QByteArray HA1; 00455 QByteArray HA2; 00456 00457 // Calculate H(A1) 00458 QByteArray authStr = info.username; 00459 authStr += ':'; 00460 authStr += info.realm; 00461 authStr += ':'; 00462 authStr += info.password; 00463 md.update( authStr ); 00464 00465 if ( info.algorithm.toLower() == "md5-sess" ) 00466 { 00467 authStr = md.hexDigest(); 00468 authStr += ':'; 00469 authStr += info.nonce; 00470 authStr += ':'; 00471 authStr += info.cnonce; 00472 md.reset(); 00473 md.update( authStr ); 00474 } 00475 HA1 = md.hexDigest(); 00476 00477 kDebug(7113) << "A1 => " << HA1; 00478 00479 // Calcualte H(A2) 00480 authStr = info.method; 00481 authStr += ':'; 00482 authStr += resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath).toLatin1(); 00483 if ( info.qop == "auth-int" ) 00484 { 00485 authStr += ':'; 00486 md.reset(); 00487 md.update(info.entityBody); 00488 authStr += md.hexDigest(); 00489 } 00490 md.reset(); 00491 md.update( authStr ); 00492 HA2 = md.hexDigest(); 00493 00494 kDebug(7113) << "A2 => " << HA2; 00495 00496 // Calcualte the response. 00497 authStr = HA1; 00498 authStr += ':'; 00499 authStr += info.nonce; 00500 authStr += ':'; 00501 if ( !info.qop.isEmpty() ) 00502 { 00503 authStr += info.nc; 00504 authStr += ':'; 00505 authStr += info.cnonce; 00506 authStr += ':'; 00507 authStr += info.qop; 00508 authStr += ':'; 00509 } 00510 authStr += HA2; 00511 md.reset(); 00512 md.update( authStr ); 00513 00514 const QByteArray response = md.hexDigest(); 00515 kDebug(7113) << "Response =>" << response; 00516 return response; 00517 } 00518 00519 00520 void KHttpDigestAuthentication::generateResponse(const QString &user, const QString &password) 00521 { 00522 generateResponseCommon(user, password); 00523 if (m_isError) { 00524 return; 00525 } 00526 00527 // magic starts here (this part is slightly modified from the original in HTTPProtocol) 00528 00529 DigestAuthInfo info; 00530 00531 info.username = m_username.toLatin1(); //### charset breakage 00532 info.password = m_password.toLatin1(); //### 00533 00534 // info.entityBody = p; // FIXME: send digest of data for POST action ?? 00535 info.realm = ""; 00536 info.nonce = ""; 00537 info.qop = ""; 00538 00539 // cnonce is recommended to contain about 64 bits of entropy 00540 #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER 00541 info.cnonce = m_nonce; 00542 #else 00543 info.cnonce = KRandom::randomString(16).toLatin1(); 00544 #endif 00545 00546 // HACK: Should be fixed according to RFC 2617 section 3.2.2 00547 info.nc = "00000001"; 00548 00549 // Set the method used... 00550 info.method = m_httpMethod; 00551 00552 // Parse the Digest response.... 00553 info.realm = valueForKey(m_challenge, "realm"); 00554 00555 info.algorithm = valueForKey(m_challenge, "algorithm"); 00556 if (info.algorithm.isEmpty()) { 00557 info.algorithm = valueForKey(m_challenge, "algorith"); 00558 } 00559 if (info.algorithm.isEmpty()) { 00560 info.algorithm = "MD5"; 00561 } 00562 00563 Q_FOREACH (const QByteArray &path, valueForKey(m_challenge, "domain").split(' ')) { 00564 KUrl u(m_resource, QString::fromLatin1(path)); 00565 if (u.isValid()) { 00566 info.digestURIs.append(u); 00567 } 00568 } 00569 00570 info.nonce = valueForKey(m_challenge, "nonce"); 00571 QByteArray opaque = valueForKey(m_challenge, "opaque"); 00572 info.qop = valueForKey(m_challenge, "qop"); 00573 00574 // NOTE: Since we do not have access to the entity body, we cannot support 00575 // the "auth-int" qop value ; so if the server returns a comma separated 00576 // list of qop values, prefer "auth".See RFC 2617 sec 3.2.2 for the details. 00577 // If "auth" is not present or it is set to "auth-int" only, then we simply 00578 // print a warning message and disregard the qop option altogether. 00579 if (info.qop.contains(',')) { 00580 const QList<QByteArray> values = info.qop.split(','); 00581 if (info.qop.contains("auth")) 00582 info.qop = "auth"; 00583 else { 00584 kWarning(7113) << "Unsupported digest authentication qop parameters:" << values; 00585 info.qop.clear(); 00586 } 00587 } else if (info.qop == "auth-int") { 00588 kWarning(7113) << "Unsupported digest authentication qop parameter:" << info.qop; 00589 info.qop.clear(); 00590 } 00591 00592 if (info.realm.isEmpty() || info.nonce.isEmpty()) { 00593 // ### proper error return 00594 m_isError = true; 00595 return; 00596 } 00597 00598 // If the "domain" attribute was not specified and the current response code 00599 // is authentication needed, add the current request url to the list over which 00600 // this credential can be automatically applied. 00601 if (info.digestURIs.isEmpty() /*###&& (m_request.responseCode == 401 || m_request.responseCode == 407)*/) 00602 info.digestURIs.append (m_resource); 00603 else 00604 { 00605 // Verify whether or not we should send a cached credential to the 00606 // server based on the stored "domain" attribute... 00607 bool send = true; 00608 00609 // Determine the path of the request url... 00610 QString requestPath = m_resource.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash); 00611 if (requestPath.isEmpty()) 00612 requestPath = QLatin1Char('/'); 00613 00614 Q_FOREACH (const KUrl &u, info.digestURIs) 00615 { 00616 send &= (m_resource.protocol().toLower() == u.protocol().toLower()); 00617 send &= (m_resource.host().toLower() == u.host().toLower()); 00618 00619 if (m_resource.port() > 0 && u.port() > 0) 00620 send &= (m_resource.port() == u.port()); 00621 00622 QString digestPath = u.directory (KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash); 00623 if (digestPath.isEmpty()) 00624 digestPath = QLatin1Char('/'); 00625 00626 send &= (requestPath.startsWith(digestPath)); 00627 00628 if (send) 00629 break; 00630 } 00631 00632 if (!send) { 00633 m_isError = true; 00634 return; 00635 } 00636 } 00637 00638 kDebug(7113) << "RESULT OF PARSING:"; 00639 kDebug(7113) << " algorithm: " << info.algorithm; 00640 kDebug(7113) << " realm: " << info.realm; 00641 kDebug(7113) << " nonce: " << info.nonce; 00642 kDebug(7113) << " opaque: " << opaque; 00643 kDebug(7113) << " qop: " << info.qop; 00644 00645 // Calculate the response... 00646 const QByteArray response = calculateResponse(info, m_resource); 00647 00648 QByteArray auth = "Digest username=\""; 00649 auth += info.username; 00650 00651 auth += "\", realm=\""; 00652 auth += info.realm; 00653 auth += "\""; 00654 00655 auth += ", nonce=\""; 00656 auth += info.nonce; 00657 00658 auth += "\", uri=\""; 00659 auth += m_resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath).toLatin1(); 00660 00661 if (!info.algorithm.isEmpty()) { 00662 auth += "\", algorithm="; 00663 auth += info.algorithm; 00664 } 00665 00666 if ( !info.qop.isEmpty() ) 00667 { 00668 auth += ", qop="; 00669 auth += info.qop; 00670 auth += ", cnonce=\""; 00671 auth += info.cnonce; 00672 auth += "\", nc="; 00673 auth += info.nc; 00674 } 00675 00676 auth += ", response=\""; 00677 auth += response; 00678 if ( !opaque.isEmpty() ) 00679 { 00680 auth += "\", opaque=\""; 00681 auth += opaque; 00682 } 00683 auth += "\"\r\n"; 00684 00685 // magic ends here 00686 // note that auth already contains \r\n 00687 m_headerFragment = auth; 00688 } 00689 00690 #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER 00691 void KHttpDigestAuthentication::setDigestNonceValue(const QByteArray& nonce) 00692 { 00693 m_nonce = nonce; 00694 } 00695 #endif 00696 00697 00698 QByteArray KHttpNtlmAuthentication::scheme() const 00699 { 00700 return "NTLM"; 00701 } 00702 00703 00704 void KHttpNtlmAuthentication::setChallenge(const QByteArray &c, const KUrl &resource, 00705 const QByteArray &httpMethod) 00706 { 00707 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); 00708 if (m_challenge.isEmpty()) { 00709 // The type 1 message we're going to send needs no credentials; 00710 // they come later in the type 3 message. 00711 m_needCredentials = false; 00712 } 00713 } 00714 00715 00716 void KHttpNtlmAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 00717 { 00718 authInfoBoilerplate(ai); 00719 // Every auth scheme is supposed to supply a realm according to the RFCs. Of course this doesn't 00720 // prevent Microsoft from not doing it... Dummy value! 00721 // we don't have the username yet which may (may!) contain a domain, so we really have no choice 00722 ai->realmValue = QLatin1String("NTLM"); 00723 } 00724 00725 00726 void KHttpNtlmAuthentication::generateResponse(const QString &_user, const QString &password) 00727 { 00728 generateResponseCommon(_user, password); 00729 if (m_isError) { 00730 return; 00731 } 00732 00733 QByteArray buf; 00734 00735 if (m_challenge.isEmpty()) { 00736 m_finalAuthStage = false; 00737 // first, send type 1 message (with empty domain, workstation..., but it still works) 00738 if (!KNTLM::getNegotiate(buf)) { 00739 kWarning(7113) << "Error while constructing Type 1 NTLM authentication request"; 00740 m_isError = true; 00741 return; 00742 } 00743 } else { 00744 m_finalAuthStage = true; 00745 // we've (hopefully) received a valid type 2 message: send type 3 message as last step 00746 QString user, domain; 00747 if (m_username.contains(QLatin1Char('\\'))) { 00748 domain = m_username.section(QLatin1Char('\\'), 0, 0); 00749 user = m_username.section(QLatin1Char('\\'), 1); 00750 } else { 00751 user = m_username; 00752 } 00753 00754 m_forceKeepAlive = true; 00755 const QByteArray challenge = QByteArray::fromBase64(m_challenge[0]); 00756 00757 KNTLM::AuthFlags flags = KNTLM::Add_LM; 00758 if (!m_config || !m_config->readEntry("EnableNTLMv2Auth", false)) { 00759 flags |= KNTLM::Force_V1; 00760 } 00761 00762 if (!KNTLM::getAuth(buf, challenge, user, password, domain, QHostInfo::localHostName(), flags)) { 00763 kWarning(7113) << "Error while constructing Type 3 NTLM authentication request"; 00764 m_isError = true; 00765 return; 00766 } 00767 } 00768 00769 m_headerFragment = "NTLM "; 00770 m_headerFragment += buf.toBase64(); 00771 m_headerFragment += "\r\n"; 00772 00773 return; 00774 } 00775 00776 00778 #ifdef HAVE_LIBGSSAPI 00779 00780 // just an error message formatter 00781 static QByteArray gssError(int major_status, int minor_status) 00782 { 00783 OM_uint32 new_status; 00784 OM_uint32 msg_ctx = 0; 00785 gss_buffer_desc major_string; 00786 gss_buffer_desc minor_string; 00787 OM_uint32 ret; 00788 QByteArray errorstr; 00789 00790 do { 00791 ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string); 00792 errorstr += (const char *)major_string.value; 00793 errorstr += ' '; 00794 ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string); 00795 errorstr += (const char *)minor_string.value; 00796 errorstr += ' '; 00797 } while (!GSS_ERROR(ret) && msg_ctx != 0); 00798 00799 return errorstr; 00800 } 00801 00802 00803 QByteArray KHttpNegotiateAuthentication::scheme() const 00804 { 00805 return "Negotiate"; 00806 } 00807 00808 00809 void KHttpNegotiateAuthentication::setChallenge(const QByteArray &c, const KUrl &resource, 00810 const QByteArray &httpMethod) 00811 { 00812 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); 00813 // GSSAPI knows how to get the credentials on its own 00814 m_needCredentials = false; 00815 } 00816 00817 00818 void KHttpNegotiateAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 00819 { 00820 authInfoBoilerplate(ai); 00821 //### does GSSAPI supply anything realm-like? dummy value for now. 00822 ai->realmValue = QLatin1String("Negotiate"); 00823 } 00824 00825 00826 void KHttpNegotiateAuthentication::generateResponse(const QString &user, const QString &password) 00827 { 00828 generateResponseCommon(user, password); 00829 if (m_isError) { 00830 return; 00831 } 00832 00833 OM_uint32 major_status, minor_status; 00834 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 00835 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 00836 gss_name_t server; 00837 gss_ctx_id_t ctx; 00838 gss_OID mech_oid; 00839 static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; 00840 static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"}; 00841 gss_OID_set mech_set; 00842 gss_OID tmp_oid; 00843 00844 ctx = GSS_C_NO_CONTEXT; 00845 mech_oid = &krb5_oid_desc; 00846 00847 // see whether we can use the SPNEGO mechanism 00848 major_status = gss_indicate_mechs(&minor_status, &mech_set); 00849 if (GSS_ERROR(major_status)) { 00850 kDebug(7113) << "gss_indicate_mechs failed: " << gssError(major_status, minor_status); 00851 } else { 00852 for (uint i = 0; i < mech_set->count; i++) { 00853 tmp_oid = &mech_set->elements[i]; 00854 if (tmp_oid->length == spnego_oid_desc.length && 00855 !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) { 00856 kDebug(7113) << "found SPNEGO mech"; 00857 mech_oid = &spnego_oid_desc; 00858 break; 00859 } 00860 } 00861 gss_release_oid_set(&minor_status, &mech_set); 00862 } 00863 00864 // the service name is "HTTP/f.q.d.n" 00865 QByteArray servicename = "HTTP@"; 00866 servicename += m_resource.host().toAscii(); 00867 00868 input_token.value = (void *)servicename.data(); 00869 input_token.length = servicename.length() + 1; 00870 00871 major_status = gss_import_name(&minor_status, &input_token, 00872 GSS_C_NT_HOSTBASED_SERVICE, &server); 00873 00874 input_token.value = NULL; 00875 input_token.length = 0; 00876 00877 if (GSS_ERROR(major_status)) { 00878 kDebug(7113) << "gss_import_name failed: " << gssError(major_status, minor_status); 00879 m_isError = true; 00880 return; 00881 } 00882 00883 OM_uint32 req_flags; 00884 if (m_config && m_config->readEntry("DelegateCredentialsOn", false)) 00885 req_flags = GSS_C_DELEG_FLAG; 00886 else 00887 req_flags = 0; 00888 00889 // GSSAPI knows how to get the credentials its own way, so don't ask for any 00890 major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, 00891 &ctx, server, mech_oid, 00892 req_flags, GSS_C_INDEFINITE, 00893 GSS_C_NO_CHANNEL_BINDINGS, 00894 GSS_C_NO_BUFFER, NULL, &output_token, 00895 NULL, NULL); 00896 00897 if (GSS_ERROR(major_status) || (output_token.length == 0)) { 00898 kDebug(7113) << "gss_init_sec_context failed: " << gssError(major_status, minor_status); 00899 gss_release_name(&minor_status, &server); 00900 if (ctx != GSS_C_NO_CONTEXT) { 00901 gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); 00902 ctx = GSS_C_NO_CONTEXT; 00903 } 00904 m_isError = true; 00905 return; 00906 } 00907 00908 m_headerFragment = "Negotiate "; 00909 m_headerFragment += QByteArray::fromRawData((const char *)output_token.value, 00910 output_token.length).toBase64(); 00911 m_headerFragment += "\r\n"; 00912 00913 // free everything 00914 gss_release_name(&minor_status, &server); 00915 if (ctx != GSS_C_NO_CONTEXT) { 00916 gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); 00917 ctx = GSS_C_NO_CONTEXT; 00918 } 00919 gss_release_buffer(&minor_status, &output_token); 00920 } 00921 00922 #endif // HAVE_LIBGSSAPI
KDE 4.8 API Reference