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