File indexing completed on 2025-03-16 10:02:23
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2008, 2009 Andreas Hartmetz <ahartmetz@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "httpauthentication.h" 0009 0010 #if HAVE_LIBGSSAPI 0011 #if GSSAPI_MIT 0012 #include <gssapi/gssapi.h> 0013 #else 0014 #include <gssapi.h> 0015 #endif /* GSSAPI_MIT */ 0016 0017 // Catch uncompatible crap (BR86019) 0018 #if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0) 0019 #include <gssapi/gssapi_generic.h> 0020 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name 0021 #endif 0022 0023 #endif /* HAVE_LIBGSSAPI */ 0024 0025 #include <KConfigGroup> 0026 #include <KRandom> 0027 #include <QDebug> 0028 #include <kio/authinfo.h> 0029 #include <kntlm.h> 0030 0031 #include <QCryptographicHash> 0032 #include <QTextCodec> 0033 0034 Q_LOGGING_CATEGORY(KIO_HTTP_AUTH, "kf.kio.workers.http.auth") 0035 0036 static bool isWhiteSpace(char ch) 0037 { 0038 return (ch == ' ' || ch == '\t' || ch == '\v' || ch == '\f'); 0039 } 0040 0041 static bool isWhiteSpaceOrComma(char ch) 0042 { 0043 return (ch == ',' || isWhiteSpace(ch)); 0044 } 0045 0046 static bool containsScheme(const char input[], int start, int end) 0047 { 0048 // skip any comma or white space 0049 while (start < end && isWhiteSpaceOrComma(input[start])) { 0050 start++; 0051 } 0052 0053 while (start < end) { 0054 if (isWhiteSpace(input[start])) { 0055 return true; 0056 } 0057 start++; 0058 } 0059 0060 return false; 0061 } 0062 0063 // keys on even indexes, values on odd indexes. Reduces code expansion for the templated 0064 // alternatives. 0065 // If "ba" starts with empty content it will be removed from ba to simplify later calls 0066 static QList<QByteArray> parseChallenge(QByteArray &ba, QByteArray *scheme, QByteArray *nextAuth = nullptr) 0067 { 0068 QList<QByteArray> values; 0069 const char *b = ba.constData(); 0070 int len = ba.count(); 0071 int start = 0; 0072 int end = 0; 0073 int pos = 0; 0074 int pos2 = 0; 0075 0076 // parse scheme 0077 while (start < len && isWhiteSpaceOrComma(b[start])) { 0078 start++; 0079 } 0080 end = start; 0081 while (end < len && !isWhiteSpace(b[end])) { 0082 end++; 0083 } 0084 0085 // drop empty stuff from the given string, it would have to be skipped over and over again 0086 if (start != 0) { 0087 ba.remove(0, start); 0088 end -= start; 0089 len -= start; 0090 start = 0; 0091 b = ba.constData(); 0092 } 0093 Q_ASSERT(scheme); 0094 *scheme = ba.left(end); 0095 0096 while (end < len) { 0097 start = end; 0098 while (end < len && b[end] != '=') { 0099 end++; 0100 } 0101 pos = end; // save the end position 0102 while (end - 1 > start && isWhiteSpace(b[end - 1])) { // trim whitespace 0103 end--; 0104 } 0105 pos2 = start; 0106 while (pos2 < end && isWhiteSpace(b[pos2])) { // skip whitespace 0107 pos2++; 0108 } 0109 if (containsScheme(b, start, end) || (b[pos2] == ',' && b[pos] != '=' && pos == len)) { 0110 if (nextAuth) { 0111 *nextAuth = QByteArray(b + start); 0112 } 0113 break; // break on start of next scheme. 0114 } 0115 while (start < len && isWhiteSpaceOrComma(b[start])) { 0116 start++; 0117 } 0118 values.append(QByteArray(b + start, end - start)); 0119 end = pos; // restore the end position 0120 if (end == len) { 0121 break; 0122 } 0123 0124 // parse value 0125 start = end + 1; // skip '=' 0126 while (start < len && isWhiteSpace(b[start])) { 0127 start++; 0128 } 0129 0130 if (b[start] == '"') { 0131 // quoted string 0132 bool hasBs = false; 0133 bool hasErr = false; 0134 end = ++start; 0135 while (end < len) { 0136 if (b[end] == '\\') { 0137 end++; 0138 if (end + 1 >= len) { 0139 hasErr = true; 0140 break; 0141 } else { 0142 hasBs = true; 0143 end++; 0144 } 0145 } else if (b[end] == '"') { 0146 break; 0147 } else { 0148 end++; 0149 } 0150 } 0151 if (hasErr || (end == len)) { 0152 // remove the key we already inserted 0153 // qDebug() << "error in quoted text for key" << values.last(); 0154 values.removeLast(); 0155 break; 0156 } 0157 QByteArray value = QByteArray(b + start, end - start); 0158 if (hasBs) { 0159 // skip over the next character, it might be an escaped backslash 0160 int i = -1; 0161 while ((i = value.indexOf('\\', i + 1)) >= 0) { 0162 value.remove(i, 1); 0163 } 0164 } 0165 values.append(value); 0166 end++; 0167 } else { 0168 // unquoted string 0169 end = start; 0170 while (end < len && b[end] != ',' && !isWhiteSpace(b[end])) { 0171 end++; 0172 } 0173 values.append(QByteArray(b + start, end - start)); 0174 } 0175 0176 // the quoted string has ended, but only a comma ends a key-value pair 0177 while (end < len && isWhiteSpace(b[end])) { 0178 end++; 0179 } 0180 0181 // garbage, here should be end or field delimiter (comma) 0182 if (end < len && b[end] != ',') { 0183 // qDebug() << "unexpected character" << b[end] << "found in WWW-authentication header where token boundary (,) was expected"; 0184 break; 0185 } 0186 } 0187 // ensure every key has a value 0188 // WARNING: Do not remove the > 1 check or parsing a Type 1 NTLM 0189 // authentication challenge will surely fail. 0190 if (values.count() > 1 && values.count() % 2) { 0191 values.removeLast(); 0192 } 0193 return values; 0194 } 0195 0196 static QByteArray valueForKey(const QList<QByteArray> &ba, const QByteArray &key) 0197 { 0198 for (int i = 0, count = ba.count(); (i + 1) < count; i += 2) { 0199 if (ba[i] == key) { 0200 return ba[i + 1]; 0201 } 0202 } 0203 return QByteArray(); 0204 } 0205 0206 KAbstractHttpAuthentication::KAbstractHttpAuthentication(KConfigGroup *config) 0207 : m_config(config) 0208 , m_finalAuthStage(false) 0209 { 0210 reset(); 0211 } 0212 0213 KAbstractHttpAuthentication::~KAbstractHttpAuthentication() 0214 { 0215 } 0216 0217 QByteArray KAbstractHttpAuthentication::bestOffer(const QList<QByteArray> &offers) 0218 { 0219 // choose the most secure auth scheme offered 0220 QByteArray negotiateOffer; 0221 QByteArray digestOffer; 0222 QByteArray ntlmOffer; 0223 QByteArray basicOffer; 0224 for (const QByteArray &offer : offers) { 0225 const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower(); 0226 #if HAVE_LIBGSSAPI 0227 if (scheme == "negotiate") { // krazy:exclude=strings 0228 negotiateOffer = offer; 0229 } else 0230 #endif 0231 if (scheme == "digest") { // krazy:exclude=strings 0232 digestOffer = offer; 0233 } else if (scheme == "ntlm") { // krazy:exclude=strings 0234 ntlmOffer = offer; 0235 } else if (scheme == "basic") { // krazy:exclude=strings 0236 basicOffer = offer; 0237 } 0238 } 0239 0240 if (!negotiateOffer.isEmpty()) { 0241 return negotiateOffer; 0242 } 0243 0244 if (!digestOffer.isEmpty()) { 0245 return digestOffer; 0246 } 0247 0248 if (!ntlmOffer.isEmpty()) { 0249 return ntlmOffer; 0250 } 0251 0252 return basicOffer; // empty or not... 0253 } 0254 0255 KAbstractHttpAuthentication *KAbstractHttpAuthentication::newAuth(const QByteArray &offer, KConfigGroup *config) 0256 { 0257 const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower(); 0258 #if HAVE_LIBGSSAPI 0259 if (scheme == "negotiate") { // krazy:exclude=strings 0260 return new KHttpNegotiateAuthentication(config); 0261 } else 0262 #endif 0263 if (scheme == "digest") { // krazy:exclude=strings 0264 return new KHttpDigestAuthentication(); 0265 } else if (scheme == "ntlm") { // krazy:exclude=strings 0266 return new KHttpNtlmAuthentication(config); 0267 } else if (scheme == "basic") { // krazy:exclude=strings 0268 return new KHttpBasicAuthentication(); 0269 } 0270 return nullptr; 0271 } 0272 0273 QList<QByteArray> KAbstractHttpAuthentication::splitOffers(const QList<QByteArray> &offers) 0274 { 0275 // first detect if one entry may contain multiple offers 0276 QList<QByteArray> alloffers; 0277 for (QByteArray offer : offers) { 0278 QByteArray scheme; 0279 QByteArray cont; 0280 0281 parseChallenge(offer, &scheme, &cont); 0282 0283 while (!cont.isEmpty()) { 0284 offer.chop(cont.length()); 0285 alloffers << offer; 0286 offer = cont; 0287 cont.clear(); 0288 parseChallenge(offer, &scheme, &cont); 0289 } 0290 alloffers << offer; 0291 } 0292 return alloffers; 0293 } 0294 0295 void KAbstractHttpAuthentication::reset() 0296 { 0297 m_scheme.clear(); 0298 m_challenge.clear(); 0299 m_challengeText.clear(); 0300 m_resource.clear(); 0301 m_httpMethod.clear(); 0302 m_isError = false; 0303 m_needCredentials = true; 0304 m_forceKeepAlive = false; 0305 m_forceDisconnect = false; 0306 m_keepPassword = false; 0307 m_headerFragment.clear(); 0308 m_username.clear(); 0309 m_password.clear(); 0310 } 0311 0312 void KAbstractHttpAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) 0313 { 0314 reset(); 0315 m_challengeText = c.trimmed(); 0316 m_challenge = parseChallenge(m_challengeText, &m_scheme); 0317 Q_ASSERT(m_scheme.toLower() == scheme().toLower()); 0318 m_resource = resource; 0319 m_httpMethod = httpMethod; 0320 } 0321 0322 QString KAbstractHttpAuthentication::realm() const 0323 { 0324 const QByteArray realm = valueForKey(m_challenge, "realm"); 0325 // TODO: Find out what this is supposed to address. The site mentioned below does not exist. 0326 if (QLocale().uiLanguages().contains(QLatin1String("ru"))) { 0327 // for sites like lib.homelinux.org 0328 return QTextCodec::codecForName("CP1251")->toUnicode(realm); 0329 } 0330 return QString::fromLatin1(realm.constData(), realm.length()); 0331 } 0332 0333 void KAbstractHttpAuthentication::authInfoBoilerplate(KIO::AuthInfo *a) const 0334 { 0335 a->url = m_resource; 0336 a->username = m_username; 0337 a->password = m_password; 0338 a->verifyPath = supportsPathMatching(); 0339 a->realmValue = realm(); 0340 a->digestInfo = QLatin1String(authDataToCache()); 0341 a->keepPassword = m_keepPassword; 0342 } 0343 0344 void KAbstractHttpAuthentication::generateResponseCommon(const QString &user, const QString &password) 0345 { 0346 if (m_scheme.isEmpty() || m_httpMethod.isEmpty()) { 0347 m_isError = true; 0348 return; 0349 } 0350 0351 if (m_needCredentials) { 0352 m_username = user; 0353 m_password = password; 0354 } 0355 0356 m_isError = false; 0357 m_forceKeepAlive = false; 0358 m_forceDisconnect = false; 0359 m_finalAuthStage = true; 0360 } 0361 0362 QByteArray KHttpBasicAuthentication::scheme() const 0363 { 0364 return "Basic"; 0365 } 0366 0367 void KHttpBasicAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 0368 { 0369 authInfoBoilerplate(ai); 0370 } 0371 0372 void KHttpBasicAuthentication::generateResponse(const QString &user, const QString &password) 0373 { 0374 generateResponseCommon(user, password); 0375 if (m_isError) { 0376 return; 0377 } 0378 0379 m_headerFragment = "Basic "; 0380 m_headerFragment += QByteArray(m_username.toLatin1() + ':' + m_password.toLatin1()).toBase64(); 0381 m_headerFragment += "\r\n"; 0382 } 0383 0384 QByteArray KHttpDigestAuthentication::scheme() const 0385 { 0386 return "Digest"; 0387 } 0388 0389 void KHttpDigestAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) 0390 { 0391 QString oldUsername; 0392 QString oldPassword; 0393 if (valueForKey(m_challenge, "stale").toLower() == "true") { 0394 // stale nonce: the auth failure that triggered this round of authentication is an artifact 0395 // of digest authentication. the credentials are probably still good, so keep them. 0396 oldUsername = m_username; 0397 oldPassword = m_password; 0398 } 0399 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); 0400 if (!oldUsername.isEmpty() && !oldPassword.isEmpty()) { 0401 // keep credentials *and* don't ask for new ones 0402 m_needCredentials = false; 0403 m_username = oldUsername; 0404 m_password = oldPassword; 0405 } 0406 } 0407 0408 void KHttpDigestAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 0409 { 0410 authInfoBoilerplate(ai); 0411 } 0412 0413 struct DigestAuthInfo { 0414 QByteArray nc; 0415 QByteArray qop; 0416 QByteArray realm; 0417 QByteArray nonce; 0418 QByteArray method; 0419 QByteArray cnonce; 0420 QByteArray username; 0421 QByteArray password; 0422 QList<QUrl> digestURIs; 0423 QByteArray algorithm; 0424 QByteArray entityBody; 0425 }; 0426 0427 // calculateResponse() from the original HTTPProtocol 0428 static QByteArray calculateResponse(const DigestAuthInfo &info, const QUrl &resource) 0429 { 0430 QCryptographicHash md(QCryptographicHash::Md5); 0431 QByteArray HA1; 0432 QByteArray HA2; 0433 0434 // Calculate H(A1) 0435 QByteArray authStr = info.username; 0436 authStr += ':'; 0437 authStr += info.realm; 0438 authStr += ':'; 0439 authStr += info.password; 0440 md.addData(authStr); 0441 0442 if (info.algorithm.toLower() == "md5-sess") { 0443 authStr = md.result().toHex(); 0444 authStr += ':'; 0445 authStr += info.nonce; 0446 authStr += ':'; 0447 authStr += info.cnonce; 0448 md.reset(); 0449 md.addData(authStr); 0450 } 0451 HA1 = md.result().toHex(); 0452 0453 // qDebug() << "A1 => " << HA1; 0454 0455 // Calculate H(A2) 0456 authStr = info.method; 0457 authStr += ':'; 0458 authStr += resource.path(QUrl::FullyEncoded).toLatin1(); 0459 if (resource.hasQuery()) { 0460 authStr += '?' + resource.query(QUrl::FullyEncoded).toLatin1(); 0461 } 0462 if (info.qop == "auth-int") { 0463 authStr += ':'; 0464 md.reset(); 0465 md.addData(info.entityBody); 0466 authStr += md.result().toHex(); 0467 } 0468 md.reset(); 0469 md.addData(authStr); 0470 HA2 = md.result().toHex(); 0471 0472 // qDebug() << "A2 => " << HA2; 0473 0474 // Calculate the response. 0475 authStr = HA1; 0476 authStr += ':'; 0477 authStr += info.nonce; 0478 authStr += ':'; 0479 if (!info.qop.isEmpty()) { 0480 authStr += info.nc; 0481 authStr += ':'; 0482 authStr += info.cnonce; 0483 authStr += ':'; 0484 authStr += info.qop; 0485 authStr += ':'; 0486 } 0487 authStr += HA2; 0488 md.reset(); 0489 md.addData(authStr); 0490 0491 const QByteArray response = md.result().toHex(); 0492 // qDebug() << "Response =>" << response; 0493 return response; 0494 } 0495 0496 void KHttpDigestAuthentication::generateResponse(const QString &user, const QString &password) 0497 { 0498 generateResponseCommon(user, password); 0499 if (m_isError) { 0500 return; 0501 } 0502 0503 // magic starts here (this part is slightly modified from the original in HTTPProtocol) 0504 0505 DigestAuthInfo info; 0506 0507 info.username = m_username.toLatin1(); // ### charset breakage 0508 info.password = m_password.toLatin1(); // ### 0509 0510 // info.entityBody = p; // FIXME: send digest of data for POST action ?? 0511 info.realm = ""; 0512 info.nonce = ""; 0513 info.qop = ""; 0514 0515 // cnonce is recommended to contain about 64 bits of entropy 0516 #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER 0517 info.cnonce = m_nonce; 0518 #else 0519 info.cnonce = KRandom::randomString(16).toLatin1(); 0520 #endif 0521 0522 // HACK: Should be fixed according to RFC 2617 section 3.2.2 0523 info.nc = "00000001"; 0524 0525 // Set the method used... 0526 info.method = m_httpMethod; 0527 0528 // Parse the Digest response.... 0529 info.realm = valueForKey(m_challenge, "realm"); 0530 0531 info.algorithm = valueForKey(m_challenge, "algorithm"); 0532 if (info.algorithm.isEmpty()) { 0533 info.algorithm = valueForKey(m_challenge, "algorithm"); 0534 } 0535 if (info.algorithm.isEmpty()) { 0536 info.algorithm = "MD5"; 0537 } 0538 0539 const QList<QByteArray> list = valueForKey(m_challenge, "domain").split(' '); 0540 for (const QByteArray &path : list) { 0541 QUrl u = m_resource.resolved(QUrl(QString::fromUtf8(path))); 0542 if (u.isValid()) { 0543 info.digestURIs.append(u); 0544 } 0545 } 0546 0547 info.nonce = valueForKey(m_challenge, "nonce"); 0548 QByteArray opaque = valueForKey(m_challenge, "opaque"); 0549 info.qop = valueForKey(m_challenge, "qop"); 0550 0551 // NOTE: Since we do not have access to the entity body, we cannot support 0552 // the "auth-int" qop value ; so if the server returns a comma separated 0553 // list of qop values, prefer "auth".See RFC 2617 sec 3.2.2 for the details. 0554 // If "auth" is not present or it is set to "auth-int" only, then we simply 0555 // print a warning message and disregard the qop option altogether. 0556 if (info.qop.contains(',')) { 0557 const QList<QByteArray> values = info.qop.split(','); 0558 if (info.qop.contains("auth")) { 0559 info.qop = "auth"; 0560 } else { 0561 qCWarning(KIO_HTTP_AUTH) << "Unsupported digest authentication qop parameters:" << values; 0562 info.qop.clear(); 0563 } 0564 } else if (info.qop == "auth-int") { 0565 qCWarning(KIO_HTTP_AUTH) << "Unsupported digest authentication qop parameter:" << info.qop; 0566 info.qop.clear(); 0567 } 0568 0569 if (info.realm.isEmpty() || info.nonce.isEmpty()) { 0570 // ### proper error return 0571 m_isError = true; 0572 return; 0573 } 0574 0575 // If the "domain" attribute was not specified and the current response code 0576 // is authentication needed, add the current request url to the list over which 0577 // this credential can be automatically applied. 0578 if (info.digestURIs.isEmpty() /*###&& (m_request.responseCode == 401 || m_request.responseCode == 407)*/) { 0579 info.digestURIs.append(m_resource); 0580 } else { 0581 // Verify whether or not we should send a cached credential to the 0582 // server based on the stored "domain" attribute... 0583 bool send = true; 0584 0585 // Determine the path of the request url... 0586 QString requestPath = m_resource.adjusted(QUrl::RemoveFilename).path(); 0587 if (requestPath.isEmpty()) { 0588 requestPath = QLatin1Char('/'); 0589 } 0590 0591 for (const QUrl &u : std::as_const(info.digestURIs)) { 0592 send &= (m_resource.scheme().toLower() == u.scheme().toLower()); 0593 send &= (m_resource.host().toLower() == u.host().toLower()); 0594 0595 if (m_resource.port() > 0 && u.port() > 0) { 0596 send &= (m_resource.port() == u.port()); 0597 } 0598 0599 QString digestPath = u.adjusted(QUrl::RemoveFilename).path(); 0600 if (digestPath.isEmpty()) { 0601 digestPath = QLatin1Char('/'); 0602 } 0603 0604 send &= (requestPath.startsWith(digestPath)); 0605 0606 if (send) { 0607 break; 0608 } 0609 } 0610 0611 if (!send) { 0612 m_isError = true; 0613 return; 0614 } 0615 } 0616 0617 // qDebug() << "RESULT OF PARSING:"; 0618 // qDebug() << " algorithm: " << info.algorithm; 0619 // qDebug() << " realm: " << info.realm; 0620 // qDebug() << " nonce: " << info.nonce; 0621 // qDebug() << " opaque: " << opaque; 0622 // qDebug() << " qop: " << info.qop; 0623 0624 // Calculate the response... 0625 const QByteArray response = calculateResponse(info, m_resource); 0626 0627 QByteArray auth = "Digest username=\""; 0628 auth += info.username; 0629 0630 auth += "\", realm=\""; 0631 auth += info.realm; 0632 auth += "\""; 0633 0634 auth += ", nonce=\""; 0635 auth += info.nonce; 0636 0637 auth += "\", uri=\""; 0638 auth += m_resource.path(QUrl::FullyEncoded).toLatin1(); 0639 if (m_resource.hasQuery()) { 0640 auth += '?' + m_resource.query(QUrl::FullyEncoded).toLatin1(); 0641 } 0642 0643 if (!info.algorithm.isEmpty()) { 0644 auth += "\", algorithm="; 0645 auth += info.algorithm; 0646 } 0647 0648 if (!info.qop.isEmpty()) { 0649 auth += ", qop="; 0650 auth += info.qop; 0651 auth += ", cnonce=\""; 0652 auth += info.cnonce; 0653 auth += "\", nc="; 0654 auth += info.nc; 0655 } 0656 0657 auth += ", response=\""; 0658 auth += response; 0659 if (!opaque.isEmpty()) { 0660 auth += "\", opaque=\""; 0661 auth += opaque; 0662 } 0663 auth += "\"\r\n"; 0664 0665 // magic ends here 0666 // note that auth already contains \r\n 0667 m_headerFragment = auth; 0668 } 0669 0670 #ifdef ENABLE_HTTP_AUTH_NONCE_SETTER 0671 void KHttpDigestAuthentication::setDigestNonceValue(const QByteArray &nonce) 0672 { 0673 m_nonce = nonce; 0674 } 0675 #endif 0676 0677 QByteArray KHttpNtlmAuthentication::scheme() const 0678 { 0679 return "NTLM"; 0680 } 0681 0682 void KHttpNtlmAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) 0683 { 0684 QString oldUsername; 0685 QString oldPassword; 0686 if (!m_finalAuthStage && !m_username.isEmpty() && !m_password.isEmpty()) { 0687 oldUsername = m_username; 0688 oldPassword = m_password; 0689 } 0690 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); 0691 if (!oldUsername.isEmpty() && !oldPassword.isEmpty()) { 0692 m_username = oldUsername; 0693 m_password = oldPassword; 0694 } 0695 // The type 1 message we're going to send needs no credentials; 0696 // they come later in the type 3 message. 0697 m_needCredentials = !m_challenge.isEmpty(); 0698 } 0699 0700 void KHttpNtlmAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 0701 { 0702 authInfoBoilerplate(ai); 0703 // Every auth scheme is supposed to supply a realm according to the RFCs. Of course this doesn't 0704 // prevent Microsoft from not doing it... Dummy value! 0705 // we don't have the username yet which may (may!) contain a domain, so we really have no choice 0706 ai->realmValue = QStringLiteral("NTLM"); 0707 } 0708 0709 void KHttpNtlmAuthentication::generateResponse(const QString &_user, const QString &password) 0710 { 0711 generateResponseCommon(_user, password); 0712 if (m_isError) { 0713 return; 0714 } 0715 0716 QByteArray buf; 0717 0718 if (m_challenge.isEmpty()) { 0719 m_finalAuthStage = false; 0720 // first, send type 1 message (with empty domain, workstation..., but it still works) 0721 switch (m_stage1State) { 0722 case Init: 0723 if (!KNTLM::getNegotiate(buf)) { 0724 qCWarning(KIO_HTTP_AUTH) << "Error while constructing Type 1 NTLMv1 authentication request"; 0725 m_isError = true; 0726 return; 0727 } 0728 m_stage1State = SentNTLMv1; 0729 break; 0730 case SentNTLMv1: 0731 if (!KNTLM::getNegotiate(buf, 0732 QString(), 0733 QString(), 0734 KNTLM::Negotiate_NTLM2_Key | KNTLM::Negotiate_Always_Sign | KNTLM::Negotiate_Unicode | KNTLM::Request_Target 0735 | KNTLM::Negotiate_NTLM)) { 0736 qCWarning(KIO_HTTP_AUTH) << "Error while constructing Type 1 NTLMv2 authentication request"; 0737 m_isError = true; 0738 return; 0739 } 0740 m_stage1State = SentNTLMv2; 0741 break; 0742 default: 0743 qCWarning(KIO_HTTP_AUTH) << "Error - Type 1 NTLM already sent - no Type 2 response received."; 0744 m_isError = true; 0745 return; 0746 } 0747 } else { 0748 m_finalAuthStage = true; 0749 // we've (hopefully) received a valid type 2 message: send type 3 message as last step 0750 QString user; 0751 QString domain; 0752 if (m_username.contains(QLatin1Char('\\'))) { 0753 domain = m_username.section(QLatin1Char('\\'), 0, 0); 0754 // KNTLM::getAuth relies on isEmpty/isNull to distinguish this case and the else path below 0755 if (domain.isNull()) { 0756 domain = QLatin1String(""); 0757 } 0758 user = m_username.section(QLatin1Char('\\'), 1); 0759 } else { 0760 user = m_username; 0761 } 0762 0763 m_forceKeepAlive = true; 0764 const QByteArray challenge = QByteArray::fromBase64(m_challenge[0]); 0765 0766 KNTLM::AuthFlags flags = KNTLM::Add_LM; 0767 if ((!m_config || !m_config->readEntry("EnableNTLMv2Auth", false)) && (m_stage1State != SentNTLMv2)) { 0768 flags |= KNTLM::Force_V1; 0769 } 0770 0771 if (!KNTLM::getAuth(buf, challenge, user, m_password, domain, QStringLiteral("WORKSTATION"), flags)) { 0772 qCWarning(KIO_HTTP_AUTH) << "Error while constructing Type 3 NTLM authentication request"; 0773 m_isError = true; 0774 return; 0775 } 0776 } 0777 0778 m_headerFragment = "NTLM " + buf.toBase64() + "\r\n"; 0779 0780 return; 0781 } 0782 0783 ////////////////////////// 0784 #if HAVE_LIBGSSAPI 0785 0786 // just an error message formatter 0787 static QByteArray gssError(int major_status, int minor_status) 0788 { 0789 OM_uint32 new_status; 0790 OM_uint32 msg_ctx = 0; 0791 gss_buffer_desc major_string; 0792 gss_buffer_desc minor_string; 0793 OM_uint32 ret; 0794 QByteArray errorstr; 0795 0796 do { 0797 ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string); 0798 errorstr += (const char *)major_string.value; 0799 errorstr += ' '; 0800 ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string); 0801 errorstr += (const char *)minor_string.value; 0802 errorstr += ' '; 0803 } while (!GSS_ERROR(ret) && msg_ctx != 0); 0804 0805 return errorstr; 0806 } 0807 0808 QByteArray KHttpNegotiateAuthentication::scheme() const 0809 { 0810 return "Negotiate"; 0811 } 0812 0813 void KHttpNegotiateAuthentication::setChallenge(const QByteArray &c, const QUrl &resource, const QByteArray &httpMethod) 0814 { 0815 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod); 0816 // GSSAPI knows how to get the credentials on its own 0817 m_needCredentials = false; 0818 } 0819 0820 void KHttpNegotiateAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const 0821 { 0822 authInfoBoilerplate(ai); 0823 // ### does GSSAPI supply anything realm-like? dummy value for now. 0824 ai->realmValue = QStringLiteral("Negotiate"); 0825 } 0826 0827 void KHttpNegotiateAuthentication::generateResponse(const QString &user, const QString &password) 0828 { 0829 generateResponseCommon(user, password); 0830 if (m_isError) { 0831 return; 0832 } 0833 0834 OM_uint32 major_status; 0835 OM_uint32 minor_status; 0836 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 0837 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 0838 gss_name_t server; 0839 gss_ctx_id_t ctx; 0840 gss_OID mech_oid; 0841 static gss_OID_desc krb5_oid_desc = {9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; 0842 static gss_OID_desc spnego_oid_desc = {6, (void *)"\x2b\x06\x01\x05\x05\x02"}; 0843 gss_OID_set mech_set; 0844 gss_OID tmp_oid; 0845 0846 ctx = GSS_C_NO_CONTEXT; 0847 mech_oid = &krb5_oid_desc; 0848 0849 // see whether we can use the SPNEGO mechanism 0850 major_status = gss_indicate_mechs(&minor_status, &mech_set); 0851 if (GSS_ERROR(major_status)) { 0852 qCDebug(KIO_HTTP_AUTH) << "gss_indicate_mechs failed:" << gssError(major_status, minor_status); 0853 } else { 0854 for (uint i = 0; i < mech_set->count; i++) { 0855 tmp_oid = &mech_set->elements[i]; 0856 if (tmp_oid->length == spnego_oid_desc.length && !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) { 0857 // qDebug() << "found SPNEGO mech"; 0858 mech_oid = &spnego_oid_desc; 0859 break; 0860 } 0861 } 0862 gss_release_oid_set(&minor_status, &mech_set); 0863 } 0864 0865 // the service name is "HTTP/f.q.d.n" 0866 QByteArray servicename = "HTTP@"; 0867 servicename += m_resource.host().toLatin1(); 0868 0869 input_token.value = (void *)servicename.data(); 0870 input_token.length = servicename.length() + 1; 0871 0872 major_status = gss_import_name(&minor_status, &input_token, GSS_C_NT_HOSTBASED_SERVICE, &server); 0873 0874 input_token.value = nullptr; 0875 input_token.length = 0; 0876 0877 if (GSS_ERROR(major_status)) { 0878 qCDebug(KIO_HTTP_AUTH) << "gss_import_name failed:" << gssError(major_status, minor_status); 0879 m_isError = true; 0880 return; 0881 } 0882 0883 OM_uint32 req_flags; 0884 if (m_config && m_config->readEntry("DelegateCredentialsOn", false)) { 0885 req_flags = GSS_C_DELEG_FLAG; 0886 } else { 0887 req_flags = 0; 0888 } 0889 0890 // GSSAPI knows how to get the credentials its own way, so don't ask for any 0891 major_status = gss_init_sec_context(&minor_status, 0892 GSS_C_NO_CREDENTIAL, 0893 &ctx, 0894 server, 0895 mech_oid, 0896 req_flags, 0897 GSS_C_INDEFINITE, 0898 GSS_C_NO_CHANNEL_BINDINGS, 0899 GSS_C_NO_BUFFER, 0900 nullptr, 0901 &output_token, 0902 nullptr, 0903 nullptr); 0904 0905 if (GSS_ERROR(major_status) || (output_token.length == 0)) { 0906 qCDebug(KIO_HTTP_AUTH) << "gss_init_sec_context failed:" << gssError(major_status, minor_status); 0907 gss_release_name(&minor_status, &server); 0908 if (ctx != GSS_C_NO_CONTEXT) { 0909 gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); 0910 ctx = GSS_C_NO_CONTEXT; 0911 } 0912 m_isError = true; 0913 return; 0914 } 0915 0916 m_headerFragment = "Negotiate "; 0917 m_headerFragment += QByteArray::fromRawData(static_cast<const char *>(output_token.value), output_token.length).toBase64(); 0918 m_headerFragment += "\r\n"; 0919 0920 // free everything 0921 gss_release_name(&minor_status, &server); 0922 if (ctx != GSS_C_NO_CONTEXT) { 0923 gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); 0924 ctx = GSS_C_NO_CONTEXT; 0925 } 0926 gss_release_buffer(&minor_status, &output_token); 0927 } 0928 0929 #endif // HAVE_LIBGSSAPI