diff options
Diffstat (limited to '')
-rw-r--r-- | linden/indra/llmessage/llblowfishcipher.cpp | 69 | ||||
-rw-r--r-- | linden/indra/newview/hippogridmanager.cpp | 177 | ||||
-rw-r--r-- | linden/indra/newview/hippogridmanager.h | 3 | ||||
-rw-r--r-- | linden/indra/newview/llappviewer.cpp | 9 |
4 files changed, 200 insertions, 58 deletions
diff --git a/linden/indra/llmessage/llblowfishcipher.cpp b/linden/indra/llmessage/llblowfishcipher.cpp index f24d103..e9d4a7c 100644 --- a/linden/indra/llmessage/llblowfishcipher.cpp +++ b/linden/indra/llmessage/llblowfishcipher.cpp | |||
@@ -73,13 +73,13 @@ U32 LLBlowfishCipher::encrypt(const U8* src, U32 src_len, U8* dst, U32 dst_len) | |||
73 | unsigned char initial_vector[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; | 73 | unsigned char initial_vector[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; |
74 | EVP_EncryptInit_ex(&context, NULL, NULL, mSecret, initial_vector); | 74 | EVP_EncryptInit_ex(&context, NULL, NULL, mSecret, initial_vector); |
75 | 75 | ||
76 | int blocksize = EVP_CIPHER_CTX_block_size(&context); | 76 | // int blocksize = EVP_CIPHER_CTX_block_size(&context); |
77 | int keylen = EVP_CIPHER_CTX_key_length(&context); | 77 | // int keylen = EVP_CIPHER_CTX_key_length(&context); |
78 | int iv_length = EVP_CIPHER_CTX_iv_length(&context); | 78 | // int iv_length = EVP_CIPHER_CTX_iv_length(&context); |
79 | lldebugs << "LLBlowfishCipher blocksize " << blocksize | 79 | // lldebugs << "LLBlowfishCipher blocksize " << blocksize |
80 | << " keylen " << keylen | 80 | // << " keylen " << keylen |
81 | << " iv_len " << iv_length | 81 | // << " iv_len " << iv_length |
82 | << llendl; | 82 | // << llendl; |
83 | 83 | ||
84 | int output_len = 0; | 84 | int output_len = 0; |
85 | int temp_len = 0; | 85 | int temp_len = 0; |
@@ -113,7 +113,60 @@ ERROR: | |||
113 | // virtual | 113 | // virtual |
114 | U32 LLBlowfishCipher::decrypt(const U8* src, U32 src_len, U8* dst, U32 dst_len) | 114 | U32 LLBlowfishCipher::decrypt(const U8* src, U32 src_len, U8* dst, U32 dst_len) |
115 | { | 115 | { |
116 | llerrs << "LLBlowfishCipher decrypt unsupported" << llendl; | 116 | if (!src || !src_len || !dst || !dst_len) return 0; |
117 | if (src_len > dst_len) return 0; | ||
118 | |||
119 | // OpenSSL uses "cipher contexts" to hold encryption parameters. | ||
120 | EVP_CIPHER_CTX context; | ||
121 | EVP_CIPHER_CTX_init(&context); | ||
122 | |||
123 | // We want a blowfish cyclic block chain cipher, but need to set | ||
124 | // the key length before we pass in a key, so call EncryptInit | ||
125 | // first with NULLs. | ||
126 | EVP_DecryptInit_ex(&context, EVP_bf_cbc(), NULL, NULL, NULL); | ||
127 | EVP_CIPHER_CTX_set_key_length(&context, (int)mSecretSize); | ||
128 | |||
129 | // Complete initialization. Per EVP_EncryptInit man page, the | ||
130 | // cipher pointer must be NULL. Apparently initial_vector must | ||
131 | // be 8 bytes for blowfish, as this is the block size. | ||
132 | unsigned char initial_vector[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; | ||
133 | EVP_DecryptInit_ex(&context, NULL, NULL, mSecret, initial_vector); | ||
134 | |||
135 | // int blocksize = EVP_CIPHER_CTX_block_size(&context); | ||
136 | // int keylen = EVP_CIPHER_CTX_key_length(&context); | ||
137 | // int iv_length = EVP_CIPHER_CTX_iv_length(&context); | ||
138 | // lldebugs << "LLBlowfishCipher blocksize " << blocksize | ||
139 | // << " keylen " << keylen | ||
140 | // << " iv_len " << iv_length | ||
141 | // << llendl; | ||
142 | |||
143 | int output_len = 0; | ||
144 | int temp_len = 0; | ||
145 | if (!EVP_DecryptUpdate(&context, | ||
146 | dst, | ||
147 | &output_len, | ||
148 | src, | ||
149 | src_len)) | ||
150 | { | ||
151 | llwarns << "LLBlowfishCipher::decrypt EVP_DecryptUpdate failure" << llendl; | ||
152 | goto ERROR; | ||
153 | } | ||
154 | |||
155 | // There may be some final data left to decrypt if the input is | ||
156 | // not an exact multiple of the block size. | ||
157 | if (!EVP_DecryptFinal_ex(&context, (unsigned char*)(dst + output_len), &temp_len)) | ||
158 | { | ||
159 | llwarns << "LLBlowfishCipher::decrypt EVP_DecryptFinal failure" << llendl; | ||
160 | goto ERROR; | ||
161 | } | ||
162 | output_len += temp_len; | ||
163 | |||
164 | EVP_CIPHER_CTX_cleanup(&context); | ||
165 | return output_len; | ||
166 | |||
167 | ERROR: | ||
168 | EVP_CIPHER_CTX_cleanup(&context); | ||
169 | return 0; | ||
117 | return 0; | 170 | return 0; |
118 | } | 171 | } |
119 | 172 | ||
diff --git a/linden/indra/newview/hippogridmanager.cpp b/linden/indra/newview/hippogridmanager.cpp index 1dfb9ac..11b144e 100644 --- a/linden/indra/newview/hippogridmanager.cpp +++ b/linden/indra/newview/hippogridmanager.cpp | |||
@@ -49,6 +49,7 @@ | |||
49 | #include "llviewernetwork.h" // gMacAddress | 49 | #include "llviewernetwork.h" // gMacAddress |
50 | #include "llweb.h" | 50 | #include "llweb.h" |
51 | #include "llxorcipher.h" // saved password, MAC address | 51 | #include "llxorcipher.h" // saved password, MAC address |
52 | #include "llblowfishcipher.h" | ||
52 | 53 | ||
53 | #include "hipporestrequest.h" | 54 | #include "hipporestrequest.h" |
54 | #include <boost/algorithm/string.hpp> | 55 | #include <boost/algorithm/string.hpp> |
@@ -87,6 +88,7 @@ HippoGridInfo::HippoGridInfo(const std::string& gridNick) : | |||
87 | mFirstName(LLStringUtil::null), | 88 | mFirstName(LLStringUtil::null), |
88 | mLastName(LLStringUtil::null), | 89 | mLastName(LLStringUtil::null), |
89 | mPasswordAvatar(LLStringUtil::null), | 90 | mPasswordAvatar(LLStringUtil::null), |
91 | mEncryptedPassword(LLStringUtil::null), | ||
90 | mXmlState(XML_VOID), | 92 | mXmlState(XML_VOID), |
91 | mVoiceConnector("SLVoice"), | 93 | mVoiceConnector("SLVoice"), |
92 | mRenderCompat(false), | 94 | mRenderCompat(false), |
@@ -423,12 +425,115 @@ void HippoGridInfo::formatFee(std::string &fee, S32 cost, bool showFree) const | |||
423 | } | 425 | } |
424 | } | 426 | } |
425 | 427 | ||
428 | const S32 HASHED_LENGTH = 32; | ||
429 | |||
430 | void HippoGridInfo::setEncryptedPassword(const std::string& encrypted_password) | ||
431 | { | ||
432 | int i; | ||
433 | LLBlowfishCipher cipher(gMACAddress, 6); | ||
434 | size_t encrypted_size = cipher.requiredEncryptionSpace(HASHED_LENGTH); | ||
435 | |||
436 | if (encrypted_password.empty()) | ||
437 | { | ||
438 | // Check if we have a password hash to encrypt. | ||
439 | if (mPasswordAvatar.empty()) | ||
440 | mEncryptedPassword = ""; | ||
441 | else | ||
442 | { | ||
443 | // In theory, this is used to convert old style Imprudence 1.4 beta 2 and earlier passwords. | ||
444 | // Encipher with MAC address | ||
445 | char out[HASHED_LENGTH * 2 + 1]; | ||
446 | |||
447 | /* indra/llmessage/llmail.cpp says "blowfish-not-supported-on-windows", but we shall see. | ||
448 | #if LL_WINDOWS | ||
449 | LLXORCipher cipherX(gMACAddress, 6); | ||
450 | cipherX.encrypt(mPasswordAvatar.c_str(), HASHED_LENGTH); | ||
451 | #else | ||
452 | */ | ||
453 | U8* encrypted = new U8[encrypted_size]; | ||
454 | U8* password = (U8 *) mPasswordAvatar.c_str(); | ||
455 | |||
456 | cipher.encrypt(password, HASHED_LENGTH, encrypted, HASHED_LENGTH); | ||
457 | for (i = 0; i < HASHED_LENGTH; i++) | ||
458 | { | ||
459 | sprintf(out + i * 2, "%02x", encrypted[i]); | ||
460 | } | ||
461 | out[HASHED_LENGTH * 2]='\0'; | ||
462 | mEncryptedPassword.assign(out); | ||
463 | } | ||
464 | |||
465 | return; | ||
466 | } | ||
467 | |||
468 | if (encrypted_password == mEncryptedPassword) | ||
469 | { | ||
470 | return; | ||
471 | } | ||
472 | |||
473 | // Max "actual" password length is 16 characters. | ||
474 | // Hex digests are always 32 characters. | ||
475 | // Encrypted passwords stored as hex digits are 64 characters. | ||
476 | if (encrypted_password.length() == (HASHED_LENGTH * 2)) | ||
477 | { | ||
478 | // This is actually encrypted, as found in the grids file. | ||
479 | mEncryptedPassword.assign(encrypted_password); | ||
480 | } | ||
481 | else | ||
482 | { | ||
483 | // Should never happen, this is only called from the file reading bit. | ||
484 | llwarns << "Encrypted password corrupted." << llendl; | ||
485 | return; | ||
486 | } | ||
487 | |||
488 | std::string hashed_password(""); | ||
489 | |||
490 | // Decrypt it for the password hash. | ||
491 | // Decipher with MAC address | ||
492 | U8 buffer[HASHED_LENGTH + 1]; | ||
493 | char in[HASHED_LENGTH * 2 + 1]; | ||
494 | |||
495 | LLStringUtil::copy(in, mEncryptedPassword.c_str(), HASHED_LENGTH * 2 + 1); | ||
496 | /* indra/llmessage/llmail.cpp says "blowfish-not-supported-on-windows", but we shall see. | ||
497 | #if LL_WINDOWS | ||
498 | for (i = 0; i < HASHED_LENGTH; i++) | ||
499 | { | ||
500 | sscanf(in + i * 2, "%2hhx", &buffer[i]); | ||
501 | } | ||
502 | // Note that an XOR "cipher" is a lousy one when the secret is repeated several times like it is here. | ||
503 | LLXORCipher cipher(gMACAddress, 6); | ||
504 | cipher.decrypt(buffer, HASHED_LENGTH); | ||
505 | #else | ||
506 | */ | ||
507 | U8* encrypted = new U8[encrypted_size]; | ||
508 | for (i = 0; i < HASHED_LENGTH; i++) | ||
509 | { | ||
510 | sscanf(in + i * 2, "%2hhx", &encrypted[i]); | ||
511 | } | ||
512 | // Not sure why, but this prints a warning saying it failed, even though it works. Which does not matter that much, we don't use the return value anyway. | ||
513 | cipher.decrypt(encrypted, HASHED_LENGTH, buffer, HASHED_LENGTH); | ||
514 | buffer[HASHED_LENGTH] = '\0'; | ||
515 | |||
516 | // Check to see if the mac address generated a bad hashed | ||
517 | // password. It should be a hex-string or else the mac adress has | ||
518 | // changed. This is a security feature to make sure that if you | ||
519 | // get someone's grid_info.xml file, you cannot hack their account. | ||
520 | // This is a lousy way to check. | ||
521 | if (is_hex_string(buffer, HASHED_LENGTH)) | ||
522 | { | ||
523 | hashed_password.assign((char*)buffer); | ||
524 | } | ||
525 | |||
526 | mPasswordAvatar.assign(hashed_password); | ||
527 | } | ||
426 | 528 | ||
427 | void HippoGridInfo::setPassword(const std::string& unhashed_password) | 529 | void HippoGridInfo::setPassword(const std::string& unhashed_password) |
428 | { | 530 | { |
531 | int i; | ||
532 | |||
429 | if (unhashed_password.empty()) | 533 | if (unhashed_password.empty()) |
430 | { | 534 | { |
431 | mPasswordAvatar = ""; | 535 | mPasswordAvatar = ""; |
536 | mEncryptedPassword = ""; | ||
432 | return; | 537 | return; |
433 | } | 538 | } |
434 | 539 | ||
@@ -454,59 +559,38 @@ void HippoGridInfo::setPassword(const std::string& unhashed_password) | |||
454 | hashed_password = munged_password; | 559 | hashed_password = munged_password; |
455 | } | 560 | } |
456 | 561 | ||
457 | // need to fix the bug in this | 562 | // Encrypt it for storing in the grids file. |
458 | /* | ||
459 | |||
460 | // Encipher with MAC address | 563 | // Encipher with MAC address |
461 | const S32 HASHED_LENGTH = 32; | 564 | char out[HASHED_LENGTH * 2 + 1]; |
462 | U8 buffer[HASHED_LENGTH+1]; | ||
463 | |||
464 | LLStringUtil::copy((char*)buffer, hashed_password.c_str(), HASHED_LENGTH+1); | ||
465 | 565 | ||
466 | LLXORCipher cipher(gMACAddress, 6); | 566 | /* indra/llmessage/llmail.cpp says "blowfish-not-supported-on-windows", but we shall see. |
467 | cipher.encrypt(buffer, HASHED_LENGTH); | 567 | #if LL_WINDOWS |
568 | LLXORCipher cipherX(gMACAddress, 6); | ||
569 | cipherX.encrypt(hashed_password.c_str(), HASHED_LENGTH); | ||
570 | #else | ||
571 | */ | ||
572 | LLBlowfishCipher cipher(gMACAddress, 6); | ||
573 | size_t encrypted_size = cipher.requiredEncryptionSpace(HASHED_LENGTH); | ||
574 | U8* encrypted = new U8[encrypted_size]; | ||
575 | U8* password = (U8 *) hashed_password.c_str(); | ||
468 | 576 | ||
469 | mPasswordAvatar.assign((char*)buffer); | 577 | cipher.encrypt(password, HASHED_LENGTH, encrypted, HASHED_LENGTH); |
470 | */ | 578 | for (i = 0; i < HASHED_LENGTH; i++) |
579 | { | ||
580 | sprintf(out + i * 2, "%02x", encrypted[i]); | ||
581 | } | ||
582 | out[HASHED_LENGTH * 2]='\0'; | ||
583 | mEncryptedPassword.assign(out); | ||
471 | mPasswordAvatar.assign(hashed_password); | 584 | mPasswordAvatar.assign(hashed_password); |
472 | } | 585 | } |
473 | 586 | ||
587 | std::string HippoGridInfo::getEncryptedPassword() const | ||
588 | { | ||
589 | return mEncryptedPassword; | ||
590 | } | ||
474 | 591 | ||
475 | std::string HippoGridInfo::getPassword() const | 592 | std::string HippoGridInfo::getPassword() const |
476 | { | 593 | { |
477 | // need to fix the bug in this | ||
478 | /* | ||
479 | if (mPasswordAvatar.empty() || mPasswordAvatar.length() == 32) | ||
480 | { | ||
481 | return mPasswordAvatar; | ||
482 | } | ||
483 | |||
484 | std::string hashed_password(""); | ||
485 | |||
486 | // UUID is 16 bytes, written into ASCII is 32 characters | ||
487 | // without trailing \0 | ||
488 | const S32 HASHED_LENGTH = 32; | ||
489 | U8 buffer[HASHED_LENGTH+1]; | ||
490 | |||
491 | LLStringUtil::copy((char*)buffer, mPasswordAvatar.c_str(), HASHED_LENGTH+1); | ||
492 | |||
493 | // Decipher with MAC address | ||
494 | LLXORCipher cipher(gMACAddress, 6); | ||
495 | cipher.decrypt(buffer, HASHED_LENGTH); | ||
496 | |||
497 | buffer[HASHED_LENGTH] = '\0'; | ||
498 | |||
499 | // Check to see if the mac address generated a bad hashed | ||
500 | // password. It should be a hex-string or else the mac adress has | ||
501 | // changed. This is a security feature to make sure that if you | ||
502 | // get someone's grid_info.xml file, you cannot hack their account. | ||
503 | if (is_hex_string(buffer, HASHED_LENGTH)) | ||
504 | { | ||
505 | hashed_password.assign((char*)buffer); | ||
506 | } | ||
507 | |||
508 | return hashed_password; | ||
509 | */ | ||
510 | return mPasswordAvatar; | 594 | return mPasswordAvatar; |
511 | } | 595 | } |
512 | 596 | ||
@@ -909,7 +993,9 @@ void HippoGridManager::parseData(LLSD &gridInfo, bool mergeIfNewer) | |||
909 | if (gridMap.has("render_compat")) grid->setRenderCompat(gridMap["render_compat"]); | 993 | if (gridMap.has("render_compat")) grid->setRenderCompat(gridMap["render_compat"]); |
910 | if (gridMap.has("firstname")) grid->setFirstName(gridMap["firstname"]); | 994 | if (gridMap.has("firstname")) grid->setFirstName(gridMap["firstname"]); |
911 | if (gridMap.has("lastname")) grid->setLastName(gridMap["lastname"]); | 995 | if (gridMap.has("lastname")) grid->setLastName(gridMap["lastname"]); |
996 | // Reading this one coz there are some old files in the wild that have it, but not encryptedpassword. | ||
912 | if (gridMap.has("avatarpassword")) grid->setPassword(gridMap["avatarpassword"]); | 997 | if (gridMap.has("avatarpassword")) grid->setPassword(gridMap["avatarpassword"]); |
998 | if (gridMap.has("encryptedpassword")) grid->setEncryptedPassword(gridMap["encryptedpassword"]); | ||
913 | if (gridMap.has("username")) grid->setUsername(gridMap["username"]); | 999 | if (gridMap.has("username")) grid->setUsername(gridMap["username"]); |
914 | if (gridMap.has("username_compat")) grid->setUsernameCompat(gridMap["username_compat"]); | 1000 | if (gridMap.has("username_compat")) grid->setUsernameCompat(gridMap["username_compat"]); |
915 | if (newGrid) addGrid(grid); | 1001 | if (newGrid) addGrid(grid); |
@@ -945,8 +1031,7 @@ void HippoGridManager::saveFile() | |||
945 | gridInfo[i]["password"] = grid->getPasswordURL(); | 1031 | gridInfo[i]["password"] = grid->getPasswordURL(); |
946 | gridInfo[i]["firstname"] = grid->getFirstName(); | 1032 | gridInfo[i]["firstname"] = grid->getFirstName(); |
947 | gridInfo[i]["lastname"] = grid->getLastName(); | 1033 | gridInfo[i]["lastname"] = grid->getLastName(); |
948 | gridInfo[i]["avatarpassword"] = grid->getPassword(); | 1034 | gridInfo[i]["encryptedpassword"] = grid->getEncryptedPassword(); |
949 | |||
950 | gridInfo[i]["search"] = grid->getSearchURL(); | 1035 | gridInfo[i]["search"] = grid->getSearchURL(); |
951 | gridInfo[i]["render_compat"] = grid->isRenderCompat(); | 1036 | gridInfo[i]["render_compat"] = grid->isRenderCompat(); |
952 | 1037 | ||
diff --git a/linden/indra/newview/hippogridmanager.h b/linden/indra/newview/hippogridmanager.h index 8e77f99..c289349 100644 --- a/linden/indra/newview/hippogridmanager.h +++ b/linden/indra/newview/hippogridmanager.h | |||
@@ -89,6 +89,7 @@ public: | |||
89 | const std::string& getLastName() const { return mLastName; } | 89 | const std::string& getLastName() const { return mLastName; } |
90 | const std::string& getUsername() const { return mUsername; } | 90 | const std::string& getUsername() const { return mUsername; } |
91 | std::string getPassword() const; | 91 | std::string getPassword() const; |
92 | std::string getEncryptedPassword() const; | ||
92 | const std::string& getVoiceConnector() const { return mVoiceConnector; } | 93 | const std::string& getVoiceConnector() const { return mVoiceConnector; } |
93 | S32 getMaxAgentGroups() const { return mMaxAgentGroups; } | 94 | S32 getMaxAgentGroups() const { return mMaxAgentGroups; } |
94 | const std::string& getCurrencySymbol() const { return mCurrencySymbol; } | 95 | const std::string& getCurrencySymbol() const { return mCurrencySymbol; } |
@@ -115,6 +116,7 @@ public: | |||
115 | void setFirstName(const std::string& firstName) { mFirstName = firstName; } | 116 | void setFirstName(const std::string& firstName) { mFirstName = firstName; } |
116 | void setLastName(const std::string& lastName) { mLastName = lastName; } | 117 | void setLastName(const std::string& lastName) { mLastName = lastName; } |
117 | void setPassword(const std::string& unhashed_password); | 118 | void setPassword(const std::string& unhashed_password); |
119 | void setEncryptedPassword(const std::string& encrypted_password); | ||
118 | void setVoiceConnector(const std::string& vc) { mVoiceConnector = vc; } | 120 | void setVoiceConnector(const std::string& vc) { mVoiceConnector = vc; } |
119 | void setCurrencySymbol(const std::string& sym) { mCurrencySymbol = sym.substr(0, 3); } | 121 | void setCurrencySymbol(const std::string& sym) { mCurrencySymbol = sym.substr(0, 3); } |
120 | void setRealCurrencySymbol(const std::string& sym) { mRealCurrencySymbol = sym.substr(0, 3); } | 122 | void setRealCurrencySymbol(const std::string& sym) { mRealCurrencySymbol = sym.substr(0, 3); } |
@@ -146,6 +148,7 @@ private: | |||
146 | std::string mFirstName; | 148 | std::string mFirstName; |
147 | std::string mLastName; | 149 | std::string mLastName; |
148 | std::string mPasswordAvatar; | 150 | std::string mPasswordAvatar; |
151 | std::string mEncryptedPassword; | ||
149 | bool mRenderCompat; | 152 | bool mRenderCompat; |
150 | S32 mMaxAgentGroups; | 153 | S32 mMaxAgentGroups; |
151 | 154 | ||
diff --git a/linden/indra/newview/llappviewer.cpp b/linden/indra/newview/llappviewer.cpp index b96f6c2..e02bfc7 100644 --- a/linden/indra/newview/llappviewer.cpp +++ b/linden/indra/newview/llappviewer.cpp | |||
@@ -611,6 +611,11 @@ bool LLAppViewer::init() | |||
611 | // Always add the version to the top of the log--makes debugging easier -- MC | 611 | // Always add the version to the top of the log--makes debugging easier -- MC |
612 | llinfos << ViewerInfo::prettyInfo() << llendl; | 612 | llinfos << ViewerInfo::prettyInfo() << llendl; |
613 | 613 | ||
614 | // Always fetch the Ethernet MAC address, needed both for login | ||
615 | // and password load. Need to do this before initConfiguration(), | ||
616 | // as the password loading part needs the MAC. | ||
617 | LLUUID::getNodeID(gMACAddress); | ||
618 | |||
614 | if (!initConfiguration()) | 619 | if (!initConfiguration()) |
615 | return false; | 620 | return false; |
616 | 621 | ||
@@ -743,10 +748,6 @@ bool LLAppViewer::init() | |||
743 | return false; | 748 | return false; |
744 | } | 749 | } |
745 | 750 | ||
746 | // Always fetch the Ethernet MAC address, needed both for login | ||
747 | // and password load. | ||
748 | LLUUID::getNodeID(gMACAddress); | ||
749 | |||
750 | // Prepare for out-of-memory situations, during which we will crash on | 751 | // Prepare for out-of-memory situations, during which we will crash on |
751 | // purpose and save a dump. | 752 | // purpose and save a dump. |
752 | #if LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP | 753 | #if LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP |