File indexing completed on 2024-05-19 15:15:51

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