extra: import at 3.0.1 beta 1
[mozilla-extra.git] / extensions / wallet / src / singsign.cpp
blob6a7dc1ce78e04a873e36e712dd1da1956f68c41d
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Pierre Phaneuf <pp@ludusdesign.com>
24 * Mike Calmus
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #define alphabetize 1
42 #include "singsign.h"
43 #include "wallet.h"
44 #include "nsNetUtil.h"
46 #ifdef XP_MAC
47 #include "prpriv.h" /* for NewNamedMonitor */
48 #include "prinrval.h" /* for PR_IntervalNow */
49 #ifdef APPLE_KEYCHAIN /* APPLE */
50 #include "Keychain.h" /* APPLE */
51 #define kNetscapeProtocolType 'form' /* APPLE */
52 #endif /* APPLE */
53 #else
54 #include "private/prpriv.h" /* for NewNamedMonitor */
55 #endif
57 #include "nsIPref.h"
58 #include "nsIServiceManager.h"
59 #include "nsIIOService.h"
60 #include "nsIURL.h"
61 #include "nsIDOMHTMLDocument.h"
62 #include "prmem.h"
63 #include "prprf.h"
64 #include "nsXPIDLString.h"
65 #include "nsReadableUtils.h"
66 #include "nsIObserverService.h"
67 #include "nsIObserver.h"
68 #include "nsIPromptService2.h"
69 #include "nsIWindowWatcher.h"
70 #include "nsIAuthInformation.h"
71 #include "nsIProxiedChannel.h"
72 #include "nsIProxyInfo.h"
73 #include "nsIIDNService.h"
74 #include "nsNetCID.h"
75 #include "nsCRT.h"
76 #include "nsPromptUtils.h"
78 //#define SINGSIGN_LOGGING
79 #ifdef SINGSIGN_LOGGING
80 #define LOG(args) printf args
81 #else
82 #define LOG(args)
83 #endif
85 // Currently the default is on, so we don't need the code to prompt the
86 // user if the default is off.
87 #undef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
89 /********************
90 * Global Variables *
91 ********************/
93 /* locks for signon cache */
95 static PRMonitor * signon_lock_monitor = NULL;
96 static PRThread * signon_lock_owner = NULL;
97 static int signon_lock_count = 0;
99 /* load states */
101 static PRBool si_PartiallyLoaded = PR_FALSE;
102 static PRInt32 si_LastFormForWhichUserHasBeenSelected = -1;
104 /* apple keychain stuff */
106 #ifdef APPLE_KEYCHAIN
107 static PRBool si_list_invalid = PR_FALSE;
108 static KCCallbackUPP si_kcUPP = NULL;
109 static int
110 si_SaveSignonDataInKeychain();
111 #endif
113 #define USERNAMEFIELD "\\=username=\\"
114 #define PASSWORDFIELD "\\=password=\\"
116 /******************
117 * Key Management *
118 ******************/
120 char* signonFileName = nsnull;
121 static PRBool gLoadedUserData = PR_FALSE;
122 static PRUint32 gSelectUserDialogCount = 0;
125 /***************************
126 * Locking the Signon List *
127 ***************************/
129 static void
130 si_lock_signon_list(void) {
131 if(!signon_lock_monitor) {
132 signon_lock_monitor = PR_NewNamedMonitor("signon-lock");
134 PR_EnterMonitor(signon_lock_monitor);
135 while(PR_TRUE) {
137 /* no current owner or owned by this thread */
138 PRThread * t = PR_GetCurrentThread();
139 if(signon_lock_owner == NULL || signon_lock_owner == t) {
140 signon_lock_owner = t;
141 signon_lock_count++;
142 PR_ExitMonitor(signon_lock_monitor);
143 return;
146 /* owned by someone else -- wait till we can get it */
147 PR_Wait(signon_lock_monitor, PR_INTERVAL_NO_TIMEOUT);
151 static void
152 si_unlock_signon_list(void) {
153 PR_EnterMonitor(signon_lock_monitor);
155 #ifdef DEBUG
156 /* make sure someone doesn't try to free a lock they don't own */
157 PR_ASSERT(signon_lock_owner == PR_GetCurrentThread());
158 #endif
160 signon_lock_count--;
161 if(signon_lock_count == 0) {
162 signon_lock_owner = NULL;
163 PR_Notify(signon_lock_monitor);
165 PR_ExitMonitor(signon_lock_monitor);
169 /********************************
170 * Preference Utility Functions *
171 ********************************/
173 void
174 SI_RegisterCallback(const char* domain, PrefChangedFunc callback, void* instance_data) {
175 nsresult ret;
176 nsCOMPtr<nsIPref> pPrefService = do_GetService(NS_PREF_CONTRACTID, &ret);
177 if (NS_SUCCEEDED(ret)) {
178 ret = pPrefService->RegisterCallback(domain, callback, instance_data);
182 void
183 SI_UnregisterCallback(const char* domain, PrefChangedFunc callback, void* instance_data) {
184 nsresult ret;
185 nsCOMPtr<nsIPref> pPrefService = do_GetService(NS_PREF_CONTRACTID, &ret);
186 if (NS_SUCCEEDED(ret)) {
187 ret = pPrefService->UnregisterCallback(domain, callback, instance_data);
191 void
192 SI_SetBoolPref(const char * prefname, PRBool prefvalue) {
193 nsresult ret;
194 nsCOMPtr<nsIPref> pPrefService = do_GetService(NS_PREF_CONTRACTID, &ret);
195 if (NS_SUCCEEDED(ret)) {
196 ret = pPrefService->SetBoolPref(prefname, prefvalue);
197 if (NS_SUCCEEDED(ret)) {
198 ret = pPrefService->SavePrefFile(nsnull);
203 PRBool
204 SI_GetBoolPref(const char * prefname, PRBool defaultvalue) {
205 nsresult ret;
206 PRBool prefvalue = defaultvalue;
207 nsCOMPtr<nsIPref> pPrefService = do_GetService(NS_PREF_CONTRACTID, &ret);
208 if (NS_SUCCEEDED(ret)) {
209 ret = pPrefService->GetBoolPref(prefname, &prefvalue);
211 return prefvalue;
214 void
215 SI_SetCharPref(const char * prefname, const char * prefvalue) {
216 if (!prefvalue) {
217 return; /* otherwise the SetCharPref routine called below will crash */
219 nsresult ret;
220 nsCOMPtr<nsIPref> pPrefService = do_GetService(NS_PREF_CONTRACTID, &ret);
221 if (NS_SUCCEEDED(ret)) {
222 ret = pPrefService->SetCharPref(prefname, prefvalue);
223 if (NS_SUCCEEDED(ret)) {
224 ret = pPrefService->SavePrefFile(nsnull);
229 void
230 SI_GetCharPref(const char * prefname, char** aPrefvalue) {
231 nsresult ret;
232 nsCOMPtr<nsIPref> pPrefService = do_GetService(NS_PREF_CONTRACTID, &ret);
233 if (NS_SUCCEEDED(ret)) {
234 ret = pPrefService->CopyCharPref(prefname, aPrefvalue);
235 if (NS_FAILED(ret)) {
236 *aPrefvalue = nsnull;
238 } else {
239 *aPrefvalue = nsnull;
243 void
244 SI_GetLocalizedUnicharPref(const char * prefname, PRUnichar** aPrefvalue) {
245 nsresult ret;
246 nsCOMPtr<nsIPref> pPrefService = do_GetService(NS_PREF_CONTRACTID, &ret);
247 if (NS_SUCCEEDED(ret)) {
248 ret = pPrefService->GetLocalizedUnicharPref(prefname, aPrefvalue);
249 if (NS_FAILED(ret)) {
250 *aPrefvalue = nsnull;
252 } else {
253 *aPrefvalue = nsnull;
258 /*********************************
259 * Preferences for Single Signon *
260 *********************************/
262 static const char *pref_rememberSignons = "signon.rememberSignons";
263 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
264 static const char *pref_Notified = "signon.Notified";
265 #endif
266 static const char *pref_SignonFileName = "signon.SignonFileName";
268 static PRBool si_RememberSignons = PR_FALSE;
269 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
270 static PRBool si_Notified = PR_FALSE;
271 #endif
273 static int
274 si_SaveSignonDataLocked(char * state, PRBool notify);
277 SI_LoadSignonData();
279 void
280 SI_RemoveAllSignonData();
282 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
283 static PRBool
284 si_GetNotificationPref(void) {
285 return si_Notified;
288 static void
289 si_SetNotificationPref(PRBool x) {
290 SI_SetBoolPref(pref_Notified, x);
291 si_Notified = x;
293 #endif
295 static void
296 si_SetSignonRememberingPref(PRBool x) {
297 #ifdef APPLE_KEYCHAIN
298 if (x == 0) {
299 /* We no longer need the Keychain callback installed */
300 KCRemoveCallback( si_kcUPP );
301 DisposeRoutineDescriptor( si_kcUPP );
302 si_kcUPP = NULL;
304 #endif
305 si_RememberSignons = x;
308 int PR_CALLBACK
309 si_SignonRememberingPrefChanged(const char * newpref, void * data) {
310 PRBool x;
311 x = SI_GetBoolPref(pref_rememberSignons, PR_TRUE);
312 si_SetSignonRememberingPref(x);
313 return 0; /* this is PREF_NOERROR but we no longer include prefapi.h */
316 static void
317 si_RegisterSignonPrefCallbacks(void) {
318 PRBool x;
319 static PRBool first_time = PR_TRUE;
320 if(first_time) {
321 first_time = PR_FALSE;
322 SI_RegisterCallback(pref_rememberSignons, si_SignonRememberingPrefChanged, NULL);
325 if (!gLoadedUserData) {
326 gLoadedUserData = PR_TRUE;
327 SI_LoadSignonData();
328 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
329 x = SI_GetBoolPref(pref_Notified, PR_FALSE);
330 si_SetNotificationPref(x);
331 #endif
332 x = SI_GetBoolPref(pref_rememberSignons, PR_FALSE);
333 si_SetSignonRememberingPref(x);
337 static PRBool
338 si_GetSignonRememberingPref(void) {
339 #ifdef APPLE_KEYCHAIN
340 /* If the Keychain has been locked or an item deleted or updated,
341 * we need to reload the signon data
343 if (si_list_invalid) {
345 * set si_list_invalid to PR_FALSE first because SI_RemoveAllSignonData
346 * calls si_GetSignonRememberingPref
348 si_list_invalid = PR_FALSE;
349 SI_LoadSignonData();
351 #endif
353 si_RegisterSignonPrefCallbacks();
355 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
357 * We initially want the rememberSignons pref to be PR_FALSE. But this will
358 * prevent the notification message from ever occurring. To get around
359 * this problem, if the signon pref is PR_FALSE and no notification has
360 * ever been given, we will treat this as if the signon pref were PR_TRUE.
362 if (!si_RememberSignons && !si_GetNotificationPref()) {
363 return PR_TRUE;
364 } else {
365 return si_RememberSignons;
367 #else
368 return si_RememberSignons;
369 #endif
372 void
373 SI_InitSignonFileName() {
374 SI_GetCharPref(pref_SignonFileName, &signonFileName);
375 if (!signonFileName) {
376 signonFileName = Wallet_RandomName("s");
377 SI_SetCharPref(pref_SignonFileName, signonFileName);
382 /***********
383 * Dialogs *
384 ***********/
386 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
387 static PRBool
388 si_ConfirmYN(PRUnichar * szMessage, nsIDOMWindowInternal* window) {
389 return Wallet_ConfirmYN(szMessage, window);
391 #endif
393 static PRInt32
394 si_3ButtonConfirm(PRUnichar * szMessage, nsIDOMWindowInternal* window) {
395 return Wallet_3ButtonConfirm(szMessage, window);
398 static PRBool
399 si_SelectDialog(const PRUnichar* szMessage, nsIPrompt* dialog, PRUnichar** pList, PRInt32* pCount, PRUint32 formNumber) {
400 if (si_LastFormForWhichUserHasBeenSelected == (PRInt32)formNumber) {
401 /* a user was already selected for this form, use same one again */
402 *pCount = 0; /* last user selected is now at head of list */
403 return PR_TRUE;
405 nsresult rv;
406 PRInt32 selectedIndex;
407 PRBool rtnValue;
408 PRUnichar * title_string = Wallet_Localize("SelectUserTitleLine");
410 /* Notify signon manager dialog to update its display */
411 nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
412 gSelectUserDialogCount++;
413 if (os) {
414 os->NotifyObservers(nsnull, "signonSelectUser", NS_LITERAL_STRING("suspend").get());
417 rv = dialog->Select( title_string, szMessage, *pCount, const_cast<const PRUnichar**>(pList), &selectedIndex, &rtnValue );
419 gSelectUserDialogCount--;
420 if (os) {
421 os->NotifyObservers(nsnull, "signonSelectUser", NS_LITERAL_STRING("resume").get());
424 Recycle(title_string);
425 if (selectedIndex >= *pCount) {
426 return PR_FALSE; // out-of-range selection
428 *pCount = selectedIndex;
429 if (rtnValue) {
430 si_LastFormForWhichUserHasBeenSelected = formNumber;
432 return rtnValue;
435 static nsresult
436 si_CheckGetPassword
437 (PRUnichar ** password,
438 const PRUnichar* dialogTitle,
439 const PRUnichar * szMessage,
440 nsIPrompt* dialog,
441 PRUint32 savePassword,
442 PRBool* checkValue)
444 nsresult res;
446 PRUnichar * prompt_string = (PRUnichar*)dialogTitle;
447 if (dialogTitle == nsnull || dialogTitle[0] == 0)
448 prompt_string = Wallet_Localize("PromptForPassword");
449 PRUnichar * check_string;
451 // According to nsIPrompt spec, the checkbox is shown or not
452 // depending on whether or not checkValue == nsnull, not checkMsg.
453 PRBool * check_value = checkValue;
454 if (savePassword != SINGSIGN_SAVE_PASSWORD_PERMANENTLY) {
455 check_string = nsnull;
456 check_value = nsnull;
457 } else if (SI_GetBoolPref(pref_Crypto, PR_FALSE)) {
458 check_string = Wallet_Localize("SaveThisPasswordEncrypted");
459 } else {
460 check_string = Wallet_Localize("SaveThisPasswordObscured");
463 PRBool confirmed = PR_FALSE;
464 res = dialog->PromptPassword(prompt_string,
465 szMessage,
466 password,
467 check_string,
468 check_value,
469 &confirmed);
471 if (dialogTitle == nsnull)
472 Recycle(prompt_string);
473 if (check_string)
474 Recycle(check_string);
476 if (NS_FAILED(res)) {
477 return res;
479 if (confirmed) {
480 return NS_OK;
481 } else {
482 return NS_ERROR_FAILURE; /* user pressed cancel */
486 static nsresult
487 si_CheckGetData
488 (PRUnichar ** data,
489 const PRUnichar* dialogTitle,
490 const PRUnichar * szMessage,
491 nsIPrompt* dialog,
492 PRUint32 savePassword,
493 PRBool* checkValue)
495 nsresult res;
497 PRUnichar * prompt_string = (PRUnichar*)dialogTitle;
498 if (dialogTitle == nsnull || dialogTitle[0] == 0)
499 prompt_string = Wallet_Localize("PromptForData");
500 PRUnichar * check_string;
502 // According to nsIPrompt spec, the checkbox is shown or not
503 // depending on whether or not checkValue == nsnull, not checkMsg.
504 PRBool * check_value = checkValue;
505 if (savePassword != SINGSIGN_SAVE_PASSWORD_PERMANENTLY) {
506 check_string = nsnull;
507 check_value = nsnull;
508 } else if (SI_GetBoolPref(pref_Crypto, PR_FALSE)) {
509 check_string = Wallet_Localize("SaveThisValueEncrypted");
510 } else {
511 check_string = Wallet_Localize("SaveThisValueObscured");
514 PRBool confirmed = PR_FALSE;
515 res = dialog->Prompt(prompt_string,
516 szMessage,
517 data,
518 check_string,
519 check_value,
520 &confirmed);
522 if (dialogTitle == nsnull || dialogTitle[0] == 0)
523 Recycle(prompt_string);
524 if (check_string)
525 Recycle(check_string);
527 if (NS_FAILED(res)) {
528 return res;
530 if (confirmed) {
531 return NS_OK;
532 } else {
533 return NS_ERROR_FAILURE; /* user pressed cancel */
537 static nsresult
538 si_CheckGetUsernamePassword
539 (PRUnichar ** username,
540 PRUnichar ** password,
541 const PRUnichar* dialogTitle,
542 const PRUnichar * szMessage,
543 nsIPrompt* dialog,
544 PRUint32 savePassword,
545 PRBool* checkValue)
547 nsresult res;
548 PRUnichar * check_string;
549 PRUnichar * prompt_string = (PRUnichar*)dialogTitle;
550 if (dialogTitle == nsnull || dialogTitle[0] == 0)
551 prompt_string = Wallet_Localize("PromptForPassword");
553 // According to nsIPrompt spec, the checkbox is shown or not
554 // depending on whether or not checkValue == nsnull, not checkMsg.
555 PRBool * check_value = checkValue;
556 if (savePassword != SINGSIGN_SAVE_PASSWORD_PERMANENTLY) {
557 check_string = nsnull;
558 check_value = nsnull;
559 } else if (SI_GetBoolPref(pref_Crypto, PR_FALSE)) {
560 check_string = Wallet_Localize("SaveTheseValuesEncrypted");
561 } else {
562 check_string = Wallet_Localize("SaveTheseValuesObscured");
565 PRBool confirmed = PR_FALSE;
566 res = dialog->PromptUsernameAndPassword(prompt_string,
567 szMessage,
568 username, password,
569 check_string,
570 check_value,
571 &confirmed);
573 if (dialogTitle == nsnull || dialogTitle[0] == 0)
574 Recycle(prompt_string);
575 if (check_string)
576 Recycle(check_string);
578 if (NS_FAILED(res)) {
579 return res;
581 if (confirmed) {
582 return NS_OK;
583 } else {
584 return NS_ERROR_FAILURE; /* user pressed cancel */
588 static nsresult
589 si_CheckPromptAuth
590 (nsIPromptService2* aService, nsIDOMWindow* aParent, nsIChannel* aChannel,
591 PRUint32 aLevel, nsIAuthInformation* aAuthInfo, PRBool* remembered)
593 PRUnichar* check_string;
594 if (SI_GetBoolPref(pref_Crypto, PR_FALSE)) {
595 check_string = Wallet_Localize("SaveTheseValuesEncrypted");
596 } else {
597 check_string = Wallet_Localize("SaveTheseValuesObscured");
600 PRBool confirmed = PR_FALSE;
601 nsresult rv = aService->PromptAuth(aParent, aChannel, aLevel,
602 aAuthInfo, check_string,
603 remembered, &confirmed);
604 if (check_string)
605 Recycle(check_string);
607 if (NS_FAILED(rv))
608 return rv;
610 if (confirmed) {
611 return NS_OK;
612 } else {
613 return NS_ERROR_FAILURE; /* user pressed cancel */
619 /********************
620 * Utility Routines *
621 ********************/
623 /* StrAllocCopy should really be defined elsewhere */
624 #include "plstr.h"
625 #include "prmem.h"
627 #undef StrAllocCopy
628 #define StrAllocCopy(dest, src) Local_SACopy (&(dest), src)
629 static char *
630 Local_SACopy(char **destination, const char *source) {
631 if(*destination) {
632 PL_strfree(*destination);
634 *destination = PL_strdup(source);
635 return *destination;
638 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
639 /* If user-entered password is "********", then generate a random password */
640 static void
641 si_Randomize(nsString& password) {
642 PRIntervalTime randomNumber;
643 int i;
644 const char * hexDigits = "0123456789AbCdEf";
645 if (password.EqualsLiteral("********")) {
646 randomNumber = PR_IntervalNow();
647 for (i=0; i<8; i++) {
648 password.SetCharAt(hexDigits[randomNumber%16], i);
649 randomNumber = randomNumber/16;
653 #endif
656 /***********************
657 * Encryption Routines *
658 ***********************/
660 static PRBool
661 si_CompareEncryptedToCleartext(const nsString& crypt, const nsString& text) {
662 nsAutoString decrypted;
663 if (NS_FAILED(Wallet_Decrypt(crypt, decrypted))) {
664 return PR_FALSE;
666 return (decrypted == text);
669 static PRBool
670 si_CompareEncryptedToEncrypted(const nsString& crypt1, const nsString& crypt2) {
671 nsAutoString decrypted1;
672 nsAutoString decrypted2;
673 if (NS_FAILED(Wallet_Decrypt(crypt1, decrypted1))) {
674 return PR_FALSE;
676 if (NS_FAILED(Wallet_Decrypt(crypt2, decrypted2))) {
677 return PR_FALSE;
679 return (decrypted1 == decrypted2);
683 /************************
684 * Managing Signon List *
685 ************************/
687 static PRUint32
688 SecondsFromPRTime(PRTime prTime) {
689 PRInt64 microSecondsPerSecond, intermediateResult;
690 PRUint32 seconds;
692 LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC);
693 LL_DIV(intermediateResult, prTime, microSecondsPerSecond);
694 LL_L2UI(seconds, intermediateResult);
695 return seconds;
698 si_SignonDataStruct::si_SignonDataStruct()
699 : isPassword(PR_FALSE)
701 MOZ_COUNT_CTOR(si_SignonDataStruct);
703 si_SignonDataStruct::~si_SignonDataStruct()
705 MOZ_COUNT_DTOR(si_SignonDataStruct);
708 class si_SignonUserStruct {
709 public:
710 si_SignonUserStruct()
712 MOZ_COUNT_CTOR(si_SignonUserStruct);
714 ~si_SignonUserStruct()
716 for (PRInt32 i = signonData_list.Count() - 1; i >= 0; i--) {
717 delete static_cast<si_SignonDataStruct*>(signonData_list.ElementAt(i));
719 MOZ_COUNT_DTOR(si_SignonUserStruct);
721 PRUint32 time;
722 nsVoidArray signonData_list; // elements are si_SignonDataStruct
725 class si_SignonURLStruct {
726 public:
727 si_SignonURLStruct() : passwordRealm(NULL), chosen_user(NULL)
729 MOZ_COUNT_CTOR(si_SignonURLStruct);
731 ~si_SignonURLStruct()
733 MOZ_COUNT_DTOR(si_SignonURLStruct);
735 char * passwordRealm;
736 si_SignonUserStruct* chosen_user; /* this is a state variable */
737 nsVoidArray signonUser_list;
740 class si_Reject {
741 public:
742 si_Reject() : passwordRealm(NULL)
744 MOZ_COUNT_CTOR(si_Reject);
746 ~si_Reject()
748 MOZ_COUNT_DTOR(si_Reject);
750 char * passwordRealm;
751 nsString userName;
754 static nsVoidArray * si_signon_list=0;
755 static nsVoidArray * si_reject_list=0;
756 #define LIST_COUNT(list) (list ? list->Count() : 0)
757 static PRBool si_signon_list_changed = PR_FALSE;
760 * Get the URL node for a given URL name
762 * This routine is called only when holding the signon lock!!!
764 static si_SignonURLStruct *
765 si_GetURL(const char * passwordRealm) {
766 si_SignonURLStruct * url;
767 if (!passwordRealm) {
768 /* no passwordRealm specified, return first URL (returns NULL if not URLs) */
769 if (LIST_COUNT(si_signon_list)==0) {
770 return NULL;
772 /* XXX how can this be right -- seems wrong to give back a random password */
773 LOG((" returning first element in the signon list\n"));
774 return (si_SignonURLStruct *) (si_signon_list->ElementAt(0));
777 PRInt32 urlCount = LIST_COUNT(si_signon_list);
778 if (urlCount) {
779 // If the last char of passwordRealm is '/' then strip it before making comparison.
780 nsCAutoString realmWithoutTrailingSlash(passwordRealm);
781 if (!realmWithoutTrailingSlash.IsEmpty() && realmWithoutTrailingSlash.Last() == '/')
782 realmWithoutTrailingSlash.Truncate(realmWithoutTrailingSlash.Length()-1);
784 for (PRInt32 i=0; i<urlCount; i++) {
785 url = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(i));
786 if(url->passwordRealm && !PL_strcmp(realmWithoutTrailingSlash.get(), url->passwordRealm)) {
787 return url;
791 return (NULL);
795 * composite URL struct for handling the migration of legacy password entries.
798 class si_SignonCompositeURLStruct : public si_SignonURLStruct {
799 public:
800 si_SignonURLStruct *primaryUrl;
801 si_SignonURLStruct *legacyUrl;
804 static si_SignonCompositeURLStruct * si_composite_url=0;
806 #if defined(SINGSIGN_LOGGING)
807 static void
808 si_DumpUserList(nsVoidArray &list)
810 LOG(("dumping user list:\n"));
811 PRInt32 i, j, user_count = list.Count(), data_count;
812 for (i=0; i<user_count; ++i) {
813 si_SignonUserStruct *user = (si_SignonUserStruct *) list[i];
814 LOG((" user[%d]\n", i));
815 data_count = user->signonData_list.Count();
816 for (j=0; j<data_count; ++j) {
817 si_SignonDataStruct *data = (si_SignonDataStruct *) user->signonData_list[j];
818 LOG((" (%s,%s)\n",
819 NS_ConvertUTF16toUTF8(data->name).get(),
820 NS_ConvertUTF16toUTF8(data->value).get()));
824 #endif
826 static si_SignonURLStruct *
827 si_GetCompositeURL(const char *primaryRealm, const char *legacyRealm)
829 si_SignonURLStruct *primaryUrl, *legacyUrl;
831 primaryUrl = si_GetURL(primaryRealm);
833 if (legacyRealm)
834 legacyUrl = si_GetURL(legacyRealm);
835 else
836 legacyUrl = nsnull;
838 if (primaryUrl && legacyUrl) {
839 LOG((">>> building composite URL struct\n"));
840 if (si_composite_url) {
841 NS_ERROR("si_composite_url already in use");
842 return NULL;
844 si_composite_url = new si_SignonCompositeURLStruct;
845 if (!si_composite_url)
846 return NULL;
848 si_composite_url->primaryUrl = primaryUrl;
849 si_composite_url->legacyUrl = legacyUrl;
851 si_composite_url->signonUser_list.AppendElements(primaryUrl->signonUser_list);
852 si_composite_url->signonUser_list.AppendElements(legacyUrl->signonUser_list);
854 #if defined(SINGSIGN_LOGGING)
855 si_DumpUserList(si_composite_url->signonUser_list);
856 #endif
858 /* need to transfer the chosen_user state variable */
859 if (primaryUrl->chosen_user)
860 si_composite_url->chosen_user = primaryUrl->chosen_user;
861 else if (legacyUrl->chosen_user) {
862 si_SignonUserStruct *chosen_user = legacyUrl->chosen_user;
863 PRInt32 index;
864 /* XXX fixup chosen_user -- THIS SHOULD NOT BE NECESSARY */
865 index = legacyUrl->signonUser_list.IndexOf(chosen_user);
866 if (index < 0) {
867 index = primaryUrl->signonUser_list.IndexOf(chosen_user);
868 if (index >= 0)
869 primaryUrl->chosen_user = chosen_user;
870 legacyUrl->chosen_user = NULL;
872 /* move first element of legacy user list to front */
873 index = si_composite_url->signonUser_list.IndexOf(chosen_user);
874 if (index > 0)
875 si_composite_url->signonUser_list.MoveElement(index, 0);
876 si_composite_url->chosen_user = chosen_user;
878 else
879 si_composite_url->chosen_user = NULL;
881 #if defined(SINGSIGN_LOGGING)
882 LOG(("after chosen_user fixup [chosen_user=%x]:\n", si_composite_url->chosen_user));
883 si_DumpUserList(si_composite_url->signonUser_list);
884 #endif
886 return si_composite_url;
889 if (primaryUrl)
890 return primaryUrl;
892 return legacyUrl;
895 static PRInt32
896 si_SetChosenUser(si_SignonURLStruct *url, si_SignonUserStruct *chosen_user)
898 PRInt32 index;
900 index = url->signonUser_list.IndexOf(chosen_user);
901 if (index < 0) {
902 url->chosen_user = NULL;
903 return -1;
906 url->chosen_user = chosen_user;
907 return index;
910 static void
911 si_ReleaseCompositeURL(si_SignonURLStruct *url)
913 if (url == si_composite_url) {
914 si_SignonUserStruct *chosen_user = url->chosen_user;
915 /* need to transfer the chosen_user state variable */
916 if (chosen_user) {
917 PRInt32 index;
919 /* store chosen_user */
920 index = si_SetChosenUser(url = si_composite_url->primaryUrl, chosen_user);
921 if (index >= 0)
922 si_composite_url->legacyUrl->chosen_user = NULL;
923 else
924 index = si_SetChosenUser(url = si_composite_url->legacyUrl, chosen_user);
925 NS_ASSERTION(index >= 0, "chosen_user not found");
927 /* need to move chosen_user to front of list */
928 url->signonUser_list.MoveElement(index, 0);
930 else {
931 si_composite_url->primaryUrl->chosen_user = NULL;
932 si_composite_url->legacyUrl->chosen_user = NULL;
934 si_composite_url->primaryUrl = NULL;
935 si_composite_url->legacyUrl = NULL;
936 si_composite_url->chosen_user = NULL;
937 si_composite_url->signonUser_list.Clear();
939 delete si_composite_url;
940 si_composite_url = NULL;
944 /* Remove a user node from a given URL node */
945 static PRBool
946 si_RemoveUser(const char *passwordRealm, const nsString& userName, PRBool save, PRBool loginFailure, PRBool notify, PRBool first = PR_FALSE) {
947 si_SignonURLStruct * url;
948 si_SignonUserStruct * user;
949 si_SignonDataStruct * data;
951 si_lock_signon_list();
953 /* get URL corresponding to host */
954 url = si_GetURL(passwordRealm);
955 if (!url) {
956 /* URL not found */
957 si_unlock_signon_list();
958 return PR_FALSE;
961 /* free the data in each node of the specified user node for this URL */
962 if (first) {
964 /* remove the first user */
965 user = static_cast<si_SignonUserStruct *>
966 (url->signonUser_list.ElementAt(0));
968 } else {
970 /* find the specified user */
971 PRInt32 userCount = url->signonUser_list.Count();
972 for (PRInt32 i=0; i<userCount; i++) {
973 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i));
974 PRInt32 dataCount = user->signonData_list.Count();
975 for (PRInt32 ii=0; ii<dataCount; ii++) {
976 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(ii));
977 if (si_CompareEncryptedToCleartext(data->value, userName)) {
978 goto foundUser;
982 si_unlock_signon_list();
983 return PR_FALSE; /* user not found so nothing to remove */
984 foundUser: ;
987 /* free the user node */
988 url->signonUser_list.RemoveElement(user);
989 delete user;
991 /* remove this URL if it contains no more users */
992 if (url->signonUser_list.Count() == 0) {
993 PR_Free(url->passwordRealm);
994 si_signon_list->RemoveElement(url);
995 delete url;
998 /* write out the change to disk */
999 if (save) {
1000 si_signon_list_changed = PR_TRUE;
1001 si_SaveSignonDataLocked("signons", notify);
1004 si_unlock_signon_list();
1005 return PR_TRUE;
1008 nsresult
1009 SINGSIGN_RemoveUser(const char *host, const PRUnichar *user, PRBool notify) {
1010 PRBool rv = si_RemoveUser(host, nsDependentString(user), PR_TRUE, PR_FALSE, notify);
1011 return rv ? NS_OK : NS_ERROR_FAILURE;
1014 nsresult
1015 SINGSIGN_RemoveUserAfterLoginFailure(const char *host, const PRUnichar *user, PRBool notify) {
1016 PRBool rv = si_RemoveUser(host, nsDependentString(user), PR_TRUE, PR_TRUE, notify);
1017 return rv ? NS_OK : NS_ERROR_FAILURE;
1020 static void
1021 si_FreeReject(si_Reject * reject);
1023 nsresult
1024 SINGSIGN_RemoveReject(const char *host) {
1025 si_Reject* reject;
1026 nsresult rv = NS_ERROR_FAILURE;
1028 /* step backwards through all rejects */
1029 si_lock_signon_list();
1030 PRInt32 rejectCount = LIST_COUNT(si_reject_list);
1031 while (rejectCount>0) {
1032 rejectCount--;
1033 reject = static_cast<si_Reject*>(si_reject_list->ElementAt(rejectCount));
1034 if (reject && !PL_strcmp(reject->passwordRealm, host)) {
1035 si_FreeReject(reject);
1036 si_signon_list_changed = PR_TRUE;
1037 rv = NS_OK;
1040 si_SaveSignonDataLocked("rejects", PR_FALSE);
1041 si_unlock_signon_list();
1042 return rv;
1045 static void
1046 si_PutReject(const char * passwordRealm, const nsString& userName, PRBool save);
1048 nsresult
1049 SINGSIGN_AddReject(const char *host /*, const char *userName*/) {
1050 si_PutReject(host, nsString(/*thisParameter_isObsolete*/), PR_TRUE);
1051 // @see http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/extensions/wallet/src/singsign.cpp&rev=1.212&mark=1693#1650
1052 return NS_OK;
1055 /* Determine if a specified url/user exists */
1056 static PRBool
1057 si_CheckForUser(const char *passwordRealm, const nsString& userName) {
1058 si_SignonURLStruct * url;
1059 si_SignonUserStruct * user;
1060 si_SignonDataStruct * data;
1062 /* do nothing if signon preference is not enabled */
1063 if (!si_GetSignonRememberingPref()) {
1064 return PR_FALSE;
1067 si_lock_signon_list();
1069 /* get URL corresponding to passwordRealm */
1070 url = si_GetURL(passwordRealm);
1071 if (!url) {
1072 /* URL not found */
1073 si_unlock_signon_list();
1074 return PR_FALSE;
1077 /* find the specified user */
1078 PRInt32 userCount = url->signonUser_list.Count();
1079 for (PRInt32 i=0; i<userCount; i++) {
1080 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i));
1081 PRInt32 dataCount = user->signonData_list.Count();
1082 for (PRInt32 ii=0; ii<dataCount; ii++) {
1083 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(ii));
1084 if (si_CompareEncryptedToCleartext(data->value, userName)) {
1085 si_unlock_signon_list();
1086 return PR_TRUE;
1090 si_unlock_signon_list();
1091 return PR_FALSE; /* user not found */
1095 * Get first data node that is not a password
1098 static si_SignonDataStruct *
1099 si_GetFirstNonPasswordData(si_SignonUserStruct* user) {
1100 PRInt32 dataCount = user->signonData_list.Count();
1101 for (PRInt32 j=0; j<dataCount; j++) {
1102 si_SignonDataStruct * data =
1103 static_cast<si_SignonDataStruct *>(user->signonData_list.ElementAt(j));
1104 if (!data->isPassword) {
1105 return data;
1108 return nsnull;
1112 * Get the user node for a given URL
1114 * This routine is called only when holding the signon lock!!!
1116 * This routine is called only if signon pref is enabled!!!
1118 static si_SignonUserStruct*
1119 si_GetUser(nsIPrompt* dialog, const char* passwordRealm, const char *legacyRealm,
1120 PRBool pickFirstUser, const nsString& userText, PRUint32 formNumber) {
1121 si_SignonURLStruct* url;
1122 si_SignonUserStruct* user = nsnull;
1123 si_SignonDataStruct* data;
1125 /* get to node for this URL */
1126 url = si_GetCompositeURL(passwordRealm, legacyRealm);
1128 if (url != NULL) {
1130 /* node for this URL was found */
1131 PRInt32 user_count;
1132 if ((user_count = url->signonUser_list.Count()) == 1) {
1134 /* only one set of data exists for this URL so select it */
1135 user = static_cast<si_SignonUserStruct *>
1136 (url->signonUser_list.ElementAt(0));
1137 url->chosen_user = user;
1139 } else if (pickFirstUser) {
1140 PRInt32 userCount = url->signonUser_list.Count();
1141 for (PRInt32 i=0; i<userCount; i++) {
1142 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i));
1143 /* consider first data node to be the identifying item */
1144 data = static_cast<si_SignonDataStruct *>
1145 (user->signonData_list.ElementAt(0));
1146 if (data->name != userText) {
1147 /* name of current data item does not match name in data node */
1148 continue;
1150 break;
1152 url->chosen_user = user;
1154 } else {
1155 /* multiple users for this URL so a choice needs to be made */
1156 PRUnichar ** list;
1157 PRUnichar ** list2;
1158 si_SignonUserStruct** users;
1159 si_SignonUserStruct** users2;
1160 list = (PRUnichar**)PR_Malloc(user_count*sizeof(PRUnichar*));
1161 users = (si_SignonUserStruct **) PR_Malloc(user_count*sizeof(si_SignonUserStruct*));
1162 list2 = list;
1163 users2 = users;
1165 /* step through set of user nodes for this URL and create list of
1166 * first data node of each (presumably that is the user name).
1167 * Note that the user nodes are already ordered by
1168 * most-recently-used so the first one in the list is the most
1169 * likely one to be chosen.
1171 user_count = 0;
1172 PRInt32 userCount = url->signonUser_list.Count();
1173 for (PRInt32 i=0; i<userCount; i++) {
1174 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i));
1175 /* consider first data node to be the identifying item */
1176 data = static_cast<si_SignonDataStruct *>
1177 (user->signonData_list.ElementAt(0));
1178 if (data->name != userText) {
1179 /* name of current data item does not match name in data node */
1180 continue;
1182 nsAutoString userName;
1183 data = si_GetFirstNonPasswordData(user);
1184 if (NS_SUCCEEDED(Wallet_Decrypt (data->value, userName))) {
1185 *(list2++) = ToNewUnicode(userName);
1186 *(users2++) = user;
1187 user_count++;
1188 } else {
1189 break;
1193 /* have user select a username from the list */
1194 PRUnichar * selectUser = Wallet_Localize("SelectUser");
1195 if (user_count == 0) {
1196 /* not first data node for any saved user, so simply pick first user */
1197 if (url->chosen_user) {
1198 user = url->chosen_user;
1199 } else {
1200 /* no user selection had been made for first data node */
1201 user = NULL;
1203 } else if (user_count == 1) {
1204 /* only one user for this form at this url, so select it */
1205 user = users[0];
1206 } else if ((user_count > 1) && si_SelectDialog(selectUser, dialog, list, &user_count, formNumber)) {
1207 /* user pressed OK */
1208 if (user_count == -1) {
1209 user_count = 0; /* user didn't select, so use first one */
1211 user = users[user_count]; /* this is the selected item */
1212 /* item selected is now most-recently used, put at head of list */
1213 url->signonUser_list.RemoveElement(user);
1214 url->signonUser_list.InsertElementAt(user, 0);
1215 } else {
1216 user = NULL;
1218 Recycle(selectUser);
1219 url->chosen_user = user;
1220 while (--list2 > list) {
1221 Recycle(*list2);
1223 PR_Free(list);
1224 PR_Free(users);
1226 /* if we don't remove the URL from the cache at this point, the
1227 * cached copy will be brought containing the last-used username
1228 * rather than the username that was just selected
1231 #ifdef junk
1232 NET_RemoveURLFromCache(NET_CreateURLStruct((char *)passwordRealm, NET_DONT_RELOAD));
1233 #endif
1236 si_ReleaseCompositeURL(url);
1237 } else {
1238 user = NULL;
1240 return user;
1244 * Get a specific user node for a given URL
1246 * This routine is called only when holding the signon lock!!!
1248 * This routine is called only if signon pref is enabled!!!
1250 static si_SignonUserStruct*
1251 si_GetSpecificUser(const char* passwordRealm, const nsString& userName, const nsString& userText) {
1252 si_SignonURLStruct* url;
1253 si_SignonUserStruct* user;
1254 si_SignonDataStruct* data;
1256 /* get to node for this URL */
1257 url = si_GetURL(passwordRealm);
1258 if (url != NULL) {
1260 /* step through set of user nodes for this URL looking for specified username */
1261 PRInt32 userCount2 = url->signonUser_list.Count();
1262 for (PRInt32 i2=0; i2<userCount2; i2++) {
1263 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i2));
1264 /* consider first data node to be the identifying item */
1265 data = static_cast<si_SignonDataStruct *>
1266 (user->signonData_list.ElementAt(0));
1267 if (data->name != userText) {
1268 /* desired username text does not match name in data node */
1269 continue;
1271 if (!si_CompareEncryptedToCleartext(data->value, userName)) {
1272 /* desired username value does not match value in data node */
1273 continue;
1275 return user;
1278 /* if we don't remove the URL from the cache at this point, the
1279 * cached copy will be brought containing the last-used username
1280 * rather than the username that was just selected
1283 #ifdef junk
1284 NET_RemoveURLFromCache(NET_CreateURLStruct((char *)passwordRealm, NET_DONT_RELOAD));
1285 #endif
1288 return NULL;
1292 * Get the url and user for which a change-of-password is to be applied
1294 * This routine is called only when holding the signon lock!!!
1296 * This routine is called only if signon pref is enabled!!!
1298 static si_SignonUserStruct*
1299 si_GetURLAndUserForChangeForm(nsIPrompt* dialog, const nsString& password)
1301 si_SignonURLStruct* url;
1302 si_SignonUserStruct* user;
1303 si_SignonDataStruct * data;
1304 PRInt32 user_count;
1306 PRUnichar ** list;
1307 PRUnichar ** list2;
1308 si_SignonUserStruct** users;
1309 si_SignonUserStruct** users2;
1310 si_SignonURLStruct** urls;
1311 si_SignonURLStruct** urls2;
1313 /* get count of total number of user nodes at all url nodes */
1314 user_count = 0;
1315 PRInt32 urlCount = LIST_COUNT(si_signon_list);
1316 for (PRInt32 i=0; i<urlCount; i++) {
1317 url = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(i));
1318 PRInt32 userCount = url->signonUser_list.Count();
1319 for (PRInt32 ii=0; ii<userCount; ii++) {
1320 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(ii));
1321 user_count++;
1325 /* avoid malloc of zero */
1326 if( user_count == 0 )
1328 return NULL;
1331 /* allocate lists for maximumum possible url and user names */
1332 list = (PRUnichar**)PR_Malloc(user_count*sizeof(PRUnichar*));
1333 users = (si_SignonUserStruct **) PR_Malloc(user_count*sizeof(si_SignonUserStruct*));
1334 urls = (si_SignonURLStruct **)PR_Malloc(user_count*sizeof(si_SignonUserStruct*));
1335 list2 = list;
1336 users2 = users;
1337 urls2 = urls;
1339 /* step through set of URLs and users and create list of each */
1340 user_count = 0;
1341 PRInt32 urlCount2 = LIST_COUNT(si_signon_list);
1342 for (PRInt32 i2=0; i2<urlCount2; i2++) {
1343 url = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(i2));
1344 PRInt32 userCount = url->signonUser_list.Count();
1345 for (PRInt32 i3=0; i3<userCount; i3++) {
1346 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i3));
1347 /* find saved password and see if it matches password user just entered */
1348 PRInt32 dataCount = user->signonData_list.Count();
1349 for (PRInt32 i4=0; i4<dataCount; i4++) {
1350 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i4));
1351 if (data->isPassword && si_CompareEncryptedToCleartext(data->value, password)) {
1352 /* passwords match so add entry to list */
1353 /* consider first data node to be the identifying item */
1354 data = static_cast<si_SignonDataStruct *>
1355 (user->signonData_list.ElementAt(0));
1357 nsAutoString userName;
1358 if (NS_SUCCEEDED(Wallet_Decrypt (data->value, userName))) {
1359 nsAutoString temp; temp.AssignASCII(url->passwordRealm); // XXX non-ascii realms?
1360 temp.AppendLiteral(":");
1361 temp.Append(userName);
1363 *list2 = ToNewUnicode(temp);
1364 list2++;
1365 *(users2++) = user;
1366 *(urls2++) = url;
1367 user_count++;
1369 break;
1375 /* query user */
1376 PRUnichar * msg = Wallet_Localize("SelectUserWhosePasswordIsBeingChanged");
1377 //@@@@ is 0 correct?
1378 if (user_count && si_SelectDialog(msg, dialog, list, &user_count, 0)) {
1379 user = users[user_count];
1380 url = urls[user_count];
1382 * since this user node is now the most-recently-used one, move it
1383 * to the head of the user list so that it can be favored for
1384 * re-use the next time this form is encountered
1386 url->signonUser_list.RemoveElement(user);
1387 url->signonUser_list.InsertElementAt(user, 0);
1388 si_signon_list_changed = PR_TRUE;
1389 si_SaveSignonDataLocked("signons", PR_TRUE);
1390 } else {
1391 user = NULL;
1393 Recycle(msg);
1395 /* free allocated strings */
1396 while (--list2 > list) {
1397 Recycle(*list2);
1399 PR_Free(list);
1400 PR_Free(users);
1401 PR_Free(urls);
1402 return user;
1406 * Remove all the signons and free everything
1409 void
1410 SI_RemoveAllSignonData() {
1411 if (si_PartiallyLoaded) {
1412 /* repeatedly remove first user node of first URL node */
1413 while (si_RemoveUser(NULL, EmptyString(), PR_FALSE, PR_FALSE, PR_FALSE, PR_TRUE)) {
1416 si_PartiallyLoaded = PR_FALSE;
1418 if (si_reject_list) {
1419 si_Reject * reject;
1420 while (LIST_COUNT(si_reject_list)>0) {
1421 reject = static_cast<si_Reject*>(si_reject_list->ElementAt(0));
1422 if (reject) {
1423 si_FreeReject(reject);
1424 si_signon_list_changed = PR_TRUE;
1427 delete si_reject_list;
1428 si_reject_list = nsnull;
1430 delete si_signon_list;
1431 si_signon_list = nsnull;
1434 void
1435 SI_DeleteAll() {
1436 if (si_PartiallyLoaded) {
1437 /* repeatedly remove first user node of first URL node */
1438 while (si_RemoveUser(NULL, EmptyString(), PR_FALSE, PR_FALSE, PR_TRUE, PR_TRUE)) {
1441 si_PartiallyLoaded = PR_FALSE;
1442 si_signon_list_changed = PR_TRUE;
1443 si_SaveSignonDataLocked("signons", PR_TRUE);
1446 void
1447 SI_ClearUserData() {
1448 SI_RemoveAllSignonData();
1449 gLoadedUserData = PR_FALSE;
1452 void
1453 SI_DeletePersistentUserData() {
1455 if (signonFileName && signonFileName[0]) {
1456 nsCOMPtr<nsIFile> file;
1457 nsresult rv = Wallet_ProfileDirectory(getter_AddRefs(file));
1458 if (NS_SUCCEEDED(rv)) {
1459 rv = file->AppendNative(nsDependentCString(signonFileName));
1460 if (NS_SUCCEEDED(rv))
1461 file->Remove(PR_FALSE);
1466 /****************************
1467 * Managing the Reject List *
1468 ****************************/
1470 static void
1471 si_FreeReject(si_Reject * reject) {
1474 * This routine should only be called while holding the
1475 * signon list lock
1478 if(!reject) {
1479 return;
1481 si_reject_list->RemoveElement(reject);
1482 PR_FREEIF(reject->passwordRealm);
1483 delete reject;
1486 static PRBool
1487 si_CheckForReject(const char * passwordRealm, const nsString& userName) {
1488 si_Reject * reject;
1490 si_lock_signon_list();
1491 if (si_reject_list) {
1492 PRInt32 rejectCount = LIST_COUNT(si_reject_list);
1493 for (PRInt32 i=0; i<rejectCount; i++) {
1494 reject = static_cast<si_Reject*>(si_reject_list->ElementAt(i));
1495 if(!PL_strcmp(passwordRealm, reject->passwordRealm)) {
1496 // No need for username check on a rejectlist entry. URL check is sufficient
1497 // if(!PL_strcmp(userName, reject->userName) && !PL_strcmp(passwordRealm, reject->passwordRealm)) {
1498 si_unlock_signon_list();
1499 return PR_TRUE;
1503 si_unlock_signon_list();
1504 return PR_FALSE;
1507 static void
1508 si_PutReject(const char * passwordRealm, const nsString& userName, PRBool save) {
1509 char * passwordRealm2=NULL;
1510 nsAutoString userName2;
1511 si_Reject * reject = new si_Reject;
1513 if (reject) {
1514 if(!si_reject_list) {
1515 si_reject_list = new nsVoidArray();
1516 if(!si_reject_list) {
1517 delete reject;
1518 return;
1523 * lock the signon list
1524 * Note that, for efficiency, SI_LoadSignonData already sets the lock
1525 * before calling this routine whereas none of the other callers do.
1526 * So we need to determine whether or not we were called from
1527 * SI_LoadSignonData before setting or clearing the lock. We can
1528 * determine this by testing "save" since only SI_LoadSignonData
1529 * passes in a value of PR_FALSE for "save".
1531 if (save) {
1532 si_lock_signon_list();
1535 StrAllocCopy(passwordRealm2, passwordRealm);
1536 userName2 = userName;
1537 reject->passwordRealm = passwordRealm2;
1538 reject->userName = userName2;
1540 #ifdef alphabetize
1541 /* add it to the list in alphabetical order */
1542 si_Reject * tmp_reject;
1543 PRBool rejectAdded = PR_FALSE;
1544 PRInt32 rejectCount = LIST_COUNT(si_reject_list);
1545 for (PRInt32 i = 0; i<rejectCount; ++i) {
1546 tmp_reject = static_cast<si_Reject *>(si_reject_list->ElementAt(i));
1547 if (tmp_reject) {
1548 if (PL_strcasecmp(reject->passwordRealm, tmp_reject->passwordRealm)<0) {
1549 si_reject_list->InsertElementAt(reject, i);
1550 rejectAdded = PR_TRUE;
1551 break;
1555 if (!rejectAdded) {
1556 si_reject_list->AppendElement(reject);
1558 #else
1559 /* add it to the end of the list */
1560 si_reject_list->AppendElement(reject);
1561 #endif
1563 if (save) {
1564 si_signon_list_changed = PR_TRUE;
1565 si_lock_signon_list();
1566 si_SaveSignonDataLocked("rejects", PR_TRUE);
1567 si_unlock_signon_list();
1573 * Put data obtained from a submit form into the data structure for
1574 * the specified URL
1576 * See comments below about state of signon lock when routine is called!!!
1578 * This routine is called only if signon pref is enabled!!!
1580 static void
1581 si_PutData(const char *passwordRealm, nsVoidArray *signonData, PRBool save) {
1582 PRBool added_to_list = PR_FALSE;
1583 si_SignonURLStruct * url;
1584 si_SignonUserStruct * user;
1585 si_SignonDataStruct * data;
1586 si_SignonDataStruct * data2;
1587 PRBool mismatch = PR_FALSE;
1589 /* discard this if the password is empty */
1590 PRInt32 count = signonData->Count();
1591 for (PRInt32 i=0; i<count; i++) {
1592 data2 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(i));
1593 if (data2->isPassword && data2->value.IsEmpty()) {
1594 return;
1598 /* make sure the signon list exists */
1599 if (!si_signon_list) {
1600 si_signon_list = new nsVoidArray();
1601 if (!si_signon_list) {
1602 return;
1607 * lock the signon list
1608 * Note that, for efficiency, SI_LoadSignonData already sets the lock
1609 * before calling this routine whereas none of the other callers do.
1610 * So we need to determine whether or not we were called from
1611 * SI_LoadSignonData before setting or clearing the lock. We can
1612 * determine this by testing "save" since only SI_LoadSignonData passes
1613 * in a value of PR_FALSE for "save".
1615 if (save) {
1616 si_lock_signon_list();
1619 /* find node in signon list having the same URL */
1620 if ((url = si_GetURL(passwordRealm)) == NULL) {
1622 /* doesn't exist so allocate new node to be put into signon list */
1623 url = new si_SignonURLStruct;
1624 if (!url) {
1625 if (save) {
1626 si_unlock_signon_list();
1628 return;
1631 /* fill in fields of new node */
1632 url->passwordRealm = nsnull;
1633 if (passwordRealm) {
1634 url->passwordRealm = PL_strdup(passwordRealm);
1637 if (!url->passwordRealm) {
1638 if (save) {
1639 si_unlock_signon_list();
1641 return;
1644 /* put new node into signon list */
1646 #ifdef alphabetize
1647 /* add it to the list in alphabetical order */
1648 si_SignonURLStruct * tmp_URL;
1649 PRInt32 urlCount = LIST_COUNT(si_signon_list);
1650 for (PRInt32 ii = 0; ii<urlCount; ++ii) {
1651 tmp_URL = static_cast<si_SignonURLStruct *>(si_signon_list->ElementAt(ii));
1652 if (tmp_URL) {
1653 if (PL_strcasecmp(url->passwordRealm, tmp_URL->passwordRealm)<0) {
1654 si_signon_list->InsertElementAt(url, ii);
1655 added_to_list = PR_TRUE;
1656 break;
1660 if (!added_to_list) {
1661 si_signon_list->AppendElement(url);
1663 #else
1664 /* add it to the end of the list */
1665 si_signon_list->AppendElement(url);
1666 #endif
1669 /* initialize state variables in URL node */
1670 url->chosen_user = NULL;
1673 * see if a user node with data list matching new data already exists
1674 * (password fields will not be checked for in this matching)
1676 PRInt32 userCount = url->signonUser_list.Count();
1677 for (PRInt32 i2=0; i2<userCount; i2++) {
1678 if (!save) {
1679 break; /* otherwise we could be asked for master password when loading signon data */
1681 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i2));
1682 PRInt32 j = 0;
1683 PRInt32 dataCount = user->signonData_list.Count();
1684 for (PRInt32 i3=0; i3<dataCount; i3++) {
1685 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i3));
1686 mismatch = PR_FALSE;
1688 /* check for match on name field and type field */
1689 if (j < signonData->Count()) {
1690 data2 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(j));
1691 if ((data->isPassword == data2->isPassword) &&
1692 (data->name == data2->name)) {
1694 /* success, now check for match on value field if not password */
1695 if (si_CompareEncryptedToEncrypted(data->value, data2->value) || data->isPassword) {
1696 j++; /* success */
1697 } else {
1698 mismatch = PR_TRUE;
1699 break; /* value mismatch, try next user */
1701 } else {
1702 mismatch = PR_TRUE;
1703 break; /* name or type mismatch, try next user */
1707 if (!mismatch) {
1709 /* all names and types matched and all non-password values matched */
1712 * note: it is ok for password values not to match; it means
1713 * that the user has either changed his password behind our
1714 * back or that he previously mis-entered the password
1717 /* update the saved password values */
1718 j = 0;
1719 PRInt32 dataCount2 = user->signonData_list.Count();
1720 for (PRInt32 i4=0; i4<dataCount2; i4++) {
1721 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i4));
1723 /* update saved password */
1724 if ((j < signonData->Count()) && data->isPassword) {
1725 data2 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(j));
1726 if (!si_CompareEncryptedToEncrypted(data->value, data2->value)) {
1727 si_signon_list_changed = PR_TRUE;
1728 data->value = data2->value;
1729 user->time = SecondsFromPRTime(PR_Now());
1730 /* commenting out because I don't see how such randomizing could ever have worked. */
1731 // si_Randomize(data->value);
1734 j++;
1738 * since this user node is now the most-recently-used one, move it
1739 * to the head of the user list so that it can be favored for
1740 * re-use the next time this form is encountered
1742 url->signonUser_list.RemoveElement(user);
1743 url->signonUser_list.InsertElementAt(user, 0);
1745 /* return */
1746 if (save) {
1747 si_signon_list_changed = PR_TRUE;
1748 si_SaveSignonDataLocked("signons", PR_TRUE);
1749 si_unlock_signon_list();
1751 return; /* nothing more to do since data already exists */
1755 /* user node with current data not found so create one */
1756 user = new si_SignonUserStruct;
1757 if (!user) {
1758 if (save) {
1759 si_unlock_signon_list();
1761 return;
1764 /* create and fill in data nodes for new user node */
1765 for (PRInt32 k=0; k<signonData->Count(); k++) {
1767 /* create signon data node */
1768 data = new si_SignonDataStruct;
1769 if (!data) {
1770 delete user;
1771 if (save) {
1772 si_unlock_signon_list();
1774 return;
1776 data2 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(k));
1777 data->isPassword = data2->isPassword;
1778 data->name = data2->name;
1779 data->value = data2->value;
1780 /* commenting out because I don't see how such randomizing could ever have worked. */
1781 // if (data->isPassword) {
1782 // si_Randomize(data->value);
1783 // }
1784 /* append new data node to end of data list */
1785 user->signonData_list.AppendElement(data);
1788 /* append new user node to front of user list for matching URL */
1790 * Note that by appending to the front, we assure that if there are
1791 * several users, the most recently used one will be favored for
1792 * reuse the next time this form is encountered. But don't do this
1793 * when reading in the saved signons (i.e., when save is PR_FALSE), otherwise
1794 * we will be reversing the order when reading in.
1796 if (save) {
1797 user->time = SecondsFromPRTime(PR_Now());
1798 url->signonUser_list.InsertElementAt(user, 0);
1799 si_signon_list_changed = PR_TRUE;
1800 si_SaveSignonDataLocked("signons", PR_TRUE);
1801 si_unlock_signon_list();
1802 } else {
1803 user->time = 0;
1804 url->signonUser_list.AppendElement(user);
1809 /*****************************
1810 * Managing the Signon Files *
1811 *****************************/
1813 ////////////////////////////////////////////////////////////////////////////////
1814 // nsSingleSignOnProfileObserver
1815 // This observer is a global object and is registered the first time any consumer
1816 // touches signon profile data. That is, when SI_LoadSignonData() is called.
1818 class nsSingleSignOnProfileObserver : public nsIObserver
1820 public:
1821 nsSingleSignOnProfileObserver() { }
1822 virtual ~nsSingleSignOnProfileObserver() {}
1824 NS_DECL_ISUPPORTS
1826 NS_IMETHODIMP Observe(nsISupports*, const char *aTopic, const PRUnichar *someData)
1828 if (!strcmp(aTopic, "profile-before-change")) {
1829 SI_ClearUserData();
1830 if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get()))
1831 SI_DeletePersistentUserData();
1833 return NS_OK;
1836 NS_IMPL_THREADSAFE_ISUPPORTS1(nsSingleSignOnProfileObserver, nsIObserver)
1838 static nsresult EnsureSingleSignOnProfileObserver()
1840 static nsSingleSignOnProfileObserver *gSignOnProfileObserver;
1842 if (!gSignOnProfileObserver) {
1843 nsCOMPtr<nsIObserverService> observerService(do_GetService("@mozilla.org/observer-service;1"));
1844 if (!observerService)
1845 return NS_ERROR_FAILURE;
1847 gSignOnProfileObserver = new nsSingleSignOnProfileObserver;
1848 if (!gSignOnProfileObserver)
1849 return NS_ERROR_OUT_OF_MEMORY;
1851 // The observer service holds the only ref to the observer
1852 // It thus has the lifespan of the observer service
1853 nsresult rv = observerService->AddObserver(gSignOnProfileObserver, "profile-before-change", PR_FALSE);
1854 if (NS_FAILED(rv)) {
1855 delete gSignOnProfileObserver;
1856 gSignOnProfileObserver = nsnull;
1857 return rv;
1860 return NS_OK;
1863 #define BUFFER_SIZE 4096
1866 * get a line from a file
1867 * return -1 if end of file reached
1868 * strip carriage returns and line feeds from end of line
1870 static PRInt32
1871 si_ReadLine(nsIInputStream* strm, nsString& lineBuffer)
1873 nsCAutoString line;
1874 nsresult rv = wallet_GetLine(strm, line);
1875 if (NS_FAILED(rv))
1876 return -1;
1878 CopyUTF8toUTF16(line, lineBuffer);
1879 return NS_OK;
1883 * Load signon data from disk file
1884 * Return value is:
1885 * -1: fatal error
1886 * 0: successfully load
1887 * +1: user aborted the load (by failing to open the database)
1890 SI_LoadSignonData() {
1891 char * passwordRealm;
1892 nsAutoString buffer;
1893 PRBool badInput = PR_FALSE;
1895 #ifdef APPLE_KEYCHAIN
1896 if (KeychainManagerAvailable()) {
1897 SI_RemoveAllSignonData();
1898 return si_LoadSignonDataFromKeychain();
1900 #endif
1902 /* open the signon file */
1903 nsCOMPtr<nsIFile> file;
1904 nsresult rv = Wallet_ProfileDirectory(getter_AddRefs(file));
1905 if (NS_FAILED(rv)) {
1906 return -1;
1910 rv = EnsureSingleSignOnProfileObserver();
1911 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to register profile change observer");
1913 SI_InitSignonFileName();
1914 file->AppendNative(nsDependentCString(signonFileName));
1916 nsCOMPtr<nsIInputStream> strm;
1917 rv = NS_NewLocalFileInputStream(getter_AddRefs(strm), file);
1918 if (NS_FAILED(rv)) {
1919 si_PartiallyLoaded = PR_TRUE;
1920 return 0;
1923 SI_RemoveAllSignonData();
1925 /* read the format information */
1926 nsAutoString format;
1927 if (NS_FAILED(si_ReadLine(strm, format))) {
1928 return -1;
1930 if (!format.EqualsLiteral(HEADER_VERSION)) {
1931 /* something's wrong */
1932 return -1;
1935 /* read the reject list */
1936 si_lock_signon_list();
1937 while (NS_SUCCEEDED(si_ReadLine(strm, buffer))) {
1938 /* a blank line is perfectly valid here -- corresponds to a local file */
1939 if (!buffer.IsEmpty() && buffer.CharAt(0) == '.') {
1940 break; /* end of reject list */
1942 passwordRealm = ToNewCString(buffer);
1943 si_PutReject(passwordRealm, buffer, PR_FALSE); /* middle parameter is obsolete */
1944 Recycle (passwordRealm);
1947 /* read the URL line */
1948 while (NS_SUCCEEDED(si_ReadLine(strm, buffer))) {
1949 /* a blank line is perfectly valid here -- corresponds to a local file */
1950 passwordRealm = ToNewCString(buffer);
1951 if (!passwordRealm) {
1952 si_unlock_signon_list();
1953 return -1;
1956 /* prepare to read the name/value pairs */
1957 badInput = PR_FALSE;
1959 nsVoidArray signonData;
1960 si_SignonDataStruct * data;
1961 while(NS_SUCCEEDED(si_ReadLine(strm, buffer))) {
1963 /* line starting with . terminates the pairs for this URL entry */
1964 if (buffer.CharAt(0) == '.') {
1965 break; /* end of URL entry */
1968 /* line just read is the name part */
1970 /* save the name part and determine if it is a password */
1971 PRBool ret;
1972 nsAutoString name;
1973 nsAutoString value;
1974 PRBool isPassword;
1975 if (buffer.CharAt(0) == '*') {
1976 isPassword = PR_TRUE;
1977 buffer.Mid(name, 1, buffer.Length()-1);
1978 ret = si_ReadLine(strm, buffer);
1979 } else {
1980 isPassword = PR_FALSE;
1981 name = buffer;
1982 ret = si_ReadLine(strm, buffer);
1985 /* read in and save the value part */
1986 if(NS_FAILED(ret)) {
1987 /* error in input file so give up */
1988 badInput = PR_TRUE;
1989 break;
1991 value = buffer;
1993 data = new si_SignonDataStruct;
1994 data->name = name;
1995 data->value = value;
1996 data->isPassword = isPassword;
1997 signonData.AppendElement(data);
2000 /* store the info for this URL into memory-resident data structure */
2001 PRInt32 count = signonData.Count();
2002 if (count) {
2003 si_PutData(passwordRealm, &signonData, PR_FALSE);
2006 /* free up all the allocations done for processing this URL */
2007 Recycle(passwordRealm);
2008 for (PRInt32 i=count-1; i>=0; i--) {
2009 data = static_cast<si_SignonDataStruct*>(signonData.ElementAt(i));
2010 delete data;
2014 si_unlock_signon_list();
2015 si_PartiallyLoaded = PR_TRUE;
2016 return 0;
2020 * Save signon data to disk file
2021 * The parameter passed in on entry is ignored
2023 * This routine is called only when holding the signon lock!!!
2025 * This routine is called only if signon pref is enabled!!!
2028 static int
2029 si_SaveSignonDataLocked(char * state, PRBool notify) {
2030 si_SignonURLStruct * url;
2031 si_SignonUserStruct * user;
2032 si_SignonDataStruct * data;
2033 si_Reject * reject;
2035 /* do nothing if signon list has not changed */
2036 if(!si_signon_list_changed) {
2037 return(-1);
2040 #ifdef APPLE_KEYCHAIN
2041 if (KeychainManagerAvailable()) {
2042 return si_SaveSignonDataInKeychain();
2044 #endif
2046 /* do nothing if we are unable to open file that contains signon list */
2047 nsCOMPtr<nsIFile> file;
2048 nsresult rv = Wallet_ProfileDirectory(getter_AddRefs(file));
2049 if (NS_FAILED(rv)) {
2050 return 0;
2053 file->AppendNative(nsDependentCString(signonFileName));
2055 nsCOMPtr<nsIOutputStream> fileOutputStream;
2056 rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(fileOutputStream),
2057 file,
2059 0600);
2060 if (NS_FAILED(rv))
2061 return 0;
2063 nsCOMPtr<nsIOutputStream> strm;
2064 rv = NS_NewBufferedOutputStream(getter_AddRefs(strm), fileOutputStream, 4096);
2065 if (NS_FAILED(rv))
2066 return 0;
2068 /* write out the format revision number */
2070 wallet_PutLine(strm, HEADER_VERSION);
2072 /* format for next part of file shall be:
2073 * passwordRealm -- first url/username on reject list
2074 * userName
2075 * passwordRealm -- second url/username on reject list
2076 * userName
2077 * ... -- etc.
2078 * . -- end of list
2081 /* write out reject list */
2082 if (si_reject_list) {
2083 PRInt32 rejectCount = LIST_COUNT(si_reject_list);
2084 for (PRInt32 i=0; i<rejectCount; i++) {
2085 reject = static_cast<si_Reject*>(si_reject_list->ElementAt(i));
2086 wallet_PutLine(strm, reject->passwordRealm);
2089 wallet_PutLine(strm, ".");
2091 /* format for cached logins shall be:
2092 * url LINEBREAK {name LINEBREAK value LINEBREAK}* . LINEBREAK
2093 * if type is password, name is preceded by an asterisk (*)
2096 /* write out each URL node */
2097 if((si_signon_list)) {
2098 PRInt32 urlCount = LIST_COUNT(si_signon_list);
2099 for (PRInt32 i2=0; i2<urlCount; i2++) {
2100 url = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(i2));
2102 /* write out each user node of the URL node */
2103 PRInt32 userCount = url->signonUser_list.Count();
2104 for (PRInt32 i3=0; i3<userCount; i3++) {
2105 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(i3));
2106 wallet_PutLine(strm, url->passwordRealm);
2108 /* write out each data node of the user node */
2109 PRInt32 dataCount = user->signonData_list.Count();
2110 for (PRInt32 i4=0; i4<dataCount; i4++) {
2111 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i4));
2112 if (data->isPassword) {
2113 static const char asterisk = '*';
2114 PRUint32 dummy;
2115 strm->Write(&asterisk, 1, &dummy);
2117 wallet_PutLine(strm, NS_ConvertUTF16toUTF8(data->name).get());
2118 wallet_PutLine(strm, NS_ConvertUTF16toUTF8(data->value).get());
2120 wallet_PutLine(strm, ".");
2124 si_signon_list_changed = PR_FALSE;
2126 // All went ok. Maybe except for problems in Write(), but the stream detects
2127 // that for us
2128 nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(strm);
2129 NS_ASSERTION(safeStream, "expected a safe output stream!");
2130 if (safeStream) {
2131 rv = safeStream->Finish();
2132 if (NS_FAILED(rv)) {
2133 NS_WARNING("failed to save wallet file! possible dataloss");
2134 return 0;
2137 strm = nsnull;
2138 fileOutputStream = nsnull;
2141 /* Notify signon manager dialog to update its display */
2142 if (notify) {
2143 nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
2144 if (os) {
2145 os->NotifyObservers(nsnull, "signonChanged", NS_ConvertASCIItoUTF16(state).get());
2149 return 0;
2153 /***************************
2154 * Processing Signon Forms *
2155 ***************************/
2157 static PRBool
2158 si_ExtractRealm(nsIURI *uri, nsCString &realm)
2160 nsCAutoString hostPort;
2162 /* Security check: if URI is of a scheme that doesn't support hostnames,
2163 * we have no host to get the signon data from, so we must not attempt to
2164 * build a valid realm from the URI (bug 159484) */
2165 nsresult rv = uri->GetHostPort(hostPort);
2166 if (NS_FAILED(rv) || hostPort.IsEmpty())
2167 return PR_FALSE;
2169 nsCAutoString scheme;
2170 rv = uri->GetScheme(scheme);
2171 if (NS_FAILED(rv) || scheme.IsEmpty())
2172 return PR_FALSE;
2174 realm = scheme + NS_LITERAL_CSTRING("://") + hostPort;
2175 return PR_TRUE;
2178 /* Ask user if it is ok to save the signon data */
2179 static PRBool
2180 si_OkToSave(const char *passwordRealm, const char *legacyRealm,
2181 const nsString& userName, nsIDOMWindowInternal* window) {
2183 /* if url/user already exists, then it is safe to save it again */
2184 if (si_CheckForUser(passwordRealm, userName)) {
2185 return PR_TRUE;
2187 if (legacyRealm && si_CheckForUser(legacyRealm, userName)) {
2188 return PR_TRUE;
2191 #ifdef WALLET_PASSWORDMANAGER_DEFAULT_IS_OFF
2192 if (!si_RememberSignons && !si_GetNotificationPref()) {
2193 PRUnichar * notification = Wallet_Localize("PasswordNotification");
2194 si_SetNotificationPref(PR_TRUE);
2195 if (!si_ConfirmYN(notification, window)) {
2196 Recycle(notification);
2197 SI_SetBoolPref(pref_rememberSignons, PR_FALSE);
2198 return PR_FALSE;
2200 Recycle(notification);
2201 SI_SetBoolPref(pref_rememberSignons, PR_TRUE);
2203 #endif
2205 if (si_CheckForReject(passwordRealm, userName)) {
2206 return PR_FALSE;
2208 if (legacyRealm && si_CheckForReject(legacyRealm, userName)) {
2209 return PR_FALSE;
2212 PRUnichar * message;
2213 if (SI_GetBoolPref(pref_Crypto, PR_FALSE)) {
2214 message = Wallet_Localize("WantToSavePasswordEncrypted?");
2215 } else {
2216 message = Wallet_Localize("WantToSavePasswordObscured?");
2219 PRInt32 button = si_3ButtonConfirm(message, window);
2220 if (button == NEVER_BUTTON) {
2221 si_PutReject(passwordRealm, userName, PR_TRUE);
2223 Recycle(message);
2224 return (button == YES_BUTTON);
2228 * Check for a signon submission and remember the data if so
2230 static void
2231 si_RememberSignonData
2232 (nsIPrompt* dialog, const char* passwordRealm, const char* legacyRealm,
2233 nsVoidArray * signonData, nsIDOMWindowInternal* window)
2235 int passwordCount = 0;
2236 int pswd[3];
2237 si_SignonDataStruct * data = nsnull; // initialization only to avoid warning message
2238 si_SignonDataStruct * data0;
2239 si_SignonDataStruct * data1;
2240 si_SignonDataStruct * data2;
2242 /* do nothing if signon preference is not enabled */
2243 if (!si_GetSignonRememberingPref()){
2244 return;
2247 /* determine how many passwords are in the form and where they are */
2248 for (PRInt32 i=0; i<signonData->Count(); i++) {
2249 data = static_cast<si_SignonDataStruct*>(signonData->ElementAt(i));
2250 if (data->isPassword) {
2251 if (passwordCount < 3 ) {
2252 pswd[passwordCount] = i;
2254 passwordCount++;
2258 /* process the form according to how many passwords it has */
2259 if (passwordCount == 1) {
2260 /* one-password form is a log-in so remember it */
2262 /* obtain the index of the first input field (that is the username) */
2263 PRInt32 j;
2264 for (j=0; j<signonData->Count(); j++) {
2265 data = static_cast<si_SignonDataStruct*>(signonData->ElementAt(j));
2266 if (!data->isPassword) {
2267 break;
2271 if (j<signonData->Count()) {
2272 if (si_OkToSave(passwordRealm, legacyRealm, data->value, window)) {
2273 // remove legacy password entry if found
2274 if (legacyRealm && si_CheckForUser(legacyRealm, data->value)) {
2275 si_RemoveUser(legacyRealm, data->value, PR_TRUE, PR_FALSE, PR_TRUE);
2277 Wallet_GiveCaveat(window, nsnull);
2278 for (j=0; j<signonData->Count(); j++) {
2279 data2 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(j));
2280 nsAutoString value(data2->value);
2281 if (NS_FAILED(Wallet_Encrypt(value, data2->value))) {
2282 return;
2285 si_PutData(passwordRealm, signonData, PR_TRUE);
2288 } else if (passwordCount == 2) {
2289 /* two-password form is a registration */
2291 } else if (passwordCount == 3) {
2292 /* three-password form is a change-of-password request */
2294 si_SignonUserStruct* user;
2296 /* make sure all passwords are non-null and 2nd and 3rd are identical */
2297 data0 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(pswd[0]));
2298 data1 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(pswd[1]));
2299 data2 = static_cast<si_SignonDataStruct*>(signonData->ElementAt(pswd[2]));
2300 if (data0->value.IsEmpty() || data1->value.IsEmpty() ||
2301 data2->value.IsEmpty() || data1->value != data2->value) {
2302 return;
2305 /* ask user if this is a password change */
2306 si_lock_signon_list();
2307 user = si_GetURLAndUserForChangeForm(dialog, data0->value);
2309 /* return if user said no */
2310 if (!user) {
2311 si_unlock_signon_list();
2312 return;
2315 /* get to password being saved */
2316 PRInt32 dataCount = user->signonData_list.Count();
2317 for (PRInt32 k=0; k<dataCount; k++) {
2318 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(k));
2319 if (data->isPassword) {
2320 break;
2325 * if second password is "********" then generate a random
2326 * password for it and use same random value for third password
2327 * as well (Note: this all works because we already know that
2328 * second and third passwords are identical so third password
2329 * must also be "********". Furthermore si_Randomize() will
2330 * create a random password of exactly eight characters -- the
2331 * same length as "********".)
2334 /* commenting out because I don't see how such randomizing could ever have worked. */
2335 // si_Randomize(data1->value);
2336 // data2->value = data1->value;
2338 if (NS_SUCCEEDED(Wallet_Encrypt(data1->value, data->value))) {
2339 user->time = SecondsFromPRTime(PR_Now());
2340 si_signon_list_changed = PR_TRUE;
2341 si_SaveSignonDataLocked("signons", PR_TRUE);
2342 si_unlock_signon_list();
2347 void
2348 SI_ShutdownModule()
2350 if(signon_lock_monitor) {
2351 PR_DestroyMonitor(signon_lock_monitor);
2352 signon_lock_monitor = nsnull;
2356 void
2357 SINGSIGN_RememberSignonData
2358 (nsIPrompt* dialog, nsIURI* passwordRealm, nsVoidArray * signonData,
2359 nsIDOMWindowInternal* window)
2361 if (!passwordRealm)
2362 return;
2364 nsCAutoString realm, legacyRealm;
2365 if (!si_ExtractRealm(passwordRealm, realm))
2366 return;
2368 if (NS_FAILED(passwordRealm->GetHost(legacyRealm)))
2369 return;
2371 if (!realm.IsEmpty()) {
2372 si_RememberSignonData(dialog, realm.get(), legacyRealm.get(), signonData, window);
2376 static void
2377 si_RestoreSignonData(nsIPrompt* dialog,
2378 const char* passwordRealm, const char* legacyRealm,
2379 const PRUnichar* name, PRUnichar** value,
2380 PRUint32 formNumber, PRUint32 elementNumber) {
2381 si_SignonUserStruct* user;
2382 si_SignonDataStruct* data;
2383 nsAutoString correctedName;
2385 /* do nothing if signon preference is not enabled */
2386 if (!si_GetSignonRememberingPref()){
2387 return;
2390 si_lock_signon_list();
2391 if (elementNumber == 0) {
2392 si_LastFormForWhichUserHasBeenSelected = -1;
2395 /* Correct the field name to avoid mistaking for fields in browser-generated form
2397 * Note that data saved for browser-generated logins (e.g. http authentication)
2398 * use artificial field names starting with * \= (see USERNAMEFIELD and PASSWORDFIELD.
2399 * To avoid mistakes whereby saved logins for http authentication is then prefilled
2400 * into a field on the html form at the same URL, we will prevent html field names
2401 * from starting with \=. We do that by doubling up a backslash if it appears in the
2402 * first character position
2404 if (*name == '\\') {
2405 correctedName = NS_LITERAL_STRING("\\") + nsDependentString(name);
2406 } else {
2407 correctedName = name;
2410 /* determine if name has been saved (avoids unlocking the database if not) */
2411 PRBool nameFound = PR_FALSE;
2412 user = si_GetUser(dialog, passwordRealm, legacyRealm, PR_FALSE, correctedName, formNumber);
2413 if (user) {
2414 PRInt32 dataCount = user->signonData_list.Count();
2415 for (PRInt32 i=0; i<dataCount; i++) {
2416 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i));
2417 LOG((" got [name=%s value=%s]\n",
2418 NS_LossyConvertUTF16toASCII(data->name).get(),
2419 NS_LossyConvertUTF16toASCII(data->value).get()));
2420 if(!correctedName.IsEmpty() && (data->name == correctedName)) {
2421 nameFound = PR_TRUE;
2425 if (!nameFound) {
2426 si_unlock_signon_list();
2427 return;
2430 #ifdef xxx
2432 * determine if it is a change-of-password field
2433 * the heuristic that we will use is that if this is the first
2434 * item on the form and it is a password, this is probably a
2435 * change-of-password form
2437 /* see if this is first item in form and is a password */
2438 /* get first saved user just so we can see the name of the first item on the form */
2439 user = si_GetUser(passwordRealm, PR_TRUE, NULL, formNumber); /* this is the first saved user */
2440 if (user) {
2441 data = static_cast<si_SignonDataStruct *>
2442 (user->signonData_list.ElementAt(0)); /* 1st item on form */
2443 if(data->isPassword && !correctedName.IsEmpty() && (data->name == correctedName)) {
2444 /* current item is first item on form and is a password */
2445 user = (passwordRealm, MK_SIGNON_PASSWORDS_FETCH);
2446 if (user) {
2447 /* user has confirmed it's a change-of-password form */
2448 PRInt32 dataCount = user->signonData_list.Count();
2449 for (PRInt32 i=1; i<dataCount; i++) {
2450 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i));
2451 if (data->isPassword) {
2452 nsAutoString password;
2453 if (NS_SUCCEEDED(Wallet_Decrypt(data->value, password))) {
2454 *value = ToNewUnicode(password);
2456 si_unlock_signon_list();
2457 return;
2463 #endif
2465 /* restore the data from previous time this URL was visited */
2467 user = si_GetUser(dialog, passwordRealm, legacyRealm, PR_FALSE, correctedName, formNumber);
2468 if (user) {
2469 PRInt32 dataCount = user->signonData_list.Count();
2470 for (PRInt32 i=0; i<dataCount; i++) {
2471 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i));
2472 LOG((" got [name=%s value=%s]\n",
2473 NS_LossyConvertUTF16toASCII(data->name).get(),
2474 NS_LossyConvertUTF16toASCII(data->value).get()));
2475 if(!correctedName.IsEmpty() && (data->name == correctedName)) {
2476 nsAutoString password;
2477 if (NS_SUCCEEDED(Wallet_Decrypt(data->value, password))) {
2478 *value = ToNewUnicode(password);
2480 si_unlock_signon_list();
2481 return;
2485 si_unlock_signon_list();
2488 void
2489 SINGSIGN_RestoreSignonData(nsIPrompt* dialog, nsIURI* passwordRealm, const PRUnichar* name, PRUnichar** value, PRUint32 formNumber, PRUint32 elementNumber) {
2490 LOG(("enter SINGSIGN_RestoreSignonData\n"));
2492 if (!passwordRealm)
2493 return;
2495 nsCAutoString realm;
2496 if (!si_ExtractRealm(passwordRealm, realm))
2497 return;
2499 nsCAutoString legacyRealm;
2500 if (NS_FAILED(passwordRealm->GetHost(legacyRealm)))
2501 return;
2503 si_RestoreSignonData(dialog, realm.get(), legacyRealm.get(), name, value, formNumber, elementNumber);
2505 LOG(("exit SINGSIGN_RestoreSignonData [value=%s]\n",
2506 *value ? NS_LossyConvertUTF16toASCII(*value).get() : "(null)"));
2510 * Remember signon data from a browser-generated password dialog
2512 static void
2513 si_RememberSignonDataFromBrowser(const char* passwordRealm, const nsString& username, const nsString& password) {
2514 /* do nothing if signon preference is not enabled */
2515 if (!si_GetSignonRememberingPref()){
2516 return;
2519 nsVoidArray signonData;
2520 si_SignonDataStruct data1;
2521 data1.name.AssignLiteral(USERNAMEFIELD);
2522 if (NS_FAILED(Wallet_Encrypt(username, data1.value))) {
2523 return;
2525 data1.isPassword = PR_FALSE;
2526 signonData.AppendElement(&data1);
2527 si_SignonDataStruct data2;
2528 data2.name.AssignLiteral(PASSWORDFIELD);
2529 if (NS_FAILED(Wallet_Encrypt(password, data2.value))) {
2530 return;
2532 data2.isPassword = PR_TRUE;
2533 signonData.AppendElement(&data2);
2535 /* Save the signon data */
2536 si_PutData(passwordRealm, &signonData, PR_TRUE);
2540 * Check for remembered data from a previous browser-generated password dialog
2541 * restore it if so
2543 static PRBool
2544 si_RestoreOldSignonDataFromBrowser
2545 (nsIPrompt* dialog, const char* passwordRealm, PRBool pickFirstUser, nsString& username, nsString& password) {
2546 si_SignonUserStruct* user;
2547 si_SignonDataStruct* data;
2549 /* get the data from previous time this URL was visited */
2550 si_lock_signon_list();
2551 if (!username.IsEmpty()) {
2552 user = si_GetSpecificUser(passwordRealm, username, NS_ConvertASCIItoUTF16(USERNAMEFIELD));
2553 } else {
2554 si_LastFormForWhichUserHasBeenSelected = -1;
2555 user = si_GetUser(dialog, passwordRealm, nsnull, pickFirstUser, NS_ConvertASCIItoUTF16(USERNAMEFIELD), 0);
2557 if (!user) {
2558 /* leave original username and password from caller unchanged */
2559 /* username = 0; */
2560 /* *password = 0; */
2561 si_unlock_signon_list();
2562 return PR_FALSE;
2565 /* restore the data from previous time this URL was visited */
2566 PRInt32 dataCount = user->signonData_list.Count();
2567 for (PRInt32 i=0; i<dataCount; i++) {
2568 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i));
2569 nsAutoString decrypted;
2570 if (NS_SUCCEEDED(Wallet_Decrypt(data->value, decrypted))) {
2571 if(data->name.EqualsLiteral(USERNAMEFIELD)) {
2572 username = decrypted;
2573 } else if(data->name.EqualsLiteral(PASSWORDFIELD)) {
2574 password = decrypted;
2578 si_unlock_signon_list();
2579 return PR_TRUE;
2582 PRBool
2583 SINGSIGN_StorePassword(const char *passwordRealm, const PRUnichar *user, const PRUnichar *password)
2585 // Wallet_GiveCaveat(nsnull, dialog); ??? what value to use for dialog?
2586 si_RememberSignonDataFromBrowser(passwordRealm, nsDependentString(user), nsDependentString(password));
2587 return PR_TRUE;
2590 enum DialogType {promptUsernameAndPassword, promptPassword, prompt};
2592 static nsresult
2593 si_DoDialogIfPrefIsOff(
2594 const PRUnichar *dialogTitle,
2595 const PRUnichar *text,
2596 PRUnichar **user,
2597 PRUnichar **pwd,
2598 const PRUnichar *defaultText,
2599 PRUnichar **resultText,
2600 const char *passwordRealm,
2601 nsIPrompt* dialog,
2602 PRBool *pressedOK,
2603 PRUint32 savePassword,
2604 DialogType dlg) {
2606 nsresult res = NS_ERROR_FAILURE;
2607 const PRUnichar * prompt_string = dialogTitle;
2608 if (dialogTitle == nsnull || !dialogTitle[0]) {
2609 prompt_string = Wallet_Localize("PromptForData");
2612 nsAutoString data(defaultText);
2613 switch (dlg) {
2614 case promptUsernameAndPassword:
2615 res = dialog->PromptUsernameAndPassword(prompt_string,
2616 text,
2617 user,
2618 pwd,
2619 nsnull, nsnull,
2620 pressedOK);
2621 break;
2622 case promptPassword:
2623 res = dialog->PromptPassword(prompt_string,
2624 text,
2625 pwd,
2626 nsnull, nsnull,
2627 pressedOK);
2628 break;
2629 case prompt:
2630 *resultText = ToNewUnicode(data);
2631 res = dialog->Prompt(prompt_string,
2632 text,
2633 resultText,
2634 nsnull, nsnull,
2635 pressedOK);
2636 #ifdef DEBUG
2637 break;
2638 default:
2639 NS_ERROR("Undefined DialogType in si_DoDialogIfPrefIsOff");
2640 #endif
2643 if (dialogTitle != prompt_string) {
2644 Recycle(const_cast<PRUnichar*>(prompt_string));
2646 return res;
2649 /* The following comments apply to the three prompt routines that follow
2651 * If a password was successfully obtain (either from the single-signon
2652 * database or from a dialog with the user), we return NS_OK for the
2653 * function value and PR_TRUE for the boolean argument "pressedOK".
2655 * If the user presses cancel from the dialog, we return NS_OK for the
2656 * function value and PR_FALSE for the boolean argument "pressedOK".
2658 * If a password is not collected for any other reason, we return the
2659 * failure code for the function value and the boolean argument
2660 * "pressedOK" is undefined.
2663 nsresult
2664 SINGSIGN_PromptUsernameAndPassword
2665 (const PRUnichar *dialogTitle, const PRUnichar *text, PRUnichar **user, PRUnichar **pwd,
2666 const char *passwordRealm, nsIPrompt* dialog, PRBool *pressedOK, PRUint32 savePassword) {
2668 nsresult res;
2670 /* do only the dialog if signon preference is not enabled */
2671 if (!si_GetSignonRememberingPref()){
2672 return si_DoDialogIfPrefIsOff(dialogTitle,
2673 text,
2674 user,
2675 pwd,
2676 nsnull,
2677 nsnull,
2678 passwordRealm,
2679 dialog,
2680 pressedOK,
2681 savePassword,
2682 promptUsernameAndPassword);
2685 /* prefill with previous username/password if any */
2686 nsAutoString username, password;
2687 si_RestoreOldSignonDataFromBrowser(dialog, passwordRealm, PR_FALSE, username, password);
2689 /* get new username/password from user */
2690 if (!(*user = ToNewUnicode(username))) {
2691 return NS_ERROR_OUT_OF_MEMORY;
2693 if (!(*pwd = ToNewUnicode(password))) {
2694 PR_Free(*user);
2695 return NS_ERROR_OUT_OF_MEMORY;
2698 PRBool checked = (**user != 0);
2699 PRBool remembered = checked;
2700 res = si_CheckGetUsernamePassword(user, pwd, dialogTitle, text, dialog, savePassword, &checked);
2701 if (NS_FAILED(res)) {
2702 /* user pressed Cancel */
2703 PR_FREEIF(*user);
2704 PR_FREEIF(*pwd);
2705 *pressedOK = PR_FALSE;
2706 return NS_OK;
2708 if (checked) {
2709 Wallet_GiveCaveat(nsnull, dialog);
2710 si_RememberSignonDataFromBrowser (passwordRealm, nsDependentString(*user), nsDependentString(*pwd));
2711 } else if (remembered) {
2712 /* a login was remembered but user unchecked the box; we forget the remembered login */
2713 si_RemoveUser(passwordRealm, username, PR_TRUE, PR_FALSE, PR_TRUE);
2716 /* cleanup and return */
2717 *pressedOK = PR_TRUE;
2718 return NS_OK;
2721 nsresult
2722 SINGSIGN_PromptPassword
2723 (const PRUnichar *dialogTitle, const PRUnichar *text, PRUnichar **pwd, const char *passwordRealm,
2724 nsIPrompt* dialog, PRBool *pressedOK, PRUint32 savePassword)
2727 nsresult res;
2728 nsAutoString password, username;
2730 /* do only the dialog if signon preference is not enabled */
2731 if (!si_GetSignonRememberingPref()){
2732 return si_DoDialogIfPrefIsOff(dialogTitle,
2733 text,
2734 nsnull,
2735 pwd,
2736 nsnull,
2737 nsnull,
2738 passwordRealm,
2739 dialog,
2740 pressedOK,
2741 savePassword,
2742 promptPassword);
2745 /* get previous password used with this username, pick first user if no username found */
2746 si_RestoreOldSignonDataFromBrowser(dialog, passwordRealm, username.IsEmpty(), username, password);
2748 /* return if a password was found */
2749 if (!password.IsEmpty()) {
2750 *pwd = ToNewUnicode(password);
2751 *pressedOK = PR_TRUE;
2752 return NS_OK;
2755 /* no password found, get new password from user */
2756 PRBool checked = PR_FALSE;
2757 res = si_CheckGetPassword(pwd, dialogTitle, text, dialog, savePassword, &checked);
2758 if (NS_FAILED(res)) {
2759 /* user pressed Cancel */
2760 PR_FREEIF(*pwd);
2761 *pressedOK = PR_FALSE;
2762 return NS_OK;
2764 if (checked) {
2765 Wallet_GiveCaveat(nsnull, dialog);
2766 si_RememberSignonDataFromBrowser(passwordRealm, username, nsDependentString(*pwd));
2769 /* cleanup and return */
2770 *pressedOK = PR_TRUE;
2771 return NS_OK;
2774 nsresult
2775 SINGSIGN_Prompt
2776 (const PRUnichar *dialogTitle, const PRUnichar *text, const PRUnichar *defaultText, PRUnichar **resultText,
2777 const char *passwordRealm, nsIPrompt* dialog, PRBool *pressedOK, PRUint32 savePassword)
2779 nsresult res;
2780 nsAutoString data, emptyUsername;
2782 /* do only the dialog if signon preference is not enabled */
2783 if (!si_GetSignonRememberingPref()){
2784 return si_DoDialogIfPrefIsOff(dialogTitle,
2785 text,
2786 nsnull,
2787 nsnull,
2788 defaultText,
2789 resultText,
2790 passwordRealm,
2791 dialog,
2792 pressedOK,
2793 savePassword,
2794 prompt);
2797 /* get previous data used with this hostname */
2798 si_RestoreOldSignonDataFromBrowser(dialog, passwordRealm, PR_TRUE, emptyUsername, data);
2800 /* return if data was found */
2801 if (!data.IsEmpty()) {
2802 *resultText = ToNewUnicode(data);
2803 *pressedOK = PR_TRUE;
2804 return NS_OK;
2807 /* no data found, get new data from user */
2808 data = defaultText;
2809 *resultText = ToNewUnicode(data);
2810 PRBool checked = PR_FALSE;
2811 res = si_CheckGetData(resultText, dialogTitle, text, dialog, savePassword, &checked);
2812 if (NS_FAILED(res)) {
2813 /* user pressed Cancel */
2814 PR_FREEIF(*resultText);
2815 *pressedOK = PR_FALSE;
2816 return NS_OK;
2818 if (checked) {
2819 Wallet_GiveCaveat(nsnull, dialog);
2820 si_RememberSignonDataFromBrowser(passwordRealm, emptyUsername, nsDependentString(*resultText));
2823 /* cleanup and return */
2824 *pressedOK = PR_TRUE;
2825 return NS_OK;
2828 nsresult
2829 SINGSIGN_PromptAuth
2830 (nsIPromptService2* aService, nsIDOMWindow* aParent, nsIChannel* aChannel,
2831 PRUint32 aLevel, nsIAuthInformation* aAuthInfo, PRBool* retval) {
2833 nsCAutoString key;
2834 NS_GetAuthKey(aChannel, aAuthInfo, key);
2836 /* do only the dialog if signon preference is not enabled */
2837 if (!si_GetSignonRememberingPref()){
2838 return aService->PromptAuth(aParent, aChannel, aLevel,
2839 aAuthInfo, nsnull, nsnull,
2840 retval);
2843 /* prefill with previous username/password if any */
2844 /* this needs a dialog to choose between multiple usernames */
2845 nsCOMPtr<nsIPrompt> prompt;
2846 nsresult rv;
2847 nsCOMPtr<nsIWindowWatcher> wwatcher =
2848 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
2849 wwatcher->GetNewPrompter(aParent, getter_AddRefs(prompt));
2851 // If we have a domain, insert a "domain\" in front of the username
2852 // for the lookup
2853 nsAutoString domain, username, password;
2854 aAuthInfo->GetDomain(domain);
2855 aAuthInfo->GetUsername(username);
2856 if (!domain.IsEmpty()) {
2857 domain.Append(PRUnichar('\\'));
2858 username.Insert(domain, 0);
2860 // Offer saving the data iff we had previously stored information
2861 PRBool checked = si_RestoreOldSignonDataFromBrowser(prompt,
2862 key.get(),
2863 PR_FALSE,
2864 username,
2865 password);
2867 PRUint32 flags = 0;
2868 aAuthInfo->GetFlags(&flags);
2870 if (checked) {
2871 NS_SetAuthInfo(aAuthInfo, username, password);
2872 // If we were only asked for a password, return immediately
2873 // (to match SINGSIGN_PromptPassword)
2874 if (flags & nsIAuthInformation::ONLY_PASSWORD)
2875 return NS_OK;
2878 PRBool remembered = checked;
2879 rv = si_CheckPromptAuth(aService, aParent, aChannel, aLevel,
2880 aAuthInfo, &checked);
2881 if (NS_FAILED(rv)) {
2882 /* user pressed Cancel */
2883 *retval = PR_FALSE;
2884 return NS_OK;
2887 /* Get the newly entered data back */
2888 aAuthInfo->GetDomain(domain);
2889 aAuthInfo->GetUsername(username);
2890 aAuthInfo->GetPassword(password);
2891 if (!domain.IsEmpty()) {
2892 domain.Append(PRUnichar('\\'));
2893 username.Insert(domain, 0);
2896 if (checked) {
2897 Wallet_GiveCaveat(nsnull, prompt);
2898 si_RememberSignonDataFromBrowser (key.get(), username, password);
2899 } else if (remembered) {
2900 /* a login was remembered but user unchecked the box; we forget the remembered login */
2901 si_RemoveUser(key.get(), username, PR_TRUE, PR_FALSE, PR_TRUE);
2904 /* cleanup and return */
2905 *retval = PR_TRUE;
2906 return NS_OK;
2910 /*****************
2911 * Signon Viewer *
2912 *****************/
2914 /* return PR_TRUE if "number" is in sequence of comma-separated numbers */
2915 PRBool
2916 SI_InSequence(const nsString& sequence, PRInt32 number)
2918 nsAutoString tail( sequence );
2919 nsAutoString head, temp;
2920 PRInt32 separator;
2922 for (;;) {
2923 /* get next item in list */
2924 separator = tail.FindChar(',');
2925 if (-1 == separator) {
2926 return PR_FALSE;
2928 tail.Left(head, separator);
2929 tail.Mid(temp, separator+1, tail.Length() - (separator+1));
2930 tail = temp;
2932 /* test item to see if it equals our number */
2933 PRInt32 error;
2934 PRInt32 numberInList = head.ToInteger(&error);
2935 if (!error && numberInList == number) {
2936 return PR_TRUE;
2939 /* NOTREACHED */
2940 return PR_FALSE;
2943 void
2944 SI_FindValueInArgs(const nsAString& results, const nsAString& name, nsAString& value)
2946 /* note: name must start and end with a vertical bar */
2947 nsReadingIterator<PRUnichar> start, end, barPos;
2948 results.BeginReading(start);
2949 results.EndReading(end);
2951 FindInReadable(name, start, end);
2952 if (start == end) {
2953 return;
2955 start.advance(name.Length()); /* get past the |name| part */
2956 barPos = start;
2957 results.EndReading(end);
2958 FindCharInReadable(PRUnichar('|'), barPos, end);
2959 value = Substring(start, barPos);
2962 PRBool
2963 SINGSIGN_ReencryptAll()
2965 /* force loading of the signons file */
2966 si_RegisterSignonPrefCallbacks();
2968 nsAutoString buffer;
2969 si_SignonURLStruct *url;
2970 si_SignonUserStruct * user;
2971 si_SignonDataStruct* data = nsnull;
2973 si_lock_signon_list();
2974 PRInt32 urlCount = LIST_COUNT(si_signon_list);
2975 for (PRInt32 i=0; i<urlCount; i++) {
2976 url = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(i));
2977 PRInt32 userCount = url->signonUser_list.Count();
2978 for (PRInt32 j=0; j<userCount; j++) {
2979 user = static_cast<si_SignonUserStruct*>(url->signonUser_list.ElementAt(j));
2981 PRInt32 dataCount = user->signonData_list.Count();
2982 for (PRInt32 k=0; k<dataCount; k++) {
2983 data = static_cast<si_SignonDataStruct *>
2984 (user->signonData_list.ElementAt(k));
2985 nsAutoString userName;
2986 if (NS_FAILED(Wallet_Decrypt(data->value, userName))) {
2987 //Don't try to re-encrypt. Just go to the next one.
2988 continue;
2990 if (NS_FAILED(Wallet_Encrypt(userName, data->value))) {
2991 return PR_FALSE;
2996 si_signon_list_changed = PR_TRUE;
2997 si_SaveSignonDataLocked("signons", PR_TRUE);
2998 si_unlock_signon_list();
2999 return PR_TRUE;
3002 nsresult
3003 SINGSIGN_HaveData(nsIPrompt* dialog, const char *passwordRealm, const PRUnichar *userName, PRBool *retval)
3005 nsAutoString data, usernameForLookup;
3007 *retval = PR_FALSE;
3009 if (!si_GetSignonRememberingPref()) {
3010 return NS_OK;
3013 /* get previous data used with this username, pick first user if no username found */
3014 si_RestoreOldSignonDataFromBrowser(dialog, passwordRealm, usernameForLookup.IsEmpty(), usernameForLookup, data);
3016 if (!data.IsEmpty()) {
3017 *retval = PR_TRUE;
3020 return NS_OK;
3023 PRInt32
3024 SINGSIGN_HostCount() {
3025 /* force loading of the signons file */
3026 si_RegisterSignonPrefCallbacks();
3028 if (!si_signon_list) {
3029 return 0;
3031 return si_signon_list->Count();
3034 PRInt32
3035 SINGSIGN_UserCount(PRInt32 host) {
3036 if (!si_signon_list) {
3037 return 0;
3040 si_SignonURLStruct *hostStruct;
3041 hostStruct = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(host));
3042 return hostStruct->signonUser_list.Count();
3045 nsresult
3046 SINGSIGN_Enumerate
3047 (PRInt32 hostNumber, PRInt32 userNumber, PRBool decrypt, char **host,
3048 PRUnichar ** user, PRUnichar ** pswd) {
3050 if (gSelectUserDialogCount>0 && hostNumber==0 && userNumber==0) {
3051 // starting to enumerate over all saved logins
3052 // notify recipients if login list is in use by SelectUserDialog
3053 nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
3054 if (os) {
3055 os->NotifyObservers(nsnull, "signonSelectUser", NS_LITERAL_STRING("inUse").get());
3059 if (hostNumber > SINGSIGN_HostCount() || userNumber > SINGSIGN_UserCount(hostNumber)) {
3060 return NS_ERROR_FAILURE;
3062 si_SignonURLStruct *hostStruct;
3063 si_SignonUserStruct * userStruct;
3064 si_SignonDataStruct* data = nsnull;
3066 hostStruct = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(hostNumber));
3067 NS_ASSERTION(hostStruct, "corrupt singlesignon list");
3068 *host = (char *) nsMemory::Clone
3069 (hostStruct->passwordRealm, strlen(hostStruct->passwordRealm) + 1);
3070 NS_ENSURE_ARG_POINTER(host);
3071 userStruct =
3072 static_cast<si_SignonUserStruct*>(hostStruct->signonUser_list.ElementAt(userNumber));
3074 /* first non-password data item for user is the username */
3075 PRInt32 dataCount = userStruct->signonData_list.Count();
3076 PRInt32 k;
3077 for (k=0; k<dataCount; k++) {
3078 data = static_cast<si_SignonDataStruct *>(userStruct->signonData_list.ElementAt(k));
3079 if (!(data->isPassword)) {
3080 break;
3084 nsresult rv;
3085 nsAutoString userName;
3086 if (decrypt) {
3087 rv = Wallet_Decrypt(data->value, userName);
3088 if (NS_FAILED(rv)) {
3089 /* don't display saved signons if user couldn't unlock the database */
3090 return rv;
3092 } else {
3093 userName = data->value;
3095 if (!(*user = ToNewUnicode(userName))) {
3096 return NS_ERROR_OUT_OF_MEMORY;
3099 /* first password data item for user is the password */
3100 for (k=0; k<dataCount; k++) {
3101 data = static_cast<si_SignonDataStruct *>(userStruct->signonData_list.ElementAt(k));
3102 if ((data->isPassword)) {
3103 break;
3107 nsAutoString passWord;
3108 if (decrypt) {
3109 rv = Wallet_Decrypt(data->value, passWord);
3110 if (NS_FAILED(rv)) {
3111 /* don't display saved signons if user couldn't unlock the database */
3112 Recycle(*user);
3113 return rv;
3115 } else {
3116 passWord = data->value;
3118 if (!(*pswd = ToNewUnicode(passWord))) {
3119 Recycle(*user);
3120 return NS_ERROR_OUT_OF_MEMORY;
3122 return NS_OK;
3125 PRInt32
3126 SINGSIGN_RejectCount() {
3127 if (!si_reject_list) {
3128 return 0;
3130 return si_reject_list->Count();
3133 nsresult
3134 SINGSIGN_RejectEnumerate
3135 (PRInt32 rejectNumber, char **host) {
3137 si_Reject *reject;
3138 reject = static_cast<si_Reject*>(si_reject_list->ElementAt(rejectNumber));
3139 NS_ASSERTION(reject, "corrupt reject list");
3141 *host = (char *) nsMemory::Clone
3142 (reject->passwordRealm, strlen(reject->passwordRealm) + 1);
3143 NS_ENSURE_ARG_POINTER(host);
3144 return NS_OK;
3147 #ifdef APPLE_KEYCHAIN
3148 /************************************
3149 * Apple Keychain Specific Routines *
3150 ************************************/
3153 * APPLE
3154 * The Keychain callback. This routine will be called whenever a lock,
3155 * delete, or update event occurs in the Keychain. The only action taken
3156 * is to make the signon list invalid, so it will be read in again the
3157 * next time it is accessed.
3159 OSStatus PR_CALLBACK
3160 si_KeychainCallback( KCEvent keychainEvent, KCCallbackInfo *info, void *userContext) {
3161 PRBool *listInvalid = (PRBool*)userContext;
3162 *listInvalid = PR_TRUE;
3166 * APPLE
3167 * Get the signon data from the keychain
3169 * This routine is called only if signon pref is enabled!!!
3171 static int
3172 si_LoadSignonDataFromKeychain() {
3173 char * passwordRealm;
3174 si_FormSubmitData submit;
3175 nsAutoString name_array[MAX_ARRAY_SIZE];
3176 nsAutoString value_array[MAX_ARRAY_SIZE];
3177 uint8 type_array[MAX_ARRAY_SIZE];
3178 char buffer[BUFFER_SIZE];
3179 PRBool badInput = PR_FALSE;
3180 int i;
3181 KCItemRef itemRef;
3182 KCAttributeList attrList;
3183 KCAttribute attr[2];
3184 KCItemClass itemClass = kInternetPasswordKCItemClass;
3185 KCProtocolType protocol = kNetscapeProtocolType;
3186 OSStatus status = noErr;
3187 KCSearchRef searchRef = NULL;
3188 /* initialize the submit structure */
3189 submit.name_array = name_array;
3190 submit.value_array = value_array;
3191 submit.type_array = (PRUnichar *)type_array;
3193 /* set up the attribute list */
3194 attrList.count = 2;
3195 attrList.attr = attr;
3196 attr[0].tag = kClassKCItemAttr;
3197 attr[0].data = &itemClass;
3198 attr[0].length = sizeof(itemClass);
3200 attr[1].tag = kProtocolKCItemAttr;
3201 attr[1].data = &protocol;
3202 attr[1].length = sizeof(protocol);
3204 status = KCFindFirstItem( &attrList, &searchRef, &itemRef );
3206 #if 0
3207 if (status == noErr) {
3208 /* if we found a Netscape item, let's assume notice has been given */
3209 si_SetNotificationPref(PR_TRUE);
3210 } else {
3211 si_SetNotificationPref(PR_FALSE);
3213 #endif
3215 si_lock_signon_list();
3216 while(status == noErr) {
3217 char *value;
3218 uint16 i = 0;
3219 uint32 actualSize;
3220 KCItemFlags flags;
3221 PRBool reject = PR_FALSE;
3222 submit.value_cnt = 0;
3224 /* first find out if it is a reject entry */
3225 attr[0].tag = kFlagsKCItemAttr;
3226 attr[0].length = sizeof(KCItemFlags);
3227 attr[0].data = &flags;
3228 status = KCGetAttribute( itemRef, attr, nil );
3229 if (status != noErr) {
3230 break;
3232 if (flags & kNegativeKCItemFlag) {
3233 reject = PR_TRUE;
3236 /* get the server name */
3237 attr[0].tag = kServerKCItemAttr;
3238 attr[0].length = BUFFER_SIZE;
3239 attr[0].data = buffer;
3240 status = KCGetAttribute( itemRef, attr, &actualSize );
3241 if (status != noErr) {
3242 break;
3245 /* null terminate */
3246 buffer[actualSize] = 0;
3247 passwordRealm = NULL;
3248 StrAllocCopy(passwordRealm, buffer);
3249 if (!reject) {
3250 /* get the password data */
3251 status = KCGetData(itemRef, BUFFER_SIZE, buffer, &actualSize);
3252 if (status != noErr) {
3253 break;
3256 /* null terminate */
3257 buffer[actualSize] = 0;
3259 /* parse for '=' which separates the name and value */
3260 uint16 bufferlen = PL_strlen(buffer);
3261 for (i = 0; i < bufferlen; i++) {
3262 if (buffer[i] == '=') {
3263 value = &buffer[i+1];
3264 buffer[i] = 0;
3265 break;
3268 name_array[submit.value_cnt] = NULL;
3269 value_array[submit.value_cnt] = NULL;
3270 type_array[submit.value_cnt] = FORM_TYPE_PASSWORD;
3271 StrAllocCopy(name_array[submit.value_cnt], buffer);
3272 StrAllocCopy(value_array[submit.value_cnt], value);
3275 /* get the account attribute */
3276 attr[0].tag = kAccountKCItemAttr;
3277 attr[0].length = BUFFER_SIZE;
3278 attr[0].data = buffer;
3279 status = KCGetAttribute( itemRef, attr, &actualSize );
3280 if (status != noErr) {
3281 break;
3284 /* null terminate */
3285 buffer[actualSize] = 0;
3286 if (!reject) {
3287 /* parse for '=' which separates the name and value */
3288 uint16 bufferlen = PL_strlen(buffer);
3289 for (i = 0; i < bufferlen; i++) {
3290 if (buffer[i] == '=') {
3291 value = &buffer[i+1];
3292 buffer[i] = 0;
3293 break;
3296 submit.value_cnt++;
3297 name_array[submit.value_cnt] = NULL;
3298 value_array[submit.value_cnt] = NULL;
3299 type_array[submit.value_cnt] = FORM_TYPE_TEXT;
3300 StrAllocCopy(name_array[submit.value_cnt], buffer);
3301 StrAllocCopy(value_array[submit.value_cnt], value);
3303 /* check for overruning of the arrays */
3304 if (submit.value_cnt >= MAX_ARRAY_SIZE) {
3305 break;
3307 submit.value_cnt++;
3308 /* store the info for this URL into memory-resident data structure */
3309 if (!passwordRealm || passwordRealm[0] == 0) {
3310 badInput = PR_TRUE;
3312 if (!badInput) {
3313 si_PutData(passwordRealm, &submit, PR_FALSE);
3316 } else {
3317 /* reject */
3318 si_PutReject(passwordRealm, nsDependentString(buffer), PR_FALSE);
3320 reject = PR_FALSE; /* reset reject flag */
3321 PR_Free(passwordRealm);
3322 KCReleaseItemRef( &itemRef );
3323 status = KCFindNextItem( searchRef, &itemRef);
3325 si_unlock_signon_list();
3327 if (searchRef) {
3328 KCReleaseSearchRef( &searchRef );
3331 /* Register a callback with the Keychain if we haven't already done so. */
3333 if (si_kcUPP == NULL) {
3334 si_kcUPP = NewKCCallbackProc( si_KeychainCallback );
3335 if (!si_kcUPP) {
3336 return memFullErr;
3339 KCAddCallback( si_kcUPP, kLockKCEventMask + kDeleteKCEventMask + kUpdateKCEventMask, &si_list_invalid );
3341 * Note that the callback is not necessarily removed. We take advantage
3342 * of the fact that the Keychain will clean up the callback when the app
3343 * goes away. It is explicitly removed when the signon preference is turned off.
3346 if (status == errKCItemNotFound) {
3347 status = 0;
3349 return (status);
3353 * APPLE
3354 * Save signon data to Apple Keychain
3356 * This routine is called only if signon pref is enabled!!!
3358 static int
3359 si_SaveSignonDataInKeychain() {
3360 char* account = nil;
3361 char* password = nil;
3362 si_SignonURLStruct * URL;
3363 si_SignonUserStruct * user;
3364 si_SignonDataStruct * data;
3365 si_Reject * reject;
3366 OSStatus status;
3367 KCItemRef itemRef;
3368 KCAttribute attr;
3369 KCItemFlags flags = kInvisibleKCItemFlag + kNegativeKCItemFlag;
3370 uint32 actualLength;
3372 /* save off the reject list */
3373 if (si_reject_list) {
3374 PRInt32 rejectCount = LIST_COUNT(si_reject_list);
3375 for (PRInt32 i=0; i<rejectCount; i++) {
3376 reject = static_cast<si_Reject*>(si_reject_list->ElementAt(i));
3377 status = kcaddinternetpassword
3378 (reject->passwordRealm, nil,
3379 reject->userName,
3380 kAnyPort,
3381 kNetscapeProtocolType,
3382 kAnyAuthType,
3384 nil,
3385 &itemRef);
3386 if (status != noErr && status != errKCDuplicateItem) {
3387 return(status);
3389 if (status == noErr) {
3391 * make the item invisible so the user doesn't see it and
3392 * negative so we know that it is a reject entry
3394 attr.tag = kFlagsKCItemAttr;
3395 attr.data = &flags;
3396 attr.length = sizeof( flags );
3398 status = KCSetAttribute( itemRef, &attr );
3399 if (status != noErr) {
3400 return(status);
3402 status = KCUpdateItem(itemRef);
3403 if (status != noErr) {
3404 return(status);
3406 KCReleaseItemRef(&itemRef);
3411 /* save off the passwords */
3412 if((si_signon_list)) {
3413 PRInt32 urlCount = LIST_COUNT(si_signon_list);
3414 for (PRInt32 i=0; i<urlCount; i++) {
3415 URL = static_cast<si_SignonURLStruct*>(si_signon_list->ElementAt(i));
3417 /* add each user node of the URL node */
3418 PRInt32 userCount = URL->signonUser_list.Count();
3419 for (PRInt32 i=0; i<userCount; i++) {
3420 user = static_cast<si_SignonUserStruct*>(URL->signonUser_list.ElementAt(i));
3422 /* write out each data node of the user node */
3423 PRInt32 count = user->signonData_list.Count();
3424 for (PRInt32 i=0; i<count; i++) {
3425 data = static_cast<si_SignonDataStruct*>(user->signonData_list.ElementAt(i));
3426 char* attribute = nil;
3427 if (data->isPassword) {
3428 password = PR_Malloc(PL_strlen(data->value) + PL_strlen(data->name) + 2);
3429 if (!password) {
3430 return (-1);
3432 attribute = password;
3433 } else {
3434 account = PR_Malloc( PL_strlen(data->value) + PL_strlen(data->name) + 2);
3435 if (!account) {
3436 PR_Free(password);
3437 return (-1);
3439 attribute = account;
3441 PL_strcpy(attribute, data->name);
3442 PL_strcat(attribute, "=");
3443 PL_strcat(attribute, data->value);
3445 /* if it's already there, we just want to change the password */
3446 status = kcfindinternetpassword
3447 (URL->passwordRealm,
3448 nil,
3449 account,
3450 kAnyPort,
3451 kNetscapeProtocolType,
3452 kAnyAuthType,
3454 nil,
3455 &actualLength,
3456 &itemRef);
3457 if (status == noErr) {
3458 status = KCSetData(itemRef, PL_strlen(password), password);
3459 if (status != noErr) {
3460 return(status);
3462 status = KCUpdateItem(itemRef);
3463 KCReleaseItemRef(&itemRef);
3464 } else {
3465 /* wasn't there, let's add it */
3466 status = kcaddinternetpassword
3467 (URL->passwordRealm,
3468 nil,
3469 account,
3470 kAnyPort,
3471 kNetscapeProtocolType,
3472 kAnyAuthType,
3473 PL_strlen(password),
3474 password,
3475 nil);
3477 if (account) {
3478 PR_Free(account);
3480 if (password) {
3481 PR_Free(password);
3483 account = password = nil;
3484 if (status != noErr) {
3485 return(status);
3490 si_signon_list_changed = PR_FALSE;
3491 return (0);
3494 #endif