void CAeonEngine::MsgOnMachineStart (const SArchonMessage &Msg, const CHexeSecurityCtx *pSecurityCtx) // MsgOnMachineStart // // Exarch.onMachineStart { CSmartLock Lock(m_cs); // If we've already started, then nothing to do. if (m_bMachineStarted) return; // Check to see if we're listed in the module list. If we're not, then we // continue waiting. if (!Msg.dPayload.IsNil() && !Msg.dPayload.Find(GetProcessCtx()->GetModuleName())) return; // Aeon starting Log(MSG_LOG_INFO, STR_AEON_STARTING); // Make a list of local volumes if (!OpenLocalVolumes()) return; // We've at least gotten to onMachineStart. From now on if we get any // storage added, we have to deal with it differently. m_bMachineStarted = true; // If we have no local volumes then we cannot proceed. We wait until we // have storage to send Aeon.onStart. if (m_LocalVolumes.GetCount() == 0) { Log(MSG_LOG_INFO, STR_ERROR_NO_LOCAL_STORAGE); return; } // Make sure the database is open if (!Open()) return; // Tell any listeners that the database is ready SendMessageNotify(ADDRESS_AEON_NOTIFY, MSG_AEON_ON_START, CDatum()); // Done Log(MSG_LOG_INFO, STR_DATABASE_OPEN); }
bool CAeonEngine::OpenTableDefinitions (void) // OpenTableDefinitions // // Opens and reads all tables in all volumes. Note that we have no idea what // could have happened since our last boot--someone could have copied files // all over the place. We make almost no assumptions. { CSmartLock Lock(m_cs); int i, j; CString sError; // Loop over all volumes and end up with a list of tables and volumes TSortMap<CString, TArray<CString>> Tables; for (i = 0; i < m_LocalVolumes.GetCount(); i++) { CString sVolume = m_LocalVolumes.GetVolume(i); TArray<CString> Dirs; fileGetFileList(fileAppend(m_LocalVolumes.GetPath(i), FILESPEC_TABLE_DIR_FILTER), FFL_FLAG_DIRECTORIES_ONLY | FFL_FLAG_RELATIVE_FILESPEC, &Dirs); for (j = 0; j < Dirs.GetCount(); j++) { TArray<CString> *pList = Tables.SetAt(Dirs[j]); pList->Insert(sVolume); } } // Open all tables for (i = 0; i < Tables.GetCount(); i++) { CString sName = Tables.GetKey(i); CAeonTable *pTable = new CAeonTable; if (!pTable->Open(GetProcessCtx(), &m_LocalVolumes, sName, Tables[i], &sError)) { Log(MSG_LOG_ERROR, strPattern("Unable to load %s: %s", sName, sError)); delete pTable; continue; } m_Tables.Insert(sName, pTable); } // Done return true; }
bool CAeonEngine::OpenLocalVolumes (void) // OpenLocalVolumes // // Initializes the m_LocalVolumes list. Returns TRUE if successful. { // Get the list of volumes in the entire arcology CString sError; if (!m_LocalVolumes.Init(GetProcessCtx(), STR_AEON_FOLDER, &sError)) { Log(MSG_LOG_ERROR, sError); return false; } // Done return true; }
bool CUserInfoSession::OnProcessMessage (const SArchonMessage &Msg) // OnProcessMessage // // We received a reply from Aeon { int i; // If this is an error, then we return the error back to the client if (IsError(Msg)) { SendMessageReplyError(MSG_ERROR_UNABLE_TO_COMPLY, Msg.dPayload); return false; } // If we're waiting for the user record, then see if we can process it now. if (m_iState == stateWaitingForUserRecord) { // Cryptosaur.getUser if (strEquals(GetOriginalMsg().sMsg, MSG_CRYPTOSAUR_GET_USER)) { CDatum dUserData = Msg.dPayload; // If the user does not exist, then we return Nil if (dUserData.IsNil()) { SendMessageReply(MSG_REPLY_DATA, CDatum()); return false; } // Generate a sanitized user record CComplexStruct *pReply = new CComplexStruct; pReply->SetElement(FIELD_USERNAME, dUserData.GetElement(FIELD_USERNAME)); // Sanitize rights CDatum dRights = dUserData.GetElement(FIELD_RIGHTS); if (!m_sScope.IsEmpty()) { CComplexArray *pRights = new CComplexArray; for (i = 0; i < dRights.GetCount(); i++) if (strStartsWith(dRights.GetElement(i), m_sScope)) pRights->Insert(dRights.GetElement(i)); pReply->SetElement(FIELD_RIGHTS, CDatum(pRights)); } else pReply->SetElement(FIELD_RIGHTS, dRights); // Done SendMessageReply(MSG_REPLY_DATA, CDatum(pReply)); return false; } // If we get back nil then the user does not exist. else if (Msg.dPayload.IsNil()) { SendMessageReplyError(MSG_ERROR_DOES_NOT_EXIST, strPattern(ERR_UNKNOWN_USERNAME, m_sUsername)); return false; } // Otherwise, we handle the result based on the original message else if (strEquals(GetOriginalMsg().sMsg, MSG_CRYPTOSAUR_CHECK_PASSWORD_SHA1)) { // Get the parameters from the original message CDatum dChallenge = GetOriginalMsg().dPayload.GetElement(1); CDatum dResponse = GetOriginalMsg().dPayload.GetElement(2); // Get the password has from the response CDatum dAuthDesc = Msg.dPayload.GetElement(FIELD_AUTH_DESC); CDatum dPasswordHash = dAuthDesc.GetElement(FIELD_CREDENTIALS); // Create a response to the challenge based on the password hash that // we have stored. CDatum dCorrect = CAI1Protocol::CreateSHAPasswordChallengeResponse(dPasswordHash, dChallenge); // Compare the correct response to the actual if ((const CIPInteger &)dResponse == (const CIPInteger &)dCorrect) return UpdateLoginSuccess(stateWaitingForSuccessUpdate); else return UpdateLoginFailure(); } // Cryptosaur.hasRights else if (strEquals(GetOriginalMsg().sMsg, MSG_CRYPTOSAUR_HAS_RIGHTS)) { CDatum dRights = Msg.dPayload.GetElement(FIELD_RIGHTS); CDatum dRightsRequired = m_dPayload.GetElement(1); // Get the rights from the user CAttributeList Rights; dRights.AsAttributeList(&Rights); // Check for (i = 0; i < dRightsRequired.GetCount(); i++) { if (!Rights.HasAttribute(dRightsRequired.GetElement(i))) { SendMessageReply(MSG_REPLY_DATA, CDatum()); return false; } } // We have all rights SendMessageReply(MSG_REPLY_DATA, CDatum(CDatum::constTrue)); return false; } // Cryptosaur.loginUser else if (strEquals(GetOriginalMsg().sMsg, MSG_CRYPTOSAUR_LOGIN_USER)) { // Get the parameters from the original message CDatum dRequestAuthDesc = GetOriginalMsg().dPayload.GetElement(1); CDatum dCredentials = dRequestAuthDesc.GetElement(FIELD_CREDENTIALS); CDatum dChallengeCredentials = dRequestAuthDesc.GetElement(FIELD_CHALLENGE_CREDENTIALS); CDatum dPassword = dRequestAuthDesc.GetElement(FIELD_PASSWORD); m_bActual = !dRequestAuthDesc.GetElement(FIELD_ACTUAL).IsNil(); if (!dRequestAuthDesc.GetElement(FIELD_AUTH_TOKEN_INFINITE).IsNil()) m_dwAuthTokenLifetime = 0; else { m_dwAuthTokenLifetime = (DWORD)(int)dRequestAuthDesc.GetElement(FIELD_AUTH_TOKEN_LIFETIME); if (m_dwAuthTokenLifetime == 0) m_dwAuthTokenLifetime = DEFAULT_AUTH_TOKEN_TIMEOUT; } // If we're not actual and have no scope, then we can't continue if (!m_bActual && m_sScope.IsEmpty()) { SendMessageReplyError(MSG_ERROR_UNABLE_TO_COMPLY, ERR_SCOPE_REQUIRED); return false; } // User data CDatum dUserData = Msg.dPayload; CDatum dAuthDesc; if (m_bActual) dAuthDesc = dUserData.GetElement(FIELD_AUTH_DESC); else dAuthDesc = dUserData.GetElement(strPattern("%s%s", m_sScope, FIELD_AUTH_DESC)); // If we have no authdesc, then we can't continue. This is likely // because the client is in a sandbox that the user has not registered // with. We treat it the same as a username/password failure. if (dAuthDesc.IsNil()) { SendMessageReplyError(MSG_ERROR_DOES_NOT_EXIST, ERR_INVALID_USERNAME_OR_PASSWORD); return false; } // If we've failed more than 5 consecutive times, we may need to delay // the next login attempt. if ((int)dUserData.GetElement(FIELD_LOGIN_FAILURE_COUNT) > MAX_LOGIN_ATTEMPTS) { CDateTime LastLoginFailure = dUserData.GetElement(FIELD_LAST_LOGIN_FAILURE_ON); CTimeSpan TimeSinceLastFailure = timeSpan(LastLoginFailure, CDateTime(CDateTime::Now)); // If it has not been at least 1 hour, we return an error. if (TimeSinceLastFailure.Days() == 0 && TimeSinceLastFailure.Seconds() < LOGIN_TIMEOUT) { // Timeout SendMessageReplyError(MSG_ERROR_DOES_NOT_EXIST, ERR_FAILURE_TIMEOUT); return false; } } // If we have straight credentials, then just compare bool bSuccess; if (!dCredentials.IsNil()) bSuccess = ((const CIPInteger &)dCredentials == (const CIPInteger &)dAuthDesc.GetElement(FIELD_CREDENTIALS)); // Otherwise, we compare against the challenge else if (!dChallengeCredentials.IsNil()) { // Get the challenge. If not provided then we get it from the user // record. CDatum dChallenge = GetOriginalMsg().dPayload.GetElement(2); if (dChallenge.IsNil()) { // Get the expiration time of the challenge const CDateTime &Expires = dAuthDesc.GetElement(FIELD_CHALLENGE_EXPIRATION); if (Expires < CDateTime(CDateTime::Now)) { SendMessageReplyError(MSG_ERROR_DOES_NOT_EXIST, ERR_INVALID_USERNAME_OR_PASSWORD); return false; } dChallenge = dAuthDesc.GetElement(FIELD_CHALLENGE); } // Create a response to the challenge based on the password hash that // we have stored. CDatum dCorrectChallenge = CAI1Protocol::CreateSHAPasswordChallengeResponse( dAuthDesc.GetElement(FIELD_CREDENTIALS), dChallenge ); bSuccess = ((const CIPInteger &)dChallengeCredentials == (const CIPInteger &)dCorrectChallenge); } // Otherwise we expect a clear text password else if (!dPassword.IsNil()) { // We have to hash the password to compare with credentials. CIPInteger Credentials; CCryptosaurInterface::CreateCredentials(dUserData.GetElement(FIELD_USERNAME), dPassword, &Credentials); // Compare bSuccess = (Credentials == (const CIPInteger &)dAuthDesc.GetElement(FIELD_CREDENTIALS)); } else bSuccess = false; // Success or failure if (bSuccess) return UpdateLoginSuccess(stateWaitingForCredentials); else return UpdateLoginFailure(); } // Can never get here. else { ASSERT(false); return false; } } // Otherwise, if we're waiting for the user record update, then continue else if (m_iState == stateWaitingForSuccessUpdate) { // Since we succeeded, we send the user sanitized user record back. SendMessageReply(MSG_REPLY_DATA, CreateSanitizedUserRecord(Msg.dPayload)); return false; } // If we're waiting for credentials, compose them else if (m_iState == stateWaitingForCredentials) { // The mutation returns the full record CDatum dUserData = Msg.dPayload; // Compute the result CComplexStruct *pAuthToken = new CComplexStruct; pAuthToken->SetElement(FIELD_USERNAME, dUserData.GetElement(FIELD_USERNAME)); pAuthToken->SetElement(FIELD_RIGHTS, dUserData.GetElement(FIELD_RIGHTS)); if (!m_bActual) pAuthToken->SetElement(FIELD_SCOPE, m_sScope); CDatum dAuthToken = m_pEngine->GenerateAuthToken(CDatum(pAuthToken), m_dwAuthTokenLifetime); // Compose a basic user record CComplexStruct *pReply = new CComplexStruct; pReply->SetElement(FIELD_AUTH_TOKEN, dAuthToken); pReply->SetElement(FIELD_RIGHTS, dUserData.GetElement(FIELD_RIGHTS)); pReply->SetElement(FIELD_USERNAME, dUserData.GetElement(FIELD_USERNAME)); // Send the reply SendMessageReply(MSG_REPLY_DATA, CDatum(pReply)); // Done return false; } // Otherwise, failure else if (m_iState == stateWaitingForFailureUpdate) { CDatum dUserData = Msg.dPayload; // If we've exceeded our limit, log it int iAttempts = (int)dUserData.GetElement(FIELD_LOGIN_FAILURE_COUNT); if (iAttempts > MAX_LOGIN_ATTEMPTS) GetProcessCtx()->Log(MSG_LOG_INFO, strPattern(ERR_USERNAME_TIMEOUT, m_sUsername, iAttempts)); // Send a failure SendMessageReplyError(MSG_ERROR_DOES_NOT_EXIST, ERR_INVALID_USERNAME_OR_PASSWORD); return false; } // Can never get here else { ASSERT(false); return false; } }
void CAeonEngine::MsgFileUpload (const SArchonMessage &Msg, const CHexeSecurityCtx *pSecurityCtx) // MsgFileUpload // // Aeon.fileUpload {filePath} {fileUploadDesc} {data} { AEONERR error; CMsgProcessCtx Ctx(*GetProcessCtx(), Msg, pSecurityCtx); // Get the filePath CString sError; CString sTable; CString sFilePath; if (!CAeonTable::ParseFilePathForCreate(Msg.dPayload.GetElement(0), &sTable, &sFilePath, &sError)) { SendMessageReplyError(MSG_ERROR_UNABLE_TO_COMPLY, strPattern(STR_ERROR_PARSING_FILE_PATH, sError), Msg); return; } // Make sure we are allowed access to this table if (!ValidateTableAccess(Msg, pSecurityCtx, sTable)) return; // Get other parameters CDatum dUploadDesc = Msg.dPayload.GetElement(1); CDatum dData = Msg.dPayload.GetElement(2); // Lock while we find or create a table CSmartLock Lock(m_cs); // If the table doesn't exist, then we create a new one inside the lock CAeonTable *pTable; if (!m_Tables.Find(sTable, &pTable)) { // Compose a table descriptor CDatum dTableDesc; if (!CDatum::Deserialize(CDatum::formatAEONScript, CBuffer(strPattern(STR_FILE_TABLE_DESC_PATTERN, sTable)), &dTableDesc)) { SendMessageReplyError(MSG_ERROR_UNABLE_TO_COMPLY, strPattern(STR_ERROR_INVALID_TABLE_NAME, sTable), Msg); return; } // Create the table if (!CreateTable(dTableDesc, &pTable, NULL, &sError)) { SendMessageReplyError(MSG_ERROR_UNABLE_TO_COMPLY, sError, Msg); return; } } // Unlock Lock.Unlock(); // Generate a unique session ID from the message CString sSessionID = strPattern("%s/%x%s", Msg.sReplyAddr, Msg.dwTicket, sFilePath); #ifdef DEBUG_FILE_UPLOAD DWORD dwStart = ::sysGetTickCount(); Log(MSG_LOG_INFO, strPattern("Aeon.fileUpload %s [%d bytes]", sFilePath, dData.GetBinarySize())); #endif // Let the table handle the rest int iComplete; if (error = pTable->UploadFile(Ctx, sSessionID, sFilePath, dUploadDesc, dData, &iComplete, &sError)) { if (error == AEONERR_OUT_OF_DATE) SendMessageReplyError(MSG_ERROR_OUT_OF_DATE, sError, Msg); else SendMessageReplyError(MSG_ERROR_UNABLE_TO_COMPLY, sError, Msg); return; } #ifdef DEBUG_FILE_UPLOAD Log(MSG_LOG_INFO, strPattern("Aeon.fileUpload complete: %d seconds.", ::sysGetTicksElapsed(dwStart) / 1000)); #endif // Reply if (iComplete == 100) SendMessageReply(MSG_OK, CDatum(), Msg); else { CDatum dReceipt; // LATER: Fill in receipt SendMessageReply(MSG_AEON_FILE_UPLOAD_PROGRESS, dReceipt, Msg); } }
bool CAeonEngine::CreateTable (CDatum dDesc, CAeonTable **retpTable, bool *retbExists, CString *retsError) // CreateTable // // Creates a new table. { // Prefill if (retbExists) *retbExists = false; // No storage if (m_LocalVolumes.GetCount() == 0) { *retsError = STR_ERROR_NO_LOCAL_STORAGE; return false; } // Check to see if this is a valid name const CString &sName = dDesc.GetElement(FIELD_NAME); if (!CAeonTable::ValidateTableName(sName)) { if (retsError) *retsError = strPattern(STR_ERROR_INVALID_TABLE_NAME, sName); return false; } // Add the table (if it doesn't already exist) CSmartLock Lock(m_cs); CAeonTable *pTable; if (m_Tables.Find(sName, &pTable)) { // If the table already exists see if we need to re-initialized it // based on a new descriptor bool bUpdated; if (!pTable->Recreate(GetProcessCtx(), dDesc, &bUpdated, retsError)) return false; // If we updated the table then we succeeded if (bUpdated) { if (retpTable) *retpTable = pTable; return true; } // Otherwise we reply that the table already exists. if (retsError) *retsError = strPattern(STR_ERROR_TABLE_ALREADY_EXISTS, sName); if (retbExists) *retbExists = true; return false; } // Create a new table. // NOTE: We rely on the fact that we've locked the engine to prevent // local storage from changing while the table is created. pTable = new CAeonTable; if (!pTable->Create(GetProcessCtx(), &m_LocalVolumes, dDesc, retsError)) { delete pTable; return false; } int iNewTableIndex = m_Tables.GetCount(); m_Tables.Insert(sName, pTable); // Done if (retpTable) *retpTable = pTable; return true; }
void CAeonEngine::MsgOnMnemosynthModified (const SArchonMessage &Msg, const CHexeSecurityCtx *pSecurityCtx) // MsgOnMnemosynthModified // // Mnemosynth.onModified {updates} { int i; // If we haven't yet started we don't care about this message. if (!m_bMachineStarted) return; // Get the updates CDatum dCollections = Msg.dPayload.GetElement(FIELD_COLLECTIONS); CDatum dEntries = Msg.dPayload.GetElement(FIELD_ENTRIES); // See what changed bool bStorageChanged = false; for (i = 0; i < dEntries.GetCount(); i++) { CDatum dEntry = dEntries.GetElement(i); const CString &sCollection = dCollections.GetElement((int)dEntry.GetElement(0)); if (strEquals(sCollection, MNEMO_ARC_STORAGE)) bStorageChanged = true; } // If storage changed then update local storage (which will tell us what // storage was added or deleted). if (bStorageChanged) { CSmartLock Lock(m_cs); CString sError; TArray<CString> VolumesAdded; TArray<CString> VolumesDeleted; if (!m_LocalVolumes.Reinit(GetProcessCtx(), &VolumesAdded, &VolumesDeleted, &sError)) { // LATER: We don't know how to recover from this, so we should go into // safe mode. Log(MSG_LOG_ERROR, sError); return; } // Tell every table that volumes were added and deleted so that they can // change their primary and backup volumes. if (VolumesAdded.GetCount() > 0 || VolumesDeleted.GetCount() > 0) { for (i = 0; i < m_Tables.GetCount(); i++) m_Tables[i]->OnVolumesChanged(VolumesDeleted); // LATER: See if there are any existing tables in the new volume. } } }