/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Password Manager. * * The Initial Developer of the Original Code is * Brian Ryner. * Portions created by the Initial Developer are Copyright (C) 2003 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Brian Ryner * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsPasswordManager.h" #include "nsIFile.h" #include "nsNetUtil.h" #include "nsILineInputStream.h" #include "plbase64.h" #include "nsISecretDecoderRing.h" #include "nsIPassword.h" #include "nsIPrompt.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsIPrefBranchInternal.h" #include "prmem.h" #include "nsIStringBundle.h" #include "nsArray.h" #include "nsICategoryManager.h" #include "nsIObserverService.h" #include "nsIDocumentLoader.h" #include "nsIWebProgress.h" #include "nsIDOMDocument.h" #include "nsIDOMWindow.h" #include "nsIDOMHTMLDocument.h" #include "nsIDocument.h" #include "nsIDOMHTMLCollection.h" #include "nsIForm.h" #include "nsIDOMHTMLInputElement.h" #include "nsIContent.h" #include "nsIFormControl.h" #include "nsIDOMWindowInternal.h" #include "nsCURILoader.h" #include "nsAppDirectoryServiceDefs.h" static const char kSatchelPropertiesURL[] = "chrome://passwordmgr/locale/passwordmgr.properties"; static PRBool sRememberPasswords = PR_FALSE; static PRBool sPrefsInitialized = PR_FALSE; static nsIStringBundle* sSatchelBundle; static void SatchelLocalizedString(const nsAString& key, nsAString& aResult, PRBool aFormatted = PR_FALSE); class nsPasswordManager::SignonDataEntry { public: nsString userField; nsString userValue; nsString passField; nsString passValue; SignonDataEntry* next; SignonDataEntry() : next(nsnull) { } }; class nsPasswordManager::PasswordEntry : public nsIPassword { public: PasswordEntry(const nsACString& aKey, SignonDataEntry* aData); virtual ~PasswordEntry() { } NS_DECL_ISUPPORTS NS_DECL_NSIPASSWORD protected: nsCString mHost; nsString mUser; nsString mPassword; }; NS_IMPL_ISUPPORTS1(nsPasswordManager::PasswordEntry, nsIPassword) nsPasswordManager::PasswordEntry::PasswordEntry(const nsACString& aKey, SignonDataEntry* aData) : mHost(aKey) { if (aData) { mUser.Assign(aData->userValue); mPassword.Assign(aData->passValue); } } NS_IMETHODIMP nsPasswordManager::PasswordEntry::GetHost(nsACString& aHost) { aHost.Assign(mHost); return NS_OK; } NS_IMETHODIMP nsPasswordManager::PasswordEntry::GetUser(nsAString& aUser) { aUser.Assign(mUser); return NS_OK; } NS_IMETHODIMP nsPasswordManager::PasswordEntry::GetPassword(nsAString& aPassword) { aPassword.Assign(mPassword); return NS_OK; } NS_IMPL_ADDREF(nsPasswordManager) NS_IMPL_RELEASE(nsPasswordManager) NS_INTERFACE_MAP_BEGIN(nsPasswordManager) NS_INTERFACE_MAP_ENTRY(nsIPasswordManager) NS_INTERFACE_MAP_ENTRY(nsIPasswordManagerInternal) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsIFormSubmitObserver) NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPasswordManager) NS_INTERFACE_MAP_END nsPasswordManager::nsPasswordManager() { } nsPasswordManager::~nsPasswordManager() { } nsresult nsPasswordManager::Init() { mSignonTable.Init(); mRejectTable.Init(); sPrefsInitialized = PR_TRUE; nsCOMPtr prefService = do_GetService(NS_PREFSERVICE_CONTRACTID); NS_ASSERTION(prefService, "No pref service"); nsCOMPtr prefBranch; prefService->GetBranch("signon.", getter_AddRefs(prefBranch)); NS_ASSERTION(prefBranch, "No pref branch"); prefBranch->GetBoolPref("rememberSignons", &sRememberPasswords); nsCOMPtr branchInternal = do_QueryInterface(prefBranch); // Have the pref service hold a weak reference; the service manager // will be holding a strong reference. branchInternal->AddObserver("rememberSignons", this, PR_TRUE); // Be a form submit and web progress observer so that we can save and // prefill passwords. nsCOMPtr obsService = do_GetService("@mozilla.org/observer-service;1"); NS_ASSERTION(obsService, "No observer service"); obsService->AddObserver(this, NS_FORMSUBMIT_SUBJECT, PR_TRUE); nsCOMPtr docLoaderService = do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); NS_ASSERTION(docLoaderService, "No document loader service"); nsCOMPtr progress = do_QueryInterface(docLoaderService); NS_ASSERTION(progress, "docloader service does not implement nsIWebProgress"); progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT); // Now read in the signon file nsXPIDLCString signonFile; prefBranch->GetCharPref("SignonFileName", getter_Copies(signonFile)); NS_ASSERTION(!signonFile.IsEmpty(), "Fallback for signon filename on present"); NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mSignonFile)); mSignonFile->AppendNative(signonFile); nsCAutoString path; mSignonFile->GetNativePath(path); ReadSignonFile(); return NS_OK; } /* static */ PRBool nsPasswordManager::SingleSignonEnabled() { if (!sPrefsInitialized) { // Create the PasswordManager service to initialize the prefs and callback nsCOMPtr manager = do_GetService(NS_PASSWORDMANAGER_CONTRACTID); } return sRememberPasswords; } /* static */ nsresult nsPasswordManager::Register(nsIComponentManager* aCompMgr, nsIFile* aPath, const char* aRegistryLocation, const char* aComponentType, const nsModuleComponentInfo* aInfo) { // By registering in NS_PASSWORDMANAGER_CATEGORY, an instance of the password // manager will be created when a password input is added to a form. We // can then register that singleton instance as a form submission observer. nsresult rv; nsCOMPtr catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsXPIDLCString prevEntry; catman->AddCategoryEntry(NS_PASSWORDMANAGER_CATEGORY, "Password Manager", NS_PASSWORDMANAGER_CONTRACTID, PR_TRUE, PR_TRUE, getter_Copies(prevEntry)); return NS_OK; } /* static */ nsresult nsPasswordManager::Unregister(nsIComponentManager* aCompMgr, nsIFile* aPath, const char* aRegistryLocation, const nsModuleComponentInfo* aInfo) { nsresult rv; nsCOMPtr catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); catman->DeleteCategoryEntry(NS_PASSWORDMANAGER_CATEGORY, NS_PASSWORDMANAGER_CONTRACTID, PR_TRUE); return NS_OK; } /* static */ void nsPasswordManager::Shutdown() { NS_IF_RELEASE(sSatchelBundle); } // nsIPasswordManager implementation NS_IMETHODIMP nsPasswordManager::AddUser(const nsACString& aHost, const nsAString& aUser, const nsAString& aPassword) { SignonDataEntry* entry = new SignonDataEntry(); entry->userValue.Assign(aUser); entry->passValue.Assign(aPassword); AddSignonData(aHost, entry); WriteSignonFile(); return NS_OK; } NS_IMETHODIMP nsPasswordManager::RemoveUser(const nsACString& aHost, const nsAString& aUser) { SignonDataEntry* entry, *prevEntry = nsnull; if (!mSignonTable.Get(aHost, &entry) || !entry) return NS_ERROR_FAILURE; for (; entry; prevEntry = entry, entry = entry->next) { if (entry->userValue.Equals(aUser)) { if (prevEntry) { prevEntry->next = entry->next; delete entry; } else // the hashtable will delete the entry mSignonTable.Remove(aHost); WriteSignonFile(); return NS_OK; } } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsPasswordManager::AddReject(const nsACString& aHost) { mRejectTable.Put(aHost, 1); return NS_OK; } NS_IMETHODIMP nsPasswordManager::RemoveReject(const nsACString& aHost) { mRejectTable.Remove(aHost); WriteSignonFile(); return NS_OK; } /* static */ PLDHashOperator PR_CALLBACK nsPasswordManager::BuildArrayEnumerator(const nsACString& aKey, SignonDataEntry* aEntry, void* aUserData) { nsIMutableArray* array = NS_STATIC_CAST(nsIMutableArray*, aUserData); nsCOMPtr passwordEntry = new PasswordEntry(aKey, aEntry); array->AppendElement(passwordEntry, PR_FALSE); return PL_DHASH_NEXT; } NS_IMETHODIMP nsPasswordManager::GetEnumerator(nsISimpleEnumerator** aEnumerator) { // Build an array out of the hashtable nsCOMPtr signonArray; NS_NewArray(getter_AddRefs(signonArray)); mSignonTable.EnumerateRead(BuildArrayEnumerator, signonArray); return signonArray->Enumerate(aEnumerator); } /* static */ PLDHashOperator PR_CALLBACK nsPasswordManager::BuildRejectArrayEnumerator(const nsACString& aKey, PRInt32 aEntry, void* aUserData) { nsIMutableArray* array = NS_STATIC_CAST(nsIMutableArray*, aUserData); nsCOMPtr passwordEntry = new PasswordEntry(aKey, nsnull); array->AppendElement(passwordEntry, PR_FALSE); return PL_DHASH_NEXT; } NS_IMETHODIMP nsPasswordManager::GetRejectEnumerator(nsISimpleEnumerator** aEnumerator) { // Build an array out of the hashtable nsCOMPtr rejectArray; NS_NewArray(getter_AddRefs(rejectArray)); mRejectTable.EnumerateRead(BuildRejectArrayEnumerator, rejectArray); return rejectArray->Enumerate(aEnumerator); } // nsIPasswordManagerInternal implementation struct findEntryContext { nsPasswordManager* manager; const nsACString& aHostURI; const nsAString& aUsername; const nsAString& aPassword; nsACString& aHostURIFound; nsAString& aUsernameFound; nsAString& aPasswordFound; PRBool matched; }; /* static */ PLDHashOperator PR_CALLBACK nsPasswordManager::FindEntryEnumerator(const nsACString& aKey, SignonDataEntry* aEntry, void* aUserData) { findEntryContext* context = NS_STATIC_CAST(findEntryContext*, aUserData); nsPasswordManager* manager = context->manager; nsresult rv; rv = manager->FindPasswordEntryFromSignonData(aEntry, context->aHostURI, context->aUsername, context->aPassword, context->aHostURIFound, context->aUsernameFound, context->aPasswordFound); if (NS_SUCCEEDED(rv)) { context->matched = PR_TRUE; return PL_DHASH_STOP; } return PL_DHASH_NEXT; } NS_IMETHODIMP nsPasswordManager::FindPasswordEntry(const nsACString& aHostURI, const nsAString& aUsername, const nsAString& aPassword, nsACString& aHostURIFound, nsAString& aUsernameFound, nsAString& aPasswordFound) { if (!aHostURI.IsEmpty()) { SignonDataEntry* entry; if (mSignonTable.Get(aHostURI, &entry) && entry) { return FindPasswordEntryFromSignonData(entry, aHostURI, aUsername, aPassword, aHostURIFound, aUsernameFound, aPasswordFound); } return NS_ERROR_FAILURE; } // No host given, so enumerate all entries in the hashtable findEntryContext context = { this, aHostURI, aUsername, aPassword, aHostURIFound, aUsernameFound, aPasswordFound, PR_FALSE }; mSignonTable.EnumerateRead(FindEntryEnumerator, &context); return NS_OK; } // nsIObserver implementation NS_IMETHODIMP nsPasswordManager::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { nsCOMPtr branch = do_QueryInterface(aSubject); NS_ASSERTION(branch, "subject should be a pref branch"); branch->GetBoolPref("rememberSignons", &sRememberPasswords); } return NS_OK; } // nsIWebProgressListener implementation NS_IMETHODIMP nsPasswordManager::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, PRUint32 aStateFlags, nsresult aStatus) { // We're only interested in successful document loads. if (NS_FAILED(aStatus) || !(aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) || !(aStateFlags & nsIWebProgressListener::STATE_STOP)) return NS_OK; nsCOMPtr domWin; nsresult rv = aWebProgress->GetDOMWindow(getter_AddRefs(domWin)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr domDoc; domWin->GetDocument(getter_AddRefs(domDoc)); NS_ASSERTION(domDoc, "DOM window should always have a document!"); // For now, only prefill forms in HTML documents. nsCOMPtr htmlDoc = do_QueryInterface(domDoc); if (!htmlDoc) return NS_OK; nsCOMPtr forms; htmlDoc->GetForms(getter_AddRefs(forms)); nsCOMPtr doc = do_QueryInterface(domDoc); nsCOMPtr uri; doc->GetDocumentURL(getter_AddRefs(uri)); nsCAutoString realm; if (uri) uri->GetPrePath(realm); // Only prefill if there is exactly one username saved for this // realm. Otherwise, we'll wait to prefill the password when the user // autocompletes the username. SignonDataEntry* entry; if (!mSignonTable.Get(realm, &entry) || !entry || (entry && entry->next)) return NS_OK; // Locate the username field by searching each form on the page PRUint32 formCount; forms->GetLength(&formCount); for (PRUint32 i = 0; i < formCount; ++i) { nsCOMPtr formNode; forms->Item(i, getter_AddRefs(formNode)); nsCOMPtr form = do_QueryInterface(formNode); nsCOMPtr foundNode; form->ResolveName(entry->userField, getter_AddRefs(foundNode)); nsCOMPtr userField = do_QueryInterface(foundNode); if (!foundNode && !userField) continue; // Ensure that the password field is also present, otherwise // this is not a login form. form->ResolveName(entry->passField, getter_AddRefs(foundNode)); nsCOMPtr passField = do_QueryInterface(foundNode); if (!foundNode || !passField) continue; userField->SetValue(entry->userValue); passField->SetValue(entry->passValue); } return NS_OK; } NS_IMETHODIMP nsPasswordManager::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, PRInt32 aCurSelfProgress, PRInt32 aMaxSelfProgress, PRInt32 aCurTotalProgress, PRInt32 aMaxTotalProgress) { return NS_OK; } NS_IMETHODIMP nsPasswordManager::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* aLocation) { return NS_OK; } NS_IMETHODIMP nsPasswordManager::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const PRUnichar* aMessage) { return NS_OK; } NS_IMETHODIMP nsPasswordManager::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, PRUint32 aState) { return NS_OK; } // nsIFormSubmitObserver implementation NS_IMETHODIMP nsPasswordManager::Notify(nsIContent* aFormNode, nsIDOMWindowInternal* aWindow, nsIURI* aActionURL, PRBool* aCancelSubmit) { // Check the reject list nsCOMPtr formDoc; aFormNode->GetDocument(getter_AddRefs(formDoc)); nsCOMPtr uri; formDoc->GetDocumentURL(getter_AddRefs(uri)); nsCAutoString realm; uri->GetPrePath(realm); PRInt32 rejectValue; if (mRejectTable.Get(realm, &rejectValue)) { // The user has opted to never save passwords for this site. return NS_OK; } nsCOMPtr formElement = do_QueryInterface(aFormNode); PRUint32 numControls; formElement->GetElementCount(&numControls); // Look for a password field in this form. If there isn't one, // don't try to save any login data. nsCOMPtr userField; nsCOMPtr passField; PRUint32 i; for (i = 0; i < numControls; ++i) { nsCOMPtr control; formElement->GetElementAt(i, getter_AddRefs(control)); if (control->GetType() == NS_FORM_INPUT_PASSWORD) { // We've got the password field now passField = do_QueryInterface(control); break; } } if (!passField) // no passwords, don't save anything return NS_OK; // Search backwards from the password field to find a username field. for (PRUint32 j = i - 1; j >= 0; --j) { nsCOMPtr control; formElement->GetElementAt(j, getter_AddRefs(control)); if (control->GetType() == NS_FORM_INPUT_TEXT) { userField = do_QueryInterface(control); break; } } // If the username field or the form has autocomplete=off, // we don't store the login nsAutoString autocomplete; if (userField) { nsCOMPtr userFieldElement = do_QueryInterface(userField); userFieldElement->GetAttribute(NS_LITERAL_STRING("autocomplete"), autocomplete); if (autocomplete.EqualsIgnoreCase("off")) return NS_OK; } nsCOMPtr formDOMEl = do_QueryInterface(aFormNode); formDOMEl->GetAttribute(NS_LITERAL_STRING("autocomplete"), autocomplete); if (autocomplete.EqualsIgnoreCase("off")) return NS_OK; // Check whether this username and password are already stored nsAutoString userValue, passValue, userFieldName, passFieldName; if (userField) { userField->GetValue(userValue); userField->GetName(userFieldName); } passField->GetValue(passValue); passField->GetName(passFieldName); SignonDataEntry* entry; if (mSignonTable.Get(realm, &entry)) { while (entry) { if (entry->userField.Equals(userFieldName) && entry->userValue.Equals(userValue) && entry->passField.Equals(passFieldName) && entry->passValue.Equals(passValue)) { // It's already present; nothing else to do. return NS_OK; } entry = entry->next; } } nsCOMPtr prompt; aWindow->GetPrompter(getter_AddRefs(prompt)); nsAutoString dialogTitle, dialogText, neverText; SatchelLocalizedString(NS_LITERAL_STRING("savePasswordTitle"), dialogTitle); SatchelLocalizedString(NS_LITERAL_STRING("savePasswordText"), dialogText, PR_TRUE); SatchelLocalizedString(NS_LITERAL_STRING("neverForSite"), neverText); PRInt32 selection; prompt->ConfirmEx(dialogTitle.get(), dialogText.get(), (nsIPrompt::BUTTON_TITLE_YES * nsIPrompt::BUTTON_POS_0) + (nsIPrompt::BUTTON_TITLE_NO * nsIPrompt::BUTTON_POS_1) + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2), nsnull, nsnull, neverText.get(), nsnull, nsnull, &selection); if (selection == 0) { SignonDataEntry* entry = new SignonDataEntry(); entry->userField.Assign(userFieldName); entry->passField.Assign(passFieldName); entry->userValue.Assign(userValue); entry->passValue.Assign(passValue); AddSignonData(realm, entry); } return NS_OK; } // internal methods /* Format of the single signon file: <1-line version header> . * . ..... . */ void nsPasswordManager::ReadSignonFile() { nsCOMPtr fileStream; NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mSignonFile); if (!fileStream) return; nsCOMPtr lineStream = do_QueryInterface(fileStream); NS_ASSERTION(lineStream, "File stream is not an nsILineInputStream"); // Read the header nsAutoString buffer; PRBool moreData = PR_FALSE; nsresult rv = lineStream->ReadLine(buffer, &moreData); if (NS_FAILED(rv)) return; if (!buffer.Equals(NS_LITERAL_STRING("#2c"))) { NS_ERROR("Unexpected version header in signon file"); return; } enum { STATE_REJECT, STATE_REALM, STATE_USERFIELD, STATE_USERVALUE, STATE_PASSFIELD, STATE_PASSVALUE } state = STATE_REJECT; nsCAutoString realm; nsAutoString decryptedValue; SignonDataEntry* entry = nsnull; PRBool decryptError = PR_FALSE; do { rv = lineStream->ReadLine(buffer, &moreData); if (NS_FAILED(rv)) return; switch (state) { case STATE_REJECT: if (buffer.Equals(NS_LITERAL_STRING("."))) state = STATE_REALM; else mRejectTable.Put(NS_ConvertUCS2toUTF8(buffer), 1); break; case STATE_REALM: realm.Assign(NS_ConvertUCS2toUTF8(buffer)); state = STATE_USERFIELD; break; case STATE_USERFIELD: // If the line is a ., we've reached the end of this realm's entries. if (buffer.Equals(NS_LITERAL_STRING("."))) { if (entry) { // Add this entry to the hashtable, unless we had a decryption error if (!decryptError) AddSignonData(realm, entry); entry = nsnull; decryptError = PR_FALSE; state = STATE_REALM; } } else { entry = new SignonDataEntry(); entry->userField.Assign(buffer); state = STATE_USERVALUE; } break; case STATE_USERVALUE: NS_ASSERTION(entry, "bad state"); if (NS_SUCCEEDED(DecryptData(buffer, decryptedValue))) entry->userValue.Assign(decryptedValue); else decryptError = PR_TRUE; // abort this entry state = STATE_PASSFIELD; break; case STATE_PASSFIELD: NS_ASSERTION(entry, "bad state"); // Strip off the leading "*" character if (!decryptError) entry->passField.Assign(Substring(buffer, 1, buffer.Length() - 1)); state = STATE_PASSVALUE; break; case STATE_PASSVALUE: NS_ASSERTION(entry, "bad state"); if (!decryptError) { if (NS_SUCCEEDED(DecryptData(buffer, decryptedValue))) entry->passValue.Assign(decryptedValue); else decryptError = PR_TRUE; // abort this entry } state = STATE_USERFIELD; break; } } while (moreData); // Don't leak if the file ended unexpectedly delete entry; } /* static */ PLDHashOperator PR_CALLBACK nsPasswordManager::WriteRejectEntryEnumerator(const nsACString& aKey, PRInt32 aEntry, void* aUserData) { nsIOutputStream* stream = NS_STATIC_CAST(nsIOutputStream*, aUserData); PRUint32 bytesWritten; nsCAutoString buffer(aKey); buffer.Append(NS_LINEBREAK); stream->Write(buffer.get(), buffer.Length(), &bytesWritten); return PL_DHASH_NEXT; } struct writeSignonEntryContext { nsIOutputStream* stream; nsPasswordManager* manager; }; /* static */ PLDHashOperator PR_CALLBACK nsPasswordManager::WriteSignonEntryEnumerator(const nsACString& aKey, SignonDataEntry* aEntry, void* aUserData) { writeSignonEntryContext* context = NS_STATIC_CAST(writeSignonEntryContext*, aUserData); nsIOutputStream* stream = context->stream; nsPasswordManager* manager = context->manager; PRUint32 bytesWritten; nsCAutoString buffer(aKey); buffer.Append(NS_LINEBREAK); stream->Write(buffer.get(), buffer.Length(), &bytesWritten); NS_ConvertUCS2toUTF8 userField(aEntry->userField); userField.Append(NS_LINEBREAK); stream->Write(userField.get(), userField.Length(), &bytesWritten); manager->EncryptData(aEntry->userValue, buffer); buffer.Append(NS_LINEBREAK); stream->Write(buffer.get(), buffer.Length(), &bytesWritten); buffer.Assign("*"); buffer.Append(NS_ConvertUCS2toUTF8(aEntry->passField)); buffer.Append(NS_LINEBREAK); stream->Write(buffer.get(), buffer.Length(), &bytesWritten); manager->EncryptData(aEntry->passValue, buffer); buffer.Append(NS_LINEBREAK); stream->Write(buffer.get(), buffer.Length(), &bytesWritten); buffer.Assign("." NS_LINEBREAK); stream->Write(buffer.get(), buffer.Length(), &bytesWritten); return PL_DHASH_NEXT; } void nsPasswordManager::WriteSignonFile() { nsCOMPtr fileStream; NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), mSignonFile); if (!fileStream) return; PRUint32 bytesWritten; // File header nsCAutoString buffer("#2c" NS_LINEBREAK); fileStream->Write(buffer.get(), buffer.Length(), &bytesWritten); // Write out the reject list. mRejectTable.EnumerateRead(WriteRejectEntryEnumerator, fileStream); buffer.Assign("." NS_LINEBREAK); fileStream->Write(buffer.get(), buffer.Length(), &bytesWritten); // Write out the signon data. writeSignonEntryContext context = { fileStream, this }; mSignonTable.EnumerateRead(WriteSignonEntryEnumerator, &context); } void nsPasswordManager::AddSignonData(const nsACString& aRealm, SignonDataEntry* aEntry) { // See if there is already an entry for this URL SignonDataEntry* oldEntry; if (mSignonTable.Get(aRealm, &oldEntry) && oldEntry) { // Add this one at the front of the linked list aEntry->next = oldEntry; } mSignonTable.Put(aRealm, aEntry); } nsresult nsPasswordManager::DecryptData(const nsAString& aData, nsAString& aPlaintext) { NS_ConvertUCS2toUTF8 flatData(aData); char* buffer = nsnull; if (flatData.CharAt(0) == '~') { // This is a base64-encoded string. Strip off the ~ prefix. PRUint32 srcLength = flatData.Length() - 1; if (!(buffer = PL_Base64Decode(&(flatData.get())[1], srcLength, NULL))) return NS_ERROR_FAILURE; } else { // This is encrypted using nsISecretDecoderRing. EnsureDecoderRing(); if (!mDecoderRing) { NS_WARNING("Unable to get decoder ring service"); return NS_ERROR_FAILURE; } if (NS_FAILED(mDecoderRing->DecryptString(flatData.get(), &buffer))) return NS_ERROR_FAILURE; } aPlaintext.Assign(NS_ConvertUTF8toUCS2(buffer)); PR_Free(buffer); return NS_OK; } nsresult nsPasswordManager::EncryptData(const nsAString& aPlaintext, nsACString& aEncrypted) { EnsureDecoderRing(); NS_ENSURE_TRUE(mDecoderRing, NS_ERROR_FAILURE); char* buffer; if (NS_FAILED(mDecoderRing->EncryptString(NS_ConvertUCS2toUTF8(aPlaintext).get(), &buffer))) return NS_ERROR_FAILURE; aEncrypted.Assign(buffer); PR_Free(buffer); return NS_OK; } void nsPasswordManager::EnsureDecoderRing() { if (!mDecoderRing) mDecoderRing = do_GetService("@mozilla.org/security/sdr;1"); } nsresult nsPasswordManager::FindPasswordEntryFromSignonData(SignonDataEntry* aEntry, const nsACString& aHost, const nsAString& aUser, const nsAString& aPassword, nsACString& aHostFound, nsAString& aUserFound, nsAString& aPasswordFound) { // host has already been checked, so just look for user/password match. SignonDataEntry* entry = aEntry; for (; entry; entry = entry->next) { PRBool userMatched = (aUser.IsEmpty() || aUser.Equals(entry->userValue)); PRBool passMatched = (aPassword.IsEmpty() || aPassword.Equals(entry->passValue)); if (userMatched && passMatched) break; } if (entry) { aHostFound.Assign(aHost); aUserFound.Assign(entry->userValue); aPasswordFound.Assign(entry->passValue); return NS_OK; } return NS_ERROR_FAILURE; } ////////////////////////////////////// // nsSingleSignonPrompt NS_IMPL_ISUPPORTS2(nsSingleSignonPrompt, nsIAuthPromptWrapper, nsIAuthPrompt) // nsIAuthPrompt NS_IMETHODIMP nsSingleSignonPrompt::Prompt(const PRUnichar* aDialogTitle, const PRUnichar* aText, const PRUnichar* aPasswordRealm, PRUint32 aSavePassword, const PRUnichar* aDefaultText, PRUnichar** aResult, PRBool* aConfirm) { nsAutoString checkMsg; nsString emptyString; PRBool checkValue = (aSavePassword == SAVE_PASSWORD_PERMANENTLY); PRBool *checkPtr = nsnull; PRUnichar* value = nsnull; nsCOMPtr mgrInternal; if (nsPasswordManager::SingleSignonEnabled()) { SatchelLocalizedString(NS_LITERAL_STRING("rememberValue"), checkMsg); checkPtr = &checkValue; mgrInternal = do_GetService(NS_PASSWORDMANAGER_CONTRACTID); nsCAutoString outHost; nsAutoString outUser, outPassword; mgrInternal->FindPasswordEntry(NS_ConvertUCS2toUTF8(aPasswordRealm), emptyString, emptyString, outHost, outUser, outPassword); value = ToNewUnicode(outUser); } mPrompt->Prompt(aDialogTitle, aText, &value, checkMsg.get(), checkPtr, aConfirm); if (*aConfirm && checkValue && value[0] != '\0') { // The user requested that we save the value // TODO: support SAVE_PASSWORD_FOR_SESSION nsCOMPtr manager = do_QueryInterface(mgrInternal); manager->AddUser(NS_ConvertUCS2toUTF8(aPasswordRealm), nsDependentString(value), emptyString); } if (value) nsMemory::Free(value); return NS_OK; } NS_IMETHODIMP nsSingleSignonPrompt::PromptUsernameAndPassword(const PRUnichar* aDialogTitle, const PRUnichar* aText, const PRUnichar* aPasswordRealm, PRUint32 aSavePassword, PRUnichar** aUser, PRUnichar** aPassword, PRBool* aConfirm) { nsAutoString checkMsg; nsString emptyString; PRBool checkValue = (aSavePassword == SAVE_PASSWORD_PERMANENTLY); PRBool *checkPtr = nsnull; PRUnichar *user = nsnull, *password = nsnull; nsCOMPtr mgrInternal; if (nsPasswordManager::SingleSignonEnabled()) { SatchelLocalizedString(NS_LITERAL_STRING("rememberPassword"), checkMsg); checkPtr = &checkValue; mgrInternal = do_GetService(NS_PASSWORDMANAGER_CONTRACTID); nsCAutoString outHost; nsAutoString outUser, outPassword; mgrInternal->FindPasswordEntry(NS_ConvertUCS2toUTF8(aPasswordRealm), emptyString, emptyString, outHost, outUser, outPassword); user = ToNewUnicode(outUser); password = ToNewUnicode(outPassword); } mPrompt->PromptUsernameAndPassword(aDialogTitle, aText, &user, &password, checkMsg.get(), checkPtr, aConfirm); if (*aConfirm && checkValue && user[0] != '\0') { // The user requested that we save the values // TODO: support SAVE_PASSWORD_FOR_SESSION nsCOMPtr manager = do_QueryInterface(mgrInternal); manager->AddUser(NS_ConvertUCS2toUTF8(aPasswordRealm), nsDependentString(user), nsDependentString(password)); } if (user) nsMemory::Free(user); if (password) nsMemory::Free(password); return NS_OK; } NS_IMETHODIMP nsSingleSignonPrompt::PromptPassword(const PRUnichar* aDialogTitle, const PRUnichar* aText, const PRUnichar* aPasswordRealm, PRUint32 aSavePassword, PRUnichar** aPassword, PRBool* aConfirm) { nsAutoString checkMsg; nsString emptyString; PRBool checkValue = (aSavePassword == SAVE_PASSWORD_PERMANENTLY); PRBool *checkPtr = nsnull; PRUnichar* password = nsnull; nsCOMPtr mgrInternal; if (nsPasswordManager::SingleSignonEnabled()) { SatchelLocalizedString(NS_LITERAL_STRING("rememberPassword"), checkMsg); checkPtr = &checkValue; mgrInternal = do_GetService(NS_PASSWORDMANAGER_CONTRACTID); nsCAutoString outHost; nsAutoString outUser, outPassword; mgrInternal->FindPasswordEntry(NS_ConvertUCS2toUTF8(aPasswordRealm), emptyString, emptyString, outHost, outUser, outPassword); password = ToNewUnicode(outPassword); } mPrompt->PromptPassword(aDialogTitle, aText, &password, checkMsg.get(), checkPtr, aConfirm); if (*aConfirm && checkValue && password[0] != '\0') { // The user requested that we save the password // TODO: support SAVE_PASSWORD_FOR_SESSION nsCOMPtr manager = do_QueryInterface(mgrInternal); manager->AddUser(NS_ConvertUCS2toUTF8(aPasswordRealm), emptyString, nsDependentString(password)); } if (password) nsMemory::Free(password); return NS_OK; } // nsIAuthPromptWrapper NS_IMETHODIMP nsSingleSignonPrompt::SetPromptDialogs(nsIPrompt* aDialogs) { mPrompt = aDialogs; return NS_OK; } static void SatchelLocalizedString(const nsAString& key, nsAString& aResult, PRBool aIsFormatted) { if (!sSatchelBundle) { nsCOMPtr bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID); bundleService->CreateBundle(kSatchelPropertiesURL, &sSatchelBundle); if (!sSatchelBundle) { NS_ERROR("string bundle not present"); return; } } nsXPIDLString str; if (aIsFormatted) sSatchelBundle->FormatStringFromName(PromiseFlatString(key).get(), nsnull, 0, getter_Copies(str)); else sSatchelBundle->GetStringFromName(PromiseFlatString(key).get(), getter_Copies(str)); aResult.Assign(str); }