• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • 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 
00021 #include "httpauthentication.h"
00022 
00023 
00024 // keys on even indexes, values on odd indexes. Reduces code expansion for the templated
00025 // alternatives.
00026 static QList<QByteArray> parseChallenge(const QByteArray &ba, QByteArray *scheme)
00027 {
00028     QList<QByteArray> values;
00029     const int len = ba.count();
00030     const char *b = ba.constData();
00031     int start = 0;
00032     int end = 0;
00033 
00034     // parse scheme
00035     while (start < len && (b[start] == ' ' || b[start] == '\t')) {
00036         start++;
00037     }
00038     end = start;
00039     while (end < len && (b[end] != ' ' && b[end] != '\t')) {
00040         end++;
00041     }
00042     if (scheme) {
00043         *scheme = QByteArray(b + start, end - start);
00044     }
00045 
00046     while (end < len) {
00047         start = end;
00048         // parse key
00049         while (start < len && (b[start] == ' ' || b[start] == '\t')) {
00050             start++;
00051         }
00052         end = start;
00053         while (end < len && b[end] != '=') {
00054             end++;
00055         }
00056         values.append(QByteArray(b + start, end - start));
00057         if (end == len) {
00058             break;
00059         }
00060 
00061         // parse value
00062         start = end + 1;    //skip '='
00063         while (start < len && (b[start] == ' ' || b[start] == '\t')) {
00064             start++;
00065         }
00066         if (start + 1 < len && b[start] == '"') {
00067             end = ++start;
00068             //quoted string
00069             while (end < len && b[end] != '"') {
00070                 end++;
00071             }
00072             values.append(QByteArray(b + start, end - start));
00073             //the quoted string has ended, but only a comma ends a key-value pair
00074             while (end < len && b[end] != ',') {
00075                 end++;
00076             }
00077         } else {
00078             end = start;
00079             //unquoted string
00080             while (end < len && b[end] != ',') {
00081                 end++;
00082             }
00083             values.append(QByteArray(b + start, end - start));
00084         }
00085         // end may point beyond the buffer already here
00086         end++;  // skip comma
00087     }
00088     // ensure every key has a value
00089     if (values.count() % 2) {
00090         values.removeLast();
00091     }
00092     return values;
00093 }
00094 
00095 
00096 static QByteArray valueForKey(const QList<QByteArray> &ba, const QByteArray &key)
00097 {
00098     for (int i = 0; i + 1 < ba.count(); i += 2) {
00099         if (ba[i] == key) {
00100             return ba[i + 1];
00101         }
00102     }
00103     return QByteArray();
00104 }
00105 
00106 
00107 QByteArray KAbstractHttpAuthentication::bestOffer(const QList<QByteArray> &offers)
00108 {
00109     // choose the most secure auth scheme offered
00110     QByteArray digestOffer;
00111     QByteArray ntlmOffer;
00112     QByteArray basicOffer;
00113     foreach (const QByteArray &offer, offers) {
00114         QByteArray scheme = offer.mid(0, 10).toLower();
00115         if (scheme.startsWith("digest")) {
00116             digestOffer = offer;
00117         } else if (scheme.startsWith("ntlm")) {
00118             ntlmOffer = offer;
00119         } else if (scheme.startsWith("basic")) {
00120             basicOffer = offer;
00121         }
00122     }
00123 
00124     if (!digestOffer.isEmpty()) {
00125         return digestOffer;
00126     } else if (!ntlmOffer.isEmpty()) {
00127         return ntlmOffer;
00128     }
00129     return basicOffer;  //empty or not...
00130 }
00131 
00132 
00133 KAbstractHttpAuthentication *KAbstractHttpAuthentication::newAuth(const QByteArray &offer)
00134 {
00135     QByteArray scheme = offer.mid(0, 10).toLower();
00136     if (scheme.startsWith("digest")) {
00137         return new KHttpDigestAuthentication();
00138     } else if (scheme.startsWith("ntlm")) {
00139         return new KHttpNtlmAuthentication();
00140     } else if (scheme.startsWith("basic")) {
00141         return new KHttpBasicAuthentication();
00142     }
00143     return 0;
00144 }
00145 
00146 
00147 void KAbstractHttpAuthentication::reset()
00148 {
00149     m_scheme.clear();
00150     m_challenge.clear();
00151     m_resource.clear();
00152     m_httpMethod.clear();
00153     m_isError = false;
00154     m_retryWithSameCredentials = false;
00155     m_forceKeepAlive = false;
00156     m_forceDisconnect = false;
00157     m_headerFragment.clear();
00158     m_username.clear();
00159     m_password.clear();
00160 }
00161 
00162 
00163 void KAbstractHttpAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
00164                                                const QByteArray &httpMethod)
00165 {
00166     reset();
00167     m_challenge = parseChallenge(c, &m_scheme);
00168     Q_ASSERT(m_scheme.toLower() == scheme().toLower());
00169     m_resource = resource;
00170     m_httpMethod = httpMethod;
00171 }
00172 
00173 
00174 QString KAbstractHttpAuthentication::realm() const
00175 {
00176     QByteArray realm = valueForKey(m_challenge, "realm");
00177     if (KGlobal::locale()->language().contains("ru")) {
00178         //for sites like lib.homelinux.org
00179         return QTextCodec::codecForName("CP1251")->toUnicode(realm);
00180     }
00181     return QString::fromLatin1(realm);
00182 }
00183 
00184 
00185 void KAbstractHttpAuthentication::authInfoBoilerplate(KIO::AuthInfo *a) const
00186 {
00187     a->verifyPath = true;  //### research this
00188     a->url = m_resource;
00189     a->realmValue = realm();
00190     a->username = m_username;
00191     a->password = m_password;
00192 }
00193 
00194 
00195 void KAbstractHttpAuthentication::generateResponseCommon(const QString &user, const QString &password)
00196 {
00197     if (user.isEmpty() || password.isEmpty() || m_scheme.isEmpty() || m_httpMethod.isEmpty()) {
00198         m_isError = true;
00199         return;
00200     }
00201 
00202     m_username = user;
00203     m_password = password;
00204     m_isError = false;
00205     m_retryWithSameCredentials = false;
00206     m_forceKeepAlive = false;
00207     m_forceDisconnect = false;
00208 }
00209 
00210 
00211 QByteArray KHttpBasicAuthentication::scheme() const
00212 {
00213     return "Basic";
00214 }
00215 
00216 
00217 void KHttpBasicAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
00218 {
00219     authInfoBoilerplate(ai);
00220 }
00221 
00222 
00223 void KHttpBasicAuthentication::generateResponse(const QString &user, const QString &password)
00224 {
00225     generateResponseCommon(user, password);
00226     if (m_isError) {
00227         return;
00228     }
00229 
00230     m_headerFragment = "Basic ";
00231     m_headerFragment += KCodecs::base64Encode(user.toLatin1() + ':' + password.toLatin1());
00232     m_headerFragment += "\r\n";
00233     return;
00234 }
00235 
00236 
00237 QByteArray KHttpDigestAuthentication::scheme() const
00238 {
00239     return "Digest";
00240 }
00241 
00242 
00243 void KHttpDigestAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
00244 {
00245     authInfoBoilerplate(ai);
00246 }
00247 
00248 
00249 struct DigestAuthInfo
00250 {
00251     QByteArray nc;
00252     QByteArray qop;
00253     QByteArray realm;
00254     QByteArray nonce;
00255     QByteArray method;
00256     QByteArray cnonce;
00257     QByteArray username;
00258     QByteArray password;
00259     KUrl::List digestURIs;
00260     QByteArray algorithm;
00261     QByteArray entityBody;
00262 };
00263 
00264 
00265 //calculateResponse() from the original HTTPProtocol
00266 static QByteArray calculateResponse(const DigestAuthInfo &info, const KUrl &resource)
00267 {
00268   kDebug(7113) << info.nc << info.qop << info.realm << info.nonce << info.method << info.cnonce
00269                << info.username << info.password << info.algorithm << info.entityBody;
00270   foreach (const KUrl &u, info.digestURIs) {
00271       kDebug(7113) << u;
00272   }
00273   kDebug(7113);
00274   KMD5 md;
00275   QByteArray HA1;
00276   QByteArray HA2;
00277 
00278   // Calculate H(A1)
00279   QByteArray authStr = info.username;
00280   authStr += ':';
00281   authStr += info.realm;
00282   authStr += ':';
00283   authStr += info.password;
00284   md.update( authStr );
00285 
00286   if ( info.algorithm.toLower() == "md5-sess" )
00287   {
00288     authStr = md.hexDigest();
00289     authStr += ':';
00290     authStr += info.nonce;
00291     authStr += ':';
00292     authStr += info.cnonce;
00293     md.reset();
00294     md.update( authStr );
00295   }
00296   HA1 = md.hexDigest();
00297 
00298   kDebug(7113) << "A1 => " << HA1;
00299 
00300   // Calcualte H(A2)
00301   authStr = info.method;
00302   authStr += ':';
00303   authStr += resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath).toLatin1();
00304   if ( info.qop == "auth-int" )
00305   {
00306     authStr += ':';
00307     authStr += info.entityBody;
00308   }
00309   md.reset();
00310   md.update( authStr );
00311   HA2 = md.hexDigest();
00312 
00313   kDebug(7113) << "A2 => " << HA2;
00314 
00315   // Calcualte the response.
00316   authStr = HA1;
00317   authStr += ':';
00318   authStr += info.nonce;
00319   authStr += ':';
00320   if ( !info.qop.isEmpty() )
00321   {
00322     authStr += info.nc;
00323     authStr += ':';
00324     authStr += info.cnonce;
00325     authStr += ':';
00326     authStr += info.qop;
00327     authStr += ':';
00328   }
00329   authStr += HA2;
00330   md.reset();
00331   md.update( authStr );
00332 
00333   QByteArray Response = md.hexDigest();
00334 
00335   kDebug(7113) << "Response => " << Response;
00336   return Response;
00337 }
00338 
00339 
00340 void KHttpDigestAuthentication::generateResponse(const QString &user, const QString &password)
00341 {
00342     generateResponseCommon(user, password);
00343     if (m_isError) {
00344         return;
00345     }
00346 
00347 // magic starts here (this part is slightly modified from the original in HTTPProtocol)
00348 
00349     DigestAuthInfo info;
00350 
00351     info.username = user.toLatin1();        //### charset breakage
00352     info.password = password.toLatin1();    //###
00353 
00354     // info.entityBody = p;  // FIXME: send digest of data for POST action ??
00355     info.realm = "";
00356     info.nonce = "";
00357     info.qop = "";
00358 
00359     // cnonce is recommended to contain about 64 bits of entropy
00360     info.cnonce = KRandom::randomString(16).toLatin1();
00361 
00362     // HACK: Should be fixed according to RFC 2617 section 3.2.2
00363     info.nc = "00000001";
00364 
00365     // Set the method used...
00366     info.method = m_httpMethod;
00367 
00368     // Parse the Digest response....
00369     if (valueForKey(m_challenge, "stale").toLower() == "true") {
00370         m_retryWithSameCredentials = true;
00371     }
00372 
00373     info.realm = valueForKey(m_challenge, "realm");
00374 
00375     info.algorithm = valueForKey(m_challenge, "algorith");
00376     if (info.algorithm.isEmpty()) {
00377         info.algorithm = valueForKey(m_challenge, "algorithm");
00378     }
00379     if (info.algorithm.isEmpty()) {
00380         info.algorithm = "MD5";
00381     }
00382 
00383     foreach (const QByteArray &path, valueForKey(m_challenge, "domain").split(' ')) {
00384         KUrl u(m_resource, QString::fromLatin1(path));
00385         if (u.isValid()) {
00386             info.digestURIs.append(u);
00387         }
00388     }
00389 
00390     info.nonce = valueForKey(m_challenge, "nonce");
00391     QByteArray opaque = valueForKey(m_challenge, "opaque");
00392     info.qop = valueForKey(m_challenge, "qop");
00393 
00394     if (info.realm.isEmpty() || info.nonce.isEmpty()) {
00395         // ### proper error return
00396         m_isError = true;
00397         return;
00398     }
00399 
00400   // If the "domain" attribute was not specified and the current response code
00401   // is authentication needed, add the current request url to the list over which
00402   // this credential can be automatically applied.
00403   if (info.digestURIs.isEmpty() /*###&& (m_request.responseCode == 401 || m_request.responseCode == 407)*/)
00404     info.digestURIs.append (m_resource);
00405   else
00406   {
00407     // Verify whether or not we should send a cached credential to the
00408     // server based on the stored "domain" attribute...
00409     bool send = true;
00410 
00411     // Determine the path of the request url...
00412     QString requestPath = m_resource.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
00413     if (requestPath.isEmpty())
00414       requestPath = "/";
00415 
00416     foreach (const KUrl &u, info.digestURIs)
00417     {
00418       send &= (m_resource.protocol().toLower() == u.protocol().toLower());
00419       send &= (m_resource.host().toLower() == u.host().toLower());
00420 
00421       if (m_resource.port() > 0 && u.port() > 0)
00422         send &= (m_resource.port() == u.port());
00423 
00424       QString digestPath = u.directory (KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
00425       if (digestPath.isEmpty())
00426         digestPath = "/";
00427 
00428       send &= (requestPath.startsWith(digestPath));
00429 
00430       if (send)
00431         break;
00432     }
00433 
00434     kDebug(7113) << "passed digest authentication credential test: " << send;
00435 
00436     if (!send) {
00437         m_isError = true;
00438         return;
00439     }
00440   }
00441 
00442   kDebug(7113) << "RESULT OF PARSING:";
00443   kDebug(7113) << "  algorithm: " << info.algorithm;
00444   kDebug(7113) << "  realm:     " << info.realm;
00445   kDebug(7113) << "  nonce:     " << info.nonce;
00446   kDebug(7113) << "  opaque:    " << opaque;
00447   kDebug(7113) << "  qop:       " << info.qop;
00448 
00449   // Calculate the response...
00450   QByteArray Response = calculateResponse(info, m_resource);
00451 
00452   QString auth = "Digest username=\"";
00453   auth += info.username;
00454 
00455   auth += "\", realm=\"";
00456   auth += info.realm;
00457   auth += "\"";
00458 
00459   auth += ", nonce=\"";
00460   auth += info.nonce;
00461 
00462   auth += "\", uri=\"";
00463   auth += m_resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath);
00464 
00465   if (!info.algorithm.isEmpty()) {
00466     auth += "\", algorithm=\"";
00467     auth += info.algorithm;
00468     auth +="\"";
00469   }
00470 
00471   if ( !info.qop.isEmpty() )
00472   {
00473     auth += ", qop=\"";
00474     auth += info.qop;
00475     auth += "\", cnonce=\"";
00476     auth += info.cnonce;
00477     auth += "\", nc=";
00478     auth += info.nc;
00479   }
00480 
00481   auth += ", response=\"";
00482   auth += Response;
00483   if ( !opaque.isEmpty() )
00484   {
00485     auth += "\", opaque=\"";
00486     auth += opaque;
00487   }
00488   auth += "\"\r\n";
00489 
00490 // magic ends here
00491     // note that auth already contains \r\n
00492     m_headerFragment = auth;
00493     return;
00494 }
00495 
00496 
00497 QByteArray KHttpNtlmAuthentication::scheme() const
00498 {
00499     return "NTLM";
00500 }
00501 
00502 
00503 void KHttpNtlmAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
00504 {
00505     authInfoBoilerplate(ai);
00506     // Every auth scheme is supposed to supply a realm according to the RFCs. Of course this doesn't
00507     // prevent Microsoft from not doing it... Dummy value!
00508     ai->realmValue = "NTLM";
00509 }
00510 
00511 
00512 void KHttpNtlmAuthentication::generateResponse(const QString &_user, const QString &password)
00513 {
00514     generateResponseCommon(_user, password);
00515     if (m_isError) {
00516         return;
00517     }
00518 
00519     QByteArray buf;
00520     
00521     // ### double check if forceDisconnect and forceKeepAlive are a) necessary b) set correctly here
00522     if (m_challenge.isEmpty()) {
00523         // first, send type 1 message (with empty domain, workstation..., but it still works)
00524         m_forceDisconnect = true;
00525         KNTLM::getNegotiate(buf);
00526     } else {
00527         // we've (hopefully) received a valid type 2 message: send type 3 message as last step
00528         QString domain;
00529         QString user = _user;
00530         if (user.contains('\\')) {
00531             domain = user.section('\\', 0, 0);
00532             user = user.section('\\', 1);
00533         }
00534 
00535         m_retryWithSameCredentials = true;
00536         m_forceKeepAlive = true;
00537         QByteArray challenge;
00538         KCodecs::base64Decode(m_challenge[0], challenge);
00539         KNTLM::getAuth(buf, challenge, user, password, domain, QHostInfo::localHostName());
00540     }
00541 
00542     m_headerFragment += KCodecs::base64Encode(buf);
00543     m_headerFragment += "\r\n";
00544 
00545     return;
00546 }

KIOSlave

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
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