• Skip to content
  • Skip to link menu
KDE 4.7 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal