00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "httpauthentication.h"
00022
00023
00024
00025
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
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
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
00062 start = end + 1;
00063 while (start < len && (b[start] == ' ' || b[start] == '\t')) {
00064 start++;
00065 }
00066 if (start + 1 < len && b[start] == '"') {
00067 end = ++start;
00068
00069 while (end < len && b[end] != '"') {
00070 end++;
00071 }
00072 values.append(QByteArray(b + start, end - start));
00073
00074 while (end < len && b[end] != ',') {
00075 end++;
00076 }
00077 } else {
00078 end = start;
00079
00080 while (end < len && b[end] != ',') {
00081 end++;
00082 }
00083 values.append(QByteArray(b + start, end - start));
00084 }
00085
00086 end++;
00087 }
00088
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
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;
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
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;
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
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
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
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
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
00348
00349 DigestAuthInfo info;
00350
00351 info.username = user.toLatin1();
00352 info.password = password.toLatin1();
00353
00354
00355 info.realm = "";
00356 info.nonce = "";
00357 info.qop = "";
00358
00359
00360 info.cnonce = KRandom::randomString(16).toLatin1();
00361
00362
00363 info.nc = "00000001";
00364
00365
00366 info.method = m_httpMethod;
00367
00368
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
00396 m_isError = true;
00397 return;
00398 }
00399
00400
00401
00402
00403 if (info.digestURIs.isEmpty() )
00404 info.digestURIs.append (m_resource);
00405 else
00406 {
00407
00408
00409 bool send = true;
00410
00411
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
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
00491
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
00507
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
00522 if (m_challenge.isEmpty()) {
00523
00524 m_forceDisconnect = true;
00525 KNTLM::getNegotiate(buf);
00526 } else {
00527
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 }