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 negotiateOffer;
00111 QByteArray digestOffer;
00112 QByteArray ntlmOffer;
00113 QByteArray basicOffer;
00114 foreach (const QByteArray &offer, offers) {
00115 QByteArray scheme = offer.mid(0, 10).toLower();
00116 #ifdef HAVE_LIBGSSAPI
00117 if (scheme.startsWith("negotiate")) {
00118 negotiateOffer = offer;
00119 } else
00120 #endif
00121 if (scheme.startsWith("digest")) {
00122 digestOffer = offer;
00123 } else if (scheme.startsWith("ntlm")) {
00124 ntlmOffer = offer;
00125 } else if (scheme.startsWith("basic")) {
00126 basicOffer = offer;
00127 }
00128 }
00129
00130 if (!negotiateOffer.isEmpty()) {
00131 return negotiateOffer;
00132 } else if (!digestOffer.isEmpty()) {
00133 return digestOffer;
00134 } else if (!ntlmOffer.isEmpty()) {
00135 return ntlmOffer;
00136 }
00137 return basicOffer;
00138 }
00139
00140
00141 KAbstractHttpAuthentication *KAbstractHttpAuthentication::newAuth(const QByteArray &offer)
00142 {
00143 QByteArray scheme = offer.mid(0, 10).toLower();
00144 #ifdef HAVE_LIBGSSAPI
00145 if (scheme.startsWith("negotiate")) {
00146 return new KHttpNegotiateAuthentication();
00147 } else
00148 #endif
00149 if (scheme.startsWith("digest")) {
00150 return new KHttpDigestAuthentication();
00151 } else if (scheme.startsWith("ntlm")) {
00152 return new KHttpNtlmAuthentication();
00153 } else if (scheme.startsWith("basic")) {
00154 return new KHttpBasicAuthentication();
00155 }
00156 return 0;
00157 }
00158
00159
00160 void KAbstractHttpAuthentication::reset()
00161 {
00162 m_scheme.clear();
00163 m_challenge.clear();
00164 m_resource.clear();
00165 m_httpMethod.clear();
00166 m_isError = false;
00167 m_needCredentials = true;
00168 m_forceKeepAlive = false;
00169 m_forceDisconnect = false;
00170 m_headerFragment.clear();
00171 m_username.clear();
00172 m_password.clear();
00173 }
00174
00175
00176 void KAbstractHttpAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
00177 const QByteArray &httpMethod)
00178 {
00179 reset();
00180 m_challenge = parseChallenge(c, &m_scheme);
00181 Q_ASSERT(m_scheme.toLower() == scheme().toLower());
00182 m_resource = resource;
00183 m_httpMethod = httpMethod;
00184 }
00185
00186
00187 QString KAbstractHttpAuthentication::realm() const
00188 {
00189 QByteArray realm = valueForKey(m_challenge, "realm");
00190 if (KGlobal::locale()->language().contains("ru")) {
00191
00192 return QTextCodec::codecForName("CP1251")->toUnicode(realm);
00193 }
00194 return QString::fromLatin1(realm);
00195 }
00196
00197
00198 void KAbstractHttpAuthentication::authInfoBoilerplate(KIO::AuthInfo *a) const
00199 {
00200 a->verifyPath = true;
00201 a->url = m_resource;
00202 a->realmValue = realm();
00203 a->username = m_username;
00204 a->password = m_password;
00205 }
00206
00207
00208 void KAbstractHttpAuthentication::generateResponseCommon(const QString &user, const QString &password)
00209 {
00210 if ((m_needCredentials && (user.isEmpty() || password.isEmpty())) ||
00211 m_scheme.isEmpty() || m_httpMethod.isEmpty()) {
00212 m_isError = true;
00213 return;
00214 }
00215
00216 if (m_needCredentials) {
00217 m_username = user;
00218 m_password = password;
00219 }
00220 m_isError = false;
00221
00222 m_needCredentials = true;
00223 m_forceKeepAlive = false;
00224 m_forceDisconnect = false;
00225 }
00226
00227
00228 QByteArray KHttpBasicAuthentication::scheme() const
00229 {
00230 return "Basic";
00231 }
00232
00233
00234 void KHttpBasicAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
00235 {
00236 authInfoBoilerplate(ai);
00237 }
00238
00239
00240 void KHttpBasicAuthentication::generateResponse(const QString &user, const QString &password)
00241 {
00242 generateResponseCommon(user, password);
00243 if (m_isError) {
00244 return;
00245 }
00246
00247 m_headerFragment = "Basic ";
00248 m_headerFragment += KCodecs::base64Encode(m_username.toLatin1() + ':' + m_password.toLatin1());
00249 m_headerFragment += "\r\n";
00250 return;
00251 }
00252
00253
00254 QByteArray KHttpDigestAuthentication::scheme() const
00255 {
00256 return "Digest";
00257 }
00258
00259
00260 void KHttpDigestAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
00261 const QByteArray &httpMethod)
00262 {
00263 QString oldUsername;
00264 QString oldPassword;
00265 if (valueForKey(m_challenge, "stale").toLower() == "true") {
00266
00267
00268 oldUsername = m_username;
00269 oldPassword = m_password;
00270 }
00271 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod);
00272 if (!oldUsername.isEmpty() && !oldPassword.isEmpty()) {
00273
00274 m_needCredentials = false;
00275 m_username = oldUsername;
00276 m_password = oldPassword;
00277 }
00278 }
00279
00280
00281 void KHttpDigestAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
00282 {
00283 authInfoBoilerplate(ai);
00284 }
00285
00286
00287 struct DigestAuthInfo
00288 {
00289 QByteArray nc;
00290 QByteArray qop;
00291 QByteArray realm;
00292 QByteArray nonce;
00293 QByteArray method;
00294 QByteArray cnonce;
00295 QByteArray username;
00296 QByteArray password;
00297 KUrl::List digestURIs;
00298 QByteArray algorithm;
00299 QByteArray entityBody;
00300 };
00301
00302
00303
00304 static QByteArray calculateResponse(const DigestAuthInfo &info, const KUrl &resource)
00305 {
00306 kDebug(7113) << info.nc << info.qop << info.realm << info.nonce << info.method << info.cnonce
00307 << info.username << info.password << info.algorithm << info.entityBody;
00308 foreach (const KUrl &u, info.digestURIs) {
00309 kDebug(7113) << u;
00310 }
00311 kDebug(7113);
00312 KMD5 md;
00313 QByteArray HA1;
00314 QByteArray HA2;
00315
00316
00317 QByteArray authStr = info.username;
00318 authStr += ':';
00319 authStr += info.realm;
00320 authStr += ':';
00321 authStr += info.password;
00322 md.update( authStr );
00323
00324 if ( info.algorithm.toLower() == "md5-sess" )
00325 {
00326 authStr = md.hexDigest();
00327 authStr += ':';
00328 authStr += info.nonce;
00329 authStr += ':';
00330 authStr += info.cnonce;
00331 md.reset();
00332 md.update( authStr );
00333 }
00334 HA1 = md.hexDigest();
00335
00336 kDebug(7113) << "A1 => " << HA1;
00337
00338
00339 authStr = info.method;
00340 authStr += ':';
00341 authStr += resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath).toLatin1();
00342 if ( info.qop == "auth-int" )
00343 {
00344 authStr += ':';
00345 authStr += info.entityBody;
00346 }
00347 md.reset();
00348 md.update( authStr );
00349 HA2 = md.hexDigest();
00350
00351 kDebug(7113) << "A2 => " << HA2;
00352
00353
00354 authStr = HA1;
00355 authStr += ':';
00356 authStr += info.nonce;
00357 authStr += ':';
00358 if ( !info.qop.isEmpty() )
00359 {
00360 authStr += info.nc;
00361 authStr += ':';
00362 authStr += info.cnonce;
00363 authStr += ':';
00364 authStr += info.qop;
00365 authStr += ':';
00366 }
00367 authStr += HA2;
00368 md.reset();
00369 md.update( authStr );
00370
00371 QByteArray Response = md.hexDigest();
00372
00373 kDebug(7113) << "Response => " << Response;
00374 return Response;
00375 }
00376
00377
00378 void KHttpDigestAuthentication::generateResponse(const QString &user, const QString &password)
00379 {
00380 generateResponseCommon(user, password);
00381 if (m_isError) {
00382 return;
00383 }
00384
00385
00386
00387 DigestAuthInfo info;
00388
00389 info.username = m_username.toLatin1();
00390 info.password = m_password.toLatin1();
00391
00392
00393 info.realm = "";
00394 info.nonce = "";
00395 info.qop = "";
00396
00397
00398 info.cnonce = KRandom::randomString(16).toLatin1();
00399
00400
00401 info.nc = "00000001";
00402
00403
00404 info.method = m_httpMethod;
00405
00406
00407 info.realm = valueForKey(m_challenge, "realm");
00408
00409 info.algorithm = valueForKey(m_challenge, "algorith");
00410 if (info.algorithm.isEmpty()) {
00411 info.algorithm = valueForKey(m_challenge, "algorithm");
00412 }
00413 if (info.algorithm.isEmpty()) {
00414 info.algorithm = "MD5";
00415 }
00416
00417 foreach (const QByteArray &path, valueForKey(m_challenge, "domain").split(' ')) {
00418 KUrl u(m_resource, QString::fromLatin1(path));
00419 if (u.isValid()) {
00420 info.digestURIs.append(u);
00421 }
00422 }
00423
00424 info.nonce = valueForKey(m_challenge, "nonce");
00425 QByteArray opaque = valueForKey(m_challenge, "opaque");
00426 info.qop = valueForKey(m_challenge, "qop");
00427
00428 if (info.realm.isEmpty() || info.nonce.isEmpty()) {
00429
00430 m_isError = true;
00431 return;
00432 }
00433
00434
00435
00436
00437 if (info.digestURIs.isEmpty() )
00438 info.digestURIs.append (m_resource);
00439 else
00440 {
00441
00442
00443 bool send = true;
00444
00445
00446 QString requestPath = m_resource.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
00447 if (requestPath.isEmpty())
00448 requestPath = "/";
00449
00450 foreach (const KUrl &u, info.digestURIs)
00451 {
00452 send &= (m_resource.protocol().toLower() == u.protocol().toLower());
00453 send &= (m_resource.host().toLower() == u.host().toLower());
00454
00455 if (m_resource.port() > 0 && u.port() > 0)
00456 send &= (m_resource.port() == u.port());
00457
00458 QString digestPath = u.directory (KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
00459 if (digestPath.isEmpty())
00460 digestPath = "/";
00461
00462 send &= (requestPath.startsWith(digestPath));
00463
00464 if (send)
00465 break;
00466 }
00467
00468 kDebug(7113) << "passed digest authentication credential test: " << send;
00469
00470 if (!send) {
00471 m_isError = true;
00472 return;
00473 }
00474 }
00475
00476 kDebug(7113) << "RESULT OF PARSING:";
00477 kDebug(7113) << " algorithm: " << info.algorithm;
00478 kDebug(7113) << " realm: " << info.realm;
00479 kDebug(7113) << " nonce: " << info.nonce;
00480 kDebug(7113) << " opaque: " << opaque;
00481 kDebug(7113) << " qop: " << info.qop;
00482
00483
00484 QByteArray Response = calculateResponse(info, m_resource);
00485
00486 QString auth = "Digest username=\"";
00487 auth += info.username;
00488
00489 auth += "\", realm=\"";
00490 auth += info.realm;
00491 auth += "\"";
00492
00493 auth += ", nonce=\"";
00494 auth += info.nonce;
00495
00496 auth += "\", uri=\"";
00497 auth += m_resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath);
00498
00499 if (!info.algorithm.isEmpty()) {
00500 auth += "\", algorithm=\"";
00501 auth += info.algorithm;
00502 auth +="\"";
00503 }
00504
00505 if ( !info.qop.isEmpty() )
00506 {
00507 auth += ", qop=\"";
00508 auth += info.qop;
00509 auth += "\", cnonce=\"";
00510 auth += info.cnonce;
00511 auth += "\", nc=";
00512 auth += info.nc;
00513 }
00514
00515 auth += ", response=\"";
00516 auth += Response;
00517 if ( !opaque.isEmpty() )
00518 {
00519 auth += "\", opaque=\"";
00520 auth += opaque;
00521 }
00522 auth += "\"\r\n";
00523
00524
00525
00526 m_headerFragment = auth;
00527 return;
00528 }
00529
00530
00531 QByteArray KHttpNtlmAuthentication::scheme() const
00532 {
00533 return "NTLM";
00534 }
00535
00536
00537 void KHttpNtlmAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
00538 const QByteArray &httpMethod)
00539 {
00540 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod);
00541 if (m_challenge.isEmpty()) {
00542
00543
00544 m_needCredentials = false;
00545 }
00546 }
00547
00548
00549 void KHttpNtlmAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
00550 {
00551 authInfoBoilerplate(ai);
00552
00553
00554
00555 ai->realmValue = "NTLM";
00556 }
00557
00558
00559 void KHttpNtlmAuthentication::generateResponse(const QString &_user, const QString &password)
00560 {
00561 generateResponseCommon(_user, password);
00562 if (m_isError) {
00563 return;
00564 }
00565
00566 QByteArray buf;
00567
00568 if (m_challenge.isEmpty()) {
00569
00570 m_forceDisconnect = true;
00571 KNTLM::getNegotiate(buf);
00572 } else {
00573
00574 QString domain;
00575 QString user = m_username;
00576 if (user.contains('\\')) {
00577 domain = user.section('\\', 0, 0);
00578 user = user.section('\\', 1);
00579 }
00580
00581 m_forceKeepAlive = true;
00582 QByteArray challenge;
00583 KCodecs::base64Decode(m_challenge[0], challenge);
00584 KNTLM::getAuth(buf, challenge, user, password, domain, QHostInfo::localHostName());
00585 }
00586
00587 m_headerFragment = "NTLM ";
00588 m_headerFragment += KCodecs::base64Encode(buf);
00589 m_headerFragment += "\r\n";
00590
00591 return;
00592 }
00593
00594
00596 #ifdef HAVE_LIBGSSAPI
00597
00598
00599 static QByteArray gssError(int major_status, int minor_status)
00600 {
00601 OM_uint32 new_status;
00602 OM_uint32 msg_ctx = 0;
00603 gss_buffer_desc major_string;
00604 gss_buffer_desc minor_string;
00605 OM_uint32 ret;
00606 QByteArray errorstr;
00607
00608 do {
00609 ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string);
00610 errorstr += (const char *)major_string.value;
00611 errorstr += ' ';
00612 ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string);
00613 errorstr += (const char *)minor_string.value;
00614 errorstr += ' ';
00615 } while (!GSS_ERROR(ret) && msg_ctx != 0);
00616
00617 return errorstr;
00618 }
00619
00620
00621 QByteArray KHttpNegotiateAuthentication::scheme() const
00622 {
00623 return "Negotiate";
00624 }
00625
00626
00627 void KHttpNegotiateAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
00628 const QByteArray &httpMethod)
00629 {
00630 KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod);
00631
00632 m_needCredentials = false;
00633 }
00634
00635
00636 void KHttpNegotiateAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
00637 {
00638 authInfoBoilerplate(ai);
00639
00640 ai->realmValue = "Negotiate";
00641 }
00642
00643
00644 void KHttpNegotiateAuthentication::generateResponse(const QString &user, const QString &password)
00645 {
00646 generateResponseCommon(user, password);
00647 if (m_isError) {
00648 return;
00649 }
00650
00651 OM_uint32 major_status, minor_status;
00652 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
00653 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
00654 gss_name_t server;
00655 gss_ctx_id_t ctx;
00656 gss_OID mech_oid;
00657 static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
00658 static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"};
00659 gss_OID_set mech_set;
00660 gss_OID tmp_oid;
00661
00662 ctx = GSS_C_NO_CONTEXT;
00663 mech_oid = &krb5_oid_desc;
00664
00665
00666 major_status = gss_indicate_mechs(&minor_status, &mech_set);
00667 if (GSS_ERROR(major_status)) {
00668 kDebug(7113) << "gss_indicate_mechs failed: " << gssError(major_status, minor_status);
00669 } else {
00670 for (uint i = 0; i < mech_set->count; i++) {
00671 tmp_oid = &mech_set->elements[i];
00672 if (tmp_oid->length == spnego_oid_desc.length &&
00673 !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) {
00674 kDebug(7113) << "found SPNEGO mech";
00675 mech_oid = &spnego_oid_desc;
00676 break;
00677 }
00678 }
00679 gss_release_oid_set(&minor_status, &mech_set);
00680 }
00681
00682
00683 QByteArray servicename = "HTTP@";
00684 servicename += m_resource.host().toAscii();
00685
00686 input_token.value = (void *)servicename.data();
00687 input_token.length = servicename.length() + 1;
00688
00689 major_status = gss_import_name(&minor_status, &input_token,
00690 GSS_C_NT_HOSTBASED_SERVICE, &server);
00691
00692 input_token.value = NULL;
00693 input_token.length = 0;
00694
00695 if (GSS_ERROR(major_status)) {
00696 kDebug(7113) << "gss_import_name failed: " << gssError(major_status, minor_status);
00697 m_isError = true;
00698 return;
00699 }
00700
00701 OM_uint32 req_flags = 0;
00702
00703 major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
00704 &ctx, server, mech_oid,
00705 req_flags, GSS_C_INDEFINITE,
00706 GSS_C_NO_CHANNEL_BINDINGS,
00707 GSS_C_NO_BUFFER, NULL, &output_token,
00708 NULL, NULL);
00709
00710 if (GSS_ERROR(major_status) || (output_token.length == 0)) {
00711 kDebug(7113) << "gss_init_sec_context failed: " << gssError(major_status, minor_status);
00712 gss_release_name(&minor_status, &server);
00713 if (ctx != GSS_C_NO_CONTEXT) {
00714 gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
00715 ctx = GSS_C_NO_CONTEXT;
00716 }
00717 m_isError = true;
00718 return;
00719 }
00720
00721 m_headerFragment = "Negotiate ";
00722 m_headerFragment += QByteArray::fromRawData((const char *)output_token.value,
00723 output_token.length).toBase64();
00724 m_headerFragment += "\r\n";
00725
00726
00727 gss_release_name(&minor_status, &server);
00728 if (ctx != GSS_C_NO_CONTEXT) {
00729 gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
00730 ctx = GSS_C_NO_CONTEXT;
00731 }
00732 gss_release_buffer(&minor_status, &output_token);
00733 }
00734
00735 #endif // HAVE_LIBGSSAPI