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