Avoid potential negative array index access to cached text.
[LibreOffice.git] / svl / source / passwordcontainer / passwordcontainer.cxx
blobd030b0a2cedf5dc376c00576d00c58c253386ecb
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <string_view>
25 #include "passwordcontainer.hxx"
27 #include <cppuhelper/factory.hxx>
28 #include <cppuhelper/supportsservice.hxx>
29 #include <comphelper/processfactory.hxx>
30 #include <comphelper/propertyvalue.hxx>
31 #include <comphelper/sequence.hxx>
32 #include <o3tl/string_view.hxx>
33 #include <com/sun/star/beans/PropertyValue.hpp>
34 #include <com/sun/star/task/InteractionHandler.hpp>
35 #include <com/sun/star/task/MasterPasswordRequest.hpp>
36 #include <com/sun/star/task/NoMasterException.hpp>
37 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
39 #include <osl/diagnose.h>
40 #include <rtl/character.hxx>
41 #include <rtl/cipher.h>
42 #include <rtl/digest.h>
43 #include <rtl/byteseq.hxx>
44 #include <rtl/ustrbuf.hxx>
46 using namespace osl;
47 using namespace utl;
48 using namespace com::sun::star;
49 using namespace com::sun::star::uno;
50 using namespace com::sun::star::registry;
51 using namespace com::sun::star::lang;
52 using namespace com::sun::star::task;
53 using namespace com::sun::star::ucb;
55 static OUString createIndex(const std::vector< OUString >& lines)
57 OUStringBuffer aResult;
59 for( size_t i = 0; i < lines.size(); i++ )
61 if( i )
62 aResult.append("__");
63 OString line = OUStringToOString( lines[i], RTL_TEXTENCODING_UTF8 );
64 const char* pLine = line.getStr();
66 while( *pLine )
68 if (rtl::isAsciiAlphanumeric(static_cast<unsigned char>(*pLine)))
70 aResult.append(*pLine);
72 else
74 aResult.append("_" + OUString::number(*pLine, 16) );
77 pLine++;
81 return aResult.makeStringAndClear();
85 static std::vector< OUString > getInfoFromInd( std::u16string_view aInd )
87 std::vector< OUString > aResult;
88 bool aStart = true;
90 OString line = OUStringToOString( aInd, RTL_TEXTENCODING_ASCII_US );
91 const char* pLine = line.getStr();
94 OUStringBuffer newItem;
95 if( !aStart )
96 pLine += 2;
97 else
98 aStart = false;
100 while( *pLine && ( pLine[0] != '_' || pLine[1] != '_' ))
101 if( *pLine != '_' )
103 newItem.append( *pLine );
104 pLine++;
106 else
108 OUString aNum;
109 for( int i = 1; i < 3; i++ )
111 if( !pLine[i]
112 || ( ( pLine[i] < '0' || pLine[i] > '9' )
113 && ( pLine[i] < 'a' || pLine[i] > 'f' )
114 && ( pLine[i] < 'A' || pLine[i] > 'F' ) ) )
116 OSL_FAIL( "Wrong index syntax!" );
117 return aResult;
120 aNum += OUStringChar( pLine[i] );
123 newItem.append( sal_Unicode( aNum.toUInt32( 16 ) ) );
124 pLine += 3;
127 aResult.push_back( newItem.makeStringAndClear() );
128 } while( pLine[0] == '_' && pLine[1] == '_' );
130 if( *pLine )
131 OSL_FAIL( "Wrong index syntax!" );
133 return aResult;
137 static bool shorterUrl( OUString& aURL )
139 sal_Int32 aInd = aURL.lastIndexOf( '/' );
140 if( aInd > 0 && aURL.indexOf( "://" ) != aInd-2 )
142 aURL = aURL.copy( 0, aInd );
143 return true;
146 return false;
150 static OUString getAsciiLine( const ::rtl::ByteSequence& buf )
152 OUString aResult;
154 ::rtl::ByteSequence outbuf( buf.getLength()*2+1 );
156 for( int ind = 0; ind < buf.getLength(); ind++ )
158 outbuf[ind*2] = ( static_cast<sal_uInt8>(buf[ind]) >> 4 ) + 'a';
159 outbuf[ind*2+1] = ( static_cast<sal_uInt8>(buf[ind]) & 0x0f ) + 'a';
161 outbuf[buf.getLength()*2] = '\0';
163 aResult = OUString::createFromAscii( reinterpret_cast<char*>(outbuf.getArray()) );
165 return aResult;
169 static ::rtl::ByteSequence getBufFromAsciiLine( std::u16string_view line )
171 OSL_ENSURE( line.size() % 2 == 0, "Wrong syntax!" );
172 OString tmpLine = OUStringToOString( line, RTL_TEXTENCODING_ASCII_US );
173 ::rtl::ByteSequence aResult(line.size()/2);
175 for( int ind = 0; ind < tmpLine.getLength()/2; ind++ )
177 aResult[ind] = ( static_cast<sal_uInt8>( tmpLine[ind*2] - 'a' ) << 4 ) | static_cast<sal_uInt8>( tmpLine[ind*2+1] - 'a' );
180 return aResult;
184 PasswordMap StorageItem::getInfo()
186 PasswordMap aResult;
188 const Sequence< OUString > aNodeNames = ConfigItem::GetNodeNames( "Store" );
189 sal_Int32 aNodeCount = aNodeNames.getLength();
190 Sequence< OUString > aPropNames( aNodeCount * 2);
192 std::transform(aNodeNames.begin(), aNodeNames.end(), aPropNames.getArray(),
193 [](const OUString& rName) -> OUString {
194 return "Store/Passwordstorage['" + rName + "']/Password"; });
195 std::transform(aNodeNames.begin(), aNodeNames.end(), aPropNames.getArray() + aNodeCount,
196 [](const OUString& rName) -> OUString {
197 return "Store/Passwordstorage['" + rName + "']/InitializationVector"; });
199 Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aPropNames );
201 if( aPropertyValues.getLength() != aNodeCount * 2)
203 OSL_FAIL( "Problems during reading" );
204 return aResult;
207 for( sal_Int32 aNodeInd = 0; aNodeInd < aNodeCount; ++aNodeInd )
209 std::vector< OUString > aUrlUsr = getInfoFromInd( aNodeNames[aNodeInd] );
211 if( aUrlUsr.size() == 2 )
213 OUString aUrl = aUrlUsr[0];
214 OUString aName = aUrlUsr[1];
216 OUString aEPasswd;
217 OUString aIV;
218 aPropertyValues[aNodeInd] >>= aEPasswd;
219 aPropertyValues[aNodeInd + aNodeCount] >>= aIV;
221 PasswordMap::iterator aIter = aResult.find( aUrl );
222 if( aIter != aResult.end() )
223 aIter->second.emplace_back( aName, aEPasswd, aIV );
224 else
226 NamePasswordRecord aNewRecord( aName, aEPasswd, aIV );
227 std::vector< NamePasswordRecord > listToAdd( 1, aNewRecord );
229 aResult.insert( PairUrlRecord( aUrl, listToAdd ) );
232 else
233 OSL_FAIL( "Wrong index syntax!" );
236 return aResult;
240 void StorageItem::setUseStorage( bool bUse )
242 ConfigItem::SetModified();
243 ConfigItem::PutProperties( { "UseStorage" }, { uno::Any(bUse) } );
247 bool StorageItem::useStorage()
249 Sequence<OUString> aNodeNames { "UseStorage" };
251 Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames );
253 if( aPropertyValues.getLength() != aNodeNames.getLength() )
255 OSL_FAIL( "Problems during reading" );
256 return false;
259 bool aResult = false;
260 aPropertyValues[0] >>= aResult;
262 return aResult;
266 sal_Int32 StorageItem::getStorageVersion()
268 Sequence<OUString> aNodeNames { "StorageVersion" };
270 Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames );
272 if( aPropertyValues.getLength() != aNodeNames.getLength() )
274 OSL_FAIL( "Problems during reading" );
275 return 0;
278 sal_Int32 nResult = 0;
279 aPropertyValues[0] >>= nResult;
281 return nResult;
284 bool StorageItem::getEncodedMasterPassword( OUString& aResult, OUString& aResultIV )
286 if( hasEncoded )
288 aResult = mEncoded;
289 aResultIV = mEncodedIV;
290 return true;
293 Sequence< OUString > aNodeNames{ "HasMaster", "Master", "MasterInitializationVector" };
295 Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames );
297 if( aPropertyValues.getLength() != aNodeNames.getLength() )
299 OSL_FAIL( "Problems during reading" );
300 return false;
303 aPropertyValues[0] >>= hasEncoded;
304 aPropertyValues[1] >>= mEncoded;
305 aPropertyValues[2] >>= mEncodedIV;
307 aResult = mEncoded;
308 aResultIV = mEncodedIV;
310 return hasEncoded;
314 void StorageItem::setEncodedMasterPassword( const OUString& aEncoded, const OUString& aEncodedIV, bool bAcceptEmpty )
316 bool bHasMaster = ( !aEncoded.isEmpty() || bAcceptEmpty );
318 ConfigItem::SetModified();
319 ConfigItem::PutProperties( { "HasMaster", "Master", "MasterInitializationVector", "StorageVersion" },
320 { uno::Any(bHasMaster), uno::Any(aEncoded),
321 uno::Any(aEncodedIV), uno::Any(nCurrentStorageVersion) } );
323 hasEncoded = bHasMaster;
324 mEncoded = aEncoded;
325 mEncodedIV = aEncodedIV;
329 void StorageItem::remove( const OUString& aURL, const OUString& aName )
331 Sequence< OUString > sendSeq { createIndex( { aURL, aName } ) };
333 ConfigItem::ClearNodeElements( "Store", sendSeq );
337 void StorageItem::clear()
339 ConfigItem::ClearNodeSet( "Store" );
343 void StorageItem::update( const OUString& aURL, const NamePasswordRecord& aRecord )
345 if ( !aRecord.HasPasswords( PERSISTENT_RECORD ) )
347 OSL_FAIL( "Unexpected storing of a record!" );
348 return;
351 Sequence< beans::PropertyValue > sendSeq{ comphelper::makePropertyValue(
352 "Store/Passwordstorage['" + createIndex( { aURL, aRecord.GetUserName() } ) + "']/InitializationVector",
353 aRecord.GetPersistentIV()), comphelper::makePropertyValue(
354 "Store/Passwordstorage['" + createIndex( { aURL, aRecord.GetUserName() } ) + "']/Password",
355 aRecord.GetPersistentPasswords()) };
357 ConfigItem::SetModified();
358 ConfigItem::SetSetProperties( "Store", sendSeq );
362 void StorageItem::Notify( const Sequence< OUString >& )
364 // this feature still should not be used
365 if( mainCont )
366 mainCont->Notify();
370 void StorageItem::ImplCommit()
372 // Do nothing, we stored everything we want already
376 PasswordContainer::PasswordContainer( const Reference<XComponentContext>& rxContext )
378 // m_pStorageFile->Notify() can be called
379 std::unique_lock aGuard( mMutex );
381 mComponent.set( rxContext->getServiceManager(), UNO_QUERY );
382 mComponent->addEventListener( this );
384 m_xStorageFile.emplace( this, "Office.Common/Passwords" );
385 if( m_xStorageFile->useStorage() )
386 m_aContainer = m_xStorageFile->getInfo();
390 PasswordContainer::~PasswordContainer()
392 std::unique_lock aGuard( mMutex );
394 m_xStorageFile.reset();
396 if( mComponent.is() )
398 mComponent->removeEventListener(this);
399 mComponent.clear();
403 void SAL_CALL PasswordContainer::disposing( const EventObject& )
405 std::unique_lock aGuard( mMutex );
407 m_xStorageFile.reset();
409 if( mComponent.is() )
411 //mComponent->removeEventListener(this);
412 mComponent.clear();
416 std::vector< OUString > PasswordContainer::DecodePasswords( std::u16string_view aLine, std::u16string_view aIV, std::u16string_view aMasterPasswd, css::task::PasswordRequestMode mode )
418 if( !aMasterPasswd.empty() )
420 rtlCipher aDecoder = rtl_cipher_create (rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream );
421 OSL_ENSURE( aDecoder, "Can't create decoder" );
423 if( aDecoder )
425 OSL_ENSURE( aMasterPasswd.size() == RTL_DIGEST_LENGTH_MD5 * 2, "Wrong master password format!" );
427 unsigned char code[RTL_DIGEST_LENGTH_MD5];
428 for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
429 code[ ind ] = static_cast<char>(o3tl::toUInt32(aMasterPasswd.substr( ind*2, 2 ), 16));
431 unsigned char iv[RTL_DIGEST_LENGTH_MD5] = {0};
432 if (!aIV.empty())
434 for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
436 auto tmp = aIV.substr( ind*2, 2 );
437 iv[ ind ] = static_cast<char>(rtl_ustr_toInt64_WithLength(tmp.data(), 16, tmp.size()));
441 rtlCipherError result = rtl_cipher_init (
442 aDecoder, rtl_Cipher_DirectionDecode,
443 code, RTL_DIGEST_LENGTH_MD5, iv, RTL_DIGEST_LENGTH_MD5 );
445 if( result == rtl_Cipher_E_None )
447 ::rtl::ByteSequence aSeq = getBufFromAsciiLine( aLine );
449 ::rtl::ByteSequence resSeq( aSeq.getLength() );
451 rtl_cipher_decode ( aDecoder, aSeq.getArray(), aSeq.getLength(),
452 reinterpret_cast<sal_uInt8*>(resSeq.getArray()), resSeq.getLength() );
454 OUString aPasswd( reinterpret_cast<char*>(resSeq.getArray()), resSeq.getLength(), RTL_TEXTENCODING_UTF8 );
456 rtl_cipher_destroy (aDecoder);
458 return getInfoFromInd( aPasswd );
461 rtl_cipher_destroy (aDecoder);
464 else
466 OSL_FAIL( "No master password provided!" );
467 // throw special exception
470 // problems with decoding
471 OSL_FAIL( "Problem with decoding" );
472 throw css::task::NoMasterException(
473 "Can't decode!", css::uno::Reference<css::uno::XInterface>(), mode);
476 OUString PasswordContainer::EncodePasswords(const std::vector< OUString >& lines, std::u16string_view aIV, std::u16string_view aMasterPasswd)
478 if( !aMasterPasswd.empty() )
480 OString aSeq = OUStringToOString( createIndex( lines ), RTL_TEXTENCODING_UTF8 );
482 rtlCipher aEncoder = rtl_cipher_create (rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream );
483 OSL_ENSURE( aEncoder, "Can't create encoder" );
485 if( aEncoder )
487 OSL_ENSURE( aMasterPasswd.size() == RTL_DIGEST_LENGTH_MD5 * 2, "Wrong master password format!" );
489 unsigned char code[RTL_DIGEST_LENGTH_MD5];
490 for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
491 code[ ind ] = static_cast<char>(o3tl::toUInt32(aMasterPasswd.substr( ind*2, 2 ), 16));
493 unsigned char iv[RTL_DIGEST_LENGTH_MD5] = {0};
494 if (!aIV.empty())
496 for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
498 auto tmp = aIV.substr( ind*2, 2 );
499 iv[ ind ] = static_cast<char>(rtl_ustr_toInt64_WithLength(tmp.data(), 16, tmp.size()));
503 rtlCipherError result = rtl_cipher_init (
504 aEncoder, rtl_Cipher_DirectionEncode,
505 code, RTL_DIGEST_LENGTH_MD5, iv, RTL_DIGEST_LENGTH_MD5 );
507 if( result == rtl_Cipher_E_None )
509 ::rtl::ByteSequence resSeq(aSeq.getLength()+1);
511 result = rtl_cipher_encode ( aEncoder, aSeq.getStr(), aSeq.getLength()+1,
512 reinterpret_cast<sal_uInt8*>(resSeq.getArray()), resSeq.getLength() );
515 //test
516 rtlCipherError result = rtl_cipher_init (
517 aEncoder, rtl_Cipher_DirectionDecode,
518 code, RTL_DIGEST_LENGTH_MD5, NULL, 0 );
521 if( result == rtl_Cipher_E_None )
523 OUString testOU = getAsciiLine( resSeq );
524 ::rtl::ByteSequence aSeq1 = getBufFromAsciiLine( testOU );
526 ::rtl::ByteSequence resSeq1( aSeq1.getLength() );
528 if( resSeq.getLength() == aSeq1.getLength() )
530 for( int ind = 0; ind < aSeq1.getLength(); ind++ )
531 if( resSeq[ind] != aSeq1[ind] )
532 testOU = "";
535 result = rtl_cipher_decode ( aEncoder, (sal_uInt8*)aSeq1.getArray(), aSeq1.getLength(),
536 (sal_uInt8*)resSeq1.getArray(), resSeq1.getLength() );
538 OUString aPasswd( ( char* )resSeq1.getArray(), resSeq1.getLength(), RTL_TEXTENCODING_UTF8 );
542 rtl_cipher_destroy (aEncoder);
544 if( result == rtl_Cipher_E_None )
545 return getAsciiLine( resSeq );
549 rtl_cipher_destroy (aEncoder);
552 else
554 OSL_FAIL( "No master password provided!" );
555 // throw special exception
558 // problems with encoding
559 OSL_FAIL( "Problem with encoding" );
560 throw RuntimeException("Can't encode!" );
563 void PasswordContainer::UpdateVector( const OUString& aURL, std::vector< NamePasswordRecord >& toUpdate, NamePasswordRecord const & aRecord, bool writeFile )
565 for (auto & aNPIter : toUpdate)
566 if( aNPIter.GetUserName() == aRecord.GetUserName() )
568 if( aRecord.HasPasswords( MEMORY_RECORD ) )
569 aNPIter.SetMemoryPasswords( aRecord.GetMemoryPasswords() );
571 if( aRecord.HasPasswords( PERSISTENT_RECORD ) )
573 aNPIter.SetPersistentPasswords( aRecord.GetPersistentPasswords(), aRecord.GetPersistentIV() );
575 if( writeFile )
577 // the password must be already encoded
578 m_xStorageFile->update( aURL, aRecord ); // change existing ( aURL, aName ) record in the configfile
582 return;
586 if( aRecord.HasPasswords( PERSISTENT_RECORD ) && writeFile )
588 // the password must be already encoded
589 m_xStorageFile->update( aURL, aRecord ); // add new aName to the existing url
592 toUpdate.insert( toUpdate.begin(), aRecord );
596 UserRecord PasswordContainer::CopyToUserRecord( const NamePasswordRecord& aRecord, bool& io_bTryToDecode, const Reference< XInteractionHandler >& aHandler )
598 ::std::vector< OUString > aPasswords;
599 if( aRecord.HasPasswords( MEMORY_RECORD ) )
600 aPasswords = aRecord.GetMemoryPasswords();
602 if( io_bTryToDecode && aRecord.HasPasswords( PERSISTENT_RECORD ) )
606 ::std::vector< OUString > aDecodedPasswords = DecodePasswords( aRecord.GetPersistentPasswords(), aRecord.GetPersistentIV(),
607 GetMasterPassword( aHandler ), css::task::PasswordRequestMode_PASSWORD_ENTER );
608 aPasswords.insert( aPasswords.end(), aDecodedPasswords.begin(), aDecodedPasswords.end() );
610 catch( NoMasterException& )
612 // if master password could not be detected the entry will be just ignored
613 io_bTryToDecode = false;
617 return UserRecord( aRecord.GetUserName(), comphelper::containerToSequence( aPasswords ) );
621 Sequence< UserRecord > PasswordContainer::CopyToUserRecordSequence( const std::vector< NamePasswordRecord >& original, const Reference< XInteractionHandler >& aHandler )
623 Sequence< UserRecord > aResult( original.size() );
624 auto aResultRange = asNonConstRange(aResult);
625 sal_uInt32 nInd = 0;
626 bool bTryToDecode = true;
628 for (auto const& aNPIter : original)
630 aResultRange[nInd] = CopyToUserRecord( aNPIter, bTryToDecode, aHandler );
631 ++nInd;
634 return aResult;
638 void SAL_CALL PasswordContainer::add( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, const Reference< XInteractionHandler >& aHandler )
640 std::unique_lock aGuard( mMutex );
642 PrivateAdd( Url, UserName, Passwords, MEMORY_RECORD, aHandler );
646 void SAL_CALL PasswordContainer::addPersistent( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, const Reference< XInteractionHandler >& aHandler )
648 std::unique_lock aGuard( mMutex );
650 PrivateAdd( Url, UserName, Passwords, PERSISTENT_RECORD, aHandler );
653 OUString PasswordContainer::createIV()
655 unsigned char iv[RTL_DIGEST_LENGTH_MD5];
656 if (rtl_random_getBytes(nullptr, iv, RTL_DIGEST_LENGTH_MD5) != rtl_Random_E_None)
658 throw uno::RuntimeException("rtl_random_getBytes failed");
660 OUStringBuffer aBuffer;
661 for (sal_uInt8 i : iv)
663 aBuffer.append(OUString::number(i >> 4, 16) + OUString::number(i & 15, 16));
665 return aBuffer.makeStringAndClear();
668 void PasswordContainer::PrivateAdd( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, char Mode, const Reference< XInteractionHandler >& aHandler )
670 NamePasswordRecord aRecord( UserName );
671 ::std::vector< OUString > aStorePass = comphelper::sequenceToContainer< std::vector<OUString> >( Passwords );
673 if( Mode == PERSISTENT_RECORD )
675 OUString sIV = createIV();
676 OUString sEncodedPasswords = EncodePasswords(aStorePass, sIV, GetMasterPassword(aHandler));
677 aRecord.SetPersistentPasswords(sEncodedPasswords, sIV);
679 else if( Mode == MEMORY_RECORD )
680 aRecord.SetMemoryPasswords( std::move(aStorePass) );
681 else
683 OSL_FAIL( "Unexpected persistence status!" );
684 return;
687 if( !m_aContainer.empty() )
689 PasswordMap::iterator aIter = m_aContainer.find( Url );
691 if( aIter != m_aContainer.end() )
693 UpdateVector( aIter->first, aIter->second, aRecord, true );
694 return;
698 std::vector< NamePasswordRecord > listToAdd( 1, aRecord );
699 m_aContainer.insert( PairUrlRecord( Url, listToAdd ) );
701 if( Mode == PERSISTENT_RECORD && m_xStorageFile && m_xStorageFile->useStorage() )
702 m_xStorageFile->update( Url, aRecord );
707 UrlRecord SAL_CALL PasswordContainer::find( const OUString& aURL, const Reference< XInteractionHandler >& aHandler )
709 return find( aURL, u"", false, aHandler );
713 UrlRecord SAL_CALL PasswordContainer::findForName( const OUString& aURL, const OUString& aName, const Reference< XInteractionHandler >& aHandler )
715 return find( aURL, aName, true, aHandler );
719 Sequence< UserRecord > PasswordContainer::FindUsr( const std::vector< NamePasswordRecord >& userlist, std::u16string_view aName, const Reference< XInteractionHandler >& aHandler )
721 for (auto const& aNPIter : userlist)
723 if( aNPIter.GetUserName() == aName )
725 bool bTryToDecode = true;
726 Sequence< UserRecord > aResult { CopyToUserRecord( aNPIter, bTryToDecode, aHandler ) };
728 return aResult;
732 return Sequence< UserRecord >();
736 bool PasswordContainer::createUrlRecord(
737 const PasswordMap::iterator & rIter,
738 bool bName,
739 std::u16string_view aName,
740 const Reference< XInteractionHandler >& aHandler,
741 UrlRecord & rRec )
743 if ( bName )
745 Sequence< UserRecord > aUsrRec
746 = FindUsr( rIter->second, aName, aHandler );
747 if( aUsrRec.hasElements() )
749 rRec = UrlRecord( rIter->first, aUsrRec );
750 return true;
753 else
755 rRec = UrlRecord(
756 rIter->first,
757 CopyToUserRecordSequence( rIter->second, aHandler ) );
758 return true;
760 return false;
764 UrlRecord PasswordContainer::find(
765 const OUString& aURL,
766 std::u16string_view aName,
767 bool bName, // only needed to support empty user names
768 const Reference< XInteractionHandler >& aHandler )
770 std::unique_lock aGuard( mMutex );
772 if( !m_aContainer.empty() && !aURL.isEmpty() )
774 OUString aUrl( aURL );
776 // each iteration remove last '/...' section from the aUrl
777 // while it's possible, up to the most left '://'
780 // first look for <url>/somename and then look for <url>/somename/...
781 PasswordMap::iterator aIter = m_aContainer.find( aUrl );
782 if( aIter != m_aContainer.end() )
784 UrlRecord aRec;
785 if ( createUrlRecord( aIter, bName, aName, aHandler, aRec ) )
786 return aRec;
788 else
790 OUString tmpUrl( aUrl );
791 if ( !tmpUrl.endsWith("/") )
792 tmpUrl += "/";
794 aIter = m_aContainer.lower_bound( tmpUrl );
795 if( aIter != m_aContainer.end() && aIter->first.match( tmpUrl ) )
797 UrlRecord aRec;
798 if ( createUrlRecord( aIter, bName, aName, aHandler, aRec ) )
799 return aRec;
803 while( shorterUrl( aUrl ) && !aUrl.isEmpty() );
806 return UrlRecord();
809 OUString PasswordContainer::GetDefaultMasterPassword()
811 OUStringBuffer aResult;
812 for ( sal_Int32 nInd = 0; nInd < RTL_DIGEST_LENGTH_MD5; nInd++ )
813 aResult.append("aa");
815 return aResult.makeStringAndClear();
818 OUString PasswordContainer::RequestPasswordFromUser( PasswordRequestMode aRMode, const uno::Reference< task::XInteractionHandler >& xHandler )
820 // empty string means that the call was cancelled or just failed
821 OUString aResult;
823 if ( xHandler.is() )
825 ::rtl::Reference< MasterPasswordRequest_Impl > xRequest = new MasterPasswordRequest_Impl( aRMode );
827 xHandler->handle( xRequest );
829 ::rtl::Reference< ucbhelper::InteractionContinuation > xSelection = xRequest->getSelection();
831 if ( xSelection.is() )
833 Reference< XInteractionAbort > xAbort( xSelection.get(), UNO_QUERY );
834 if ( !xAbort.is() )
836 const ::rtl::Reference< ucbhelper::InteractionSupplyAuthentication > & xSupp
837 = xRequest->getAuthenticationSupplier();
839 aResult = xSupp->getPassword();
844 return aResult;
847 // Mangle the key to match an old bug
848 static OUString ReencodeAsOldHash(std::u16string_view rPass)
850 OUStringBuffer aBuffer;
851 for (int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ++ind)
853 auto tmp = rPass.substr(ind * 2, 2);
854 unsigned char i = static_cast<char>(rtl_ustr_toInt64_WithLength(tmp.data(), 16, tmp.size()));
855 aBuffer.append(OUStringChar(static_cast< sal_Unicode >('a' + (i >> 4)))
856 + OUStringChar(static_cast< sal_Unicode >('a' + (i & 15))));
858 return aBuffer.makeStringAndClear();
861 OUString const & PasswordContainer::GetMasterPassword( const Reference< XInteractionHandler >& aHandler )
863 PasswordRequestMode aRMode = PasswordRequestMode_PASSWORD_ENTER;
864 if( !m_xStorageFile || !m_xStorageFile->useStorage() )
865 throw NoMasterException("Password storing is not active!", Reference< XInterface >(), aRMode );
867 if( m_aMasterPassword.isEmpty() && aHandler.is() )
869 OUString aEncodedMP, aEncodedMPIV;
870 bool bDefaultPassword = false;
872 if( !m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) )
873 aRMode = PasswordRequestMode_PASSWORD_CREATE;
874 else if ( aEncodedMP.isEmpty() )
876 m_aMasterPassword = GetDefaultMasterPassword();
877 bDefaultPassword = true;
880 if ( !bDefaultPassword )
882 bool bAskAgain = false;
883 do {
884 bAskAgain = false;
886 OUString aPass = RequestPasswordFromUser( aRMode, aHandler );
887 if ( !aPass.isEmpty() )
889 if( aRMode == PasswordRequestMode_PASSWORD_CREATE )
891 m_aMasterPassword = aPass;
892 std::vector< OUString > aMaster( 1, m_aMasterPassword );
894 OUString sIV = createIV();
895 m_xStorageFile->setEncodedMasterPassword(EncodePasswords(aMaster, sIV, m_aMasterPassword), sIV);
897 else
899 if (m_xStorageFile->getStorageVersion() == 0)
900 aPass = ReencodeAsOldHash(aPass);
902 std::vector< OUString > aRM( DecodePasswords( aEncodedMP, aEncodedMPIV, aPass, aRMode ) );
903 if( aRM.empty() || aPass != aRM[0] )
905 bAskAgain = true;
906 aRMode = PasswordRequestMode_PASSWORD_REENTER;
908 else
909 m_aMasterPassword = aPass;
913 } while( bAskAgain );
917 if ( m_aMasterPassword.isEmpty() )
918 throw NoMasterException("No master password!", Reference< XInterface >(), aRMode );
920 return m_aMasterPassword;
924 void SAL_CALL PasswordContainer::remove( const OUString& aURL, const OUString& aName )
926 std::unique_lock aGuard( mMutex );
928 OUString aUrl( aURL );
929 if( m_aContainer.empty() )
930 return;
932 PasswordMap::iterator aIter = m_aContainer.find( aUrl );
934 if( aIter == m_aContainer.end() )
936 if( aUrl.endsWith("/") )
937 aUrl = aUrl.copy( 0, aUrl.getLength() - 1 );
938 else
939 aUrl += "/";
941 aIter = m_aContainer.find( aUrl );
944 if( aIter == m_aContainer.end() )
945 return;
947 auto aNPIter = std::find_if(aIter->second.begin(), aIter->second.end(),
948 [&aName](const NamePasswordRecord& rNPRecord) { return rNPRecord.GetUserName() == aName; });
950 if (aNPIter != aIter->second.end())
952 if( aNPIter->HasPasswords( PERSISTENT_RECORD ) && m_xStorageFile )
953 m_xStorageFile->remove( aURL, aName ); // remove record ( aURL, aName )
955 // the iterator will not be used any more so it can be removed directly
956 aIter->second.erase( aNPIter );
958 if( aIter->second.empty() )
959 m_aContainer.erase( aIter );
964 void SAL_CALL PasswordContainer::removePersistent( const OUString& aURL, const OUString& aName )
966 std::unique_lock aGuard( mMutex );
968 OUString aUrl( aURL );
969 if( m_aContainer.empty() )
970 return;
972 PasswordMap::iterator aIter = m_aContainer.find( aUrl );
974 if( aIter == m_aContainer.end() )
976 if( aUrl.endsWith("/") )
977 aUrl = aUrl.copy( 0, aUrl.getLength() - 1 );
978 else
979 aUrl += "/";
981 aIter = m_aContainer.find( aUrl );
984 if( aIter == m_aContainer.end() )
985 return;
987 auto aNPIter = std::find_if(aIter->second.begin(), aIter->second.end(),
988 [&aName](const NamePasswordRecord& rNPRecord) { return rNPRecord.GetUserName() == aName; });
990 if (aNPIter == aIter->second.end())
991 return;
993 if( aNPIter->HasPasswords( PERSISTENT_RECORD ) )
995 // TODO/LATER: should the password be converted to MemoryPassword?
996 aNPIter->RemovePasswords( PERSISTENT_RECORD );
998 if ( m_xStorageFile )
999 m_xStorageFile->remove( aURL, aName ); // remove record ( aURL, aName )
1002 if( !aNPIter->HasPasswords( MEMORY_RECORD ) )
1003 aIter->second.erase( aNPIter );
1005 if( aIter->second.empty() )
1006 m_aContainer.erase( aIter );
1009 void SAL_CALL PasswordContainer::removeAllPersistent()
1011 std::unique_lock aGuard(mMutex);
1012 removeAllPersistent(aGuard);
1015 void PasswordContainer::removeAllPersistent(std::unique_lock<std::mutex>& /*rGuard*/)
1017 if( m_xStorageFile )
1018 m_xStorageFile->clear();
1020 for( PasswordMap::iterator aIter = m_aContainer.begin(); aIter != m_aContainer.end(); )
1022 for( std::vector< NamePasswordRecord >::iterator aNPIter = aIter->second.begin(); aNPIter != aIter->second.end(); )
1024 if( aNPIter->HasPasswords( PERSISTENT_RECORD ) )
1026 // TODO/LATER: should the password be converted to MemoryPassword?
1027 aNPIter->RemovePasswords( PERSISTENT_RECORD );
1029 if ( m_xStorageFile )
1030 m_xStorageFile->remove( aIter->first, aNPIter->GetUserName() ); // remove record ( aURL, aName )
1033 if( !aNPIter->HasPasswords( MEMORY_RECORD ) )
1035 aNPIter = aIter->second.erase(aNPIter);
1037 else
1038 ++aNPIter;
1041 if( aIter->second.empty() )
1043 aIter = m_aContainer.erase(aIter);
1045 else
1046 ++aIter;
1050 Sequence< UrlRecord > SAL_CALL PasswordContainer::getAllPersistent( const Reference< XInteractionHandler >& xHandler )
1052 std::unique_lock aGuard( mMutex );
1053 return getAllPersistent(aGuard, xHandler);
1056 Sequence< UrlRecord > PasswordContainer::getAllPersistent( std::unique_lock<std::mutex>& /*rGuard*/, const Reference< XInteractionHandler >& xHandler )
1058 Sequence< UrlRecord > aResult;
1060 for( const auto& rEntry : m_aContainer )
1062 Sequence< UserRecord > aUsers;
1063 for (auto const& aNP : rEntry.second)
1064 if( aNP.HasPasswords( PERSISTENT_RECORD ) )
1066 sal_Int32 oldLen = aUsers.getLength();
1067 aUsers.realloc( oldLen + 1 );
1068 aUsers.getArray()[ oldLen ] = UserRecord( aNP.GetUserName(), comphelper::containerToSequence( DecodePasswords( aNP.GetPersistentPasswords(), aNP.GetPersistentIV(),
1069 GetMasterPassword( xHandler ), css::task::PasswordRequestMode_PASSWORD_ENTER ) ) );
1072 if( aUsers.hasElements() )
1074 sal_Int32 oldLen = aResult.getLength();
1075 aResult.realloc( oldLen + 1 );
1076 aResult.getArray()[ oldLen ] = UrlRecord( rEntry.first, aUsers );
1080 return aResult;
1083 sal_Bool SAL_CALL PasswordContainer::authorizateWithMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler )
1085 std::unique_lock aGuard( mMutex );
1086 return authorizateWithMasterPassword(aGuard, xHandler);
1089 bool PasswordContainer::authorizateWithMasterPassword( std::unique_lock<std::mutex>& /*rGuard*/, const uno::Reference< task::XInteractionHandler >& xHandler )
1091 bool bResult = false;
1092 OUString aEncodedMP, aEncodedMPIV;
1093 uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler;
1095 // the method should fail if there is no master password
1096 if( m_xStorageFile && m_xStorageFile->useStorage() && m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) )
1098 if ( aEncodedMP.isEmpty() )
1100 // this is a default master password
1101 // no UI is necessary
1102 bResult = true;
1104 else
1106 if ( !xTmpHandler.is() )
1108 uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW );
1109 uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) );
1110 xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
1113 if ( !m_aMasterPassword.isEmpty() )
1115 // there is a password, it should be just rechecked
1116 PasswordRequestMode aRMode = PasswordRequestMode_PASSWORD_ENTER;
1117 OUString aPass;
1119 do {
1120 aPass = RequestPasswordFromUser( aRMode, xTmpHandler );
1122 if (!aPass.isEmpty() && m_xStorageFile->getStorageVersion() == 0)
1124 aPass = ReencodeAsOldHash(aPass);
1127 bResult = ( !aPass.isEmpty() && aPass == m_aMasterPassword );
1128 aRMode = PasswordRequestMode_PASSWORD_REENTER; // further questions with error notification
1129 } while( !bResult && !aPass.isEmpty() );
1131 else
1135 // ask for the password, if user provide no correct password an exception will be thrown
1136 bResult = !GetMasterPassword( xTmpHandler ).isEmpty();
1138 catch( uno::Exception& )
1144 return bResult;
1147 sal_Bool SAL_CALL PasswordContainer::changeMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler )
1149 bool bResult = false;
1150 uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler;
1151 std::unique_lock aGuard( mMutex );
1153 if ( m_xStorageFile && m_xStorageFile->useStorage() )
1155 if ( !xTmpHandler.is() )
1157 uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW );
1158 uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) );
1159 xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
1162 bool bCanChangePassword = true;
1163 // if there is already a stored master password it should be entered by the user before the change happen
1164 OUString aEncodedMP, aEncodedMPIV;
1165 if( !m_aMasterPassword.isEmpty() || m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) )
1166 bCanChangePassword = authorizateWithMasterPassword( aGuard, xTmpHandler );
1168 if ( bCanChangePassword )
1170 // ask for the new password, but do not set it
1171 OUString aPass = RequestPasswordFromUser( PasswordRequestMode_PASSWORD_CREATE, xTmpHandler );
1173 if ( !aPass.isEmpty() )
1175 // get all the persistent entries if it is possible
1176 const Sequence< UrlRecord > aPersistent = getAllPersistent( aGuard, uno::Reference< task::XInteractionHandler >() );
1178 // remove the master password and the entries persistence
1179 removeMasterPassword(aGuard);
1181 // store the new master password
1182 m_aMasterPassword = aPass;
1183 std::vector< OUString > aMaster( 1, m_aMasterPassword );
1184 OUString aIV = createIV();
1185 m_xStorageFile->setEncodedMasterPassword(EncodePasswords(aMaster, aIV, m_aMasterPassword), aIV);
1187 // store all the entries with the new password
1188 for ( const auto& rURL : aPersistent )
1189 for ( const auto& rUser : rURL.UserList )
1190 PrivateAdd( rURL.Url, rUser.UserName, rUser.Passwords, PERSISTENT_RECORD,
1191 uno::Reference< task::XInteractionHandler >() );
1193 bResult = true;
1198 return bResult;
1201 void SAL_CALL PasswordContainer::removeMasterPassword()
1203 std::unique_lock aGuard(mMutex);
1204 removeMasterPassword(aGuard);
1207 void PasswordContainer::removeMasterPassword(std::unique_lock<std::mutex>& rGuard)
1209 // remove all the stored passwords and the master password
1210 removeAllPersistent(rGuard);
1212 if ( m_xStorageFile )
1214 m_aMasterPassword.clear();
1215 m_xStorageFile->setEncodedMasterPassword( OUString(), OUString() ); // let the master password be removed from configuration
1219 sal_Bool SAL_CALL PasswordContainer::hasMasterPassword( )
1221 std::unique_lock aGuard( mMutex );
1223 if ( !m_xStorageFile )
1224 throw uno::RuntimeException();
1226 OUString aEncodedMP, aEncodedMPIV;
1227 return ( m_xStorageFile->useStorage() && m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) );
1230 sal_Bool SAL_CALL PasswordContainer::allowPersistentStoring( sal_Bool bAllow )
1232 std::unique_lock aGuard( mMutex );
1234 if ( !m_xStorageFile )
1235 throw uno::RuntimeException();
1237 if ( !bAllow )
1238 removeMasterPassword(aGuard);
1240 if (m_xStorageFile->useStorage() == static_cast<bool>(bAllow))
1241 return bAllow;
1243 m_xStorageFile->setUseStorage( bAllow );
1244 return !bAllow;
1247 sal_Bool SAL_CALL PasswordContainer::isPersistentStoringAllowed()
1249 std::unique_lock aGuard( mMutex );
1251 if ( !m_xStorageFile )
1252 throw uno::RuntimeException();
1254 return m_xStorageFile->useStorage();
1257 sal_Bool SAL_CALL PasswordContainer::useDefaultMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler )
1259 bool bResult = false;
1260 uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler;
1261 std::unique_lock aGuard( mMutex );
1263 if ( m_xStorageFile && m_xStorageFile->useStorage() )
1265 if ( !xTmpHandler.is() )
1267 uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW );
1268 uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) );
1269 xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
1272 bool bCanChangePassword = true;
1273 // if there is already a stored nondefault master password it should be entered by the user before the change happen
1274 OUString aEncodedMP, aEncodedMPIV;
1275 if( m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) && !aEncodedMP.isEmpty() )
1276 bCanChangePassword = authorizateWithMasterPassword( aGuard, xTmpHandler );
1278 if ( bCanChangePassword )
1280 // generate the default password
1281 OUString aPass = GetDefaultMasterPassword();
1282 if ( !aPass.isEmpty() )
1284 // get all the persistent entries if it is possible
1285 const Sequence< UrlRecord > aPersistent = getAllPersistent( aGuard, uno::Reference< task::XInteractionHandler >() );
1287 // remove the master password and the entries persistence
1288 removeMasterPassword(aGuard);
1290 // store the empty string to flag the default master password
1291 m_aMasterPassword = aPass;
1292 m_xStorageFile->setEncodedMasterPassword( OUString(), OUString(), true );
1294 // store all the entries with the new password
1295 for ( const auto& rURL : aPersistent )
1296 for ( const auto& rUser : rURL.UserList )
1297 PrivateAdd( rURL.Url, rUser.UserName, rUser.Passwords, PERSISTENT_RECORD,
1298 uno::Reference< task::XInteractionHandler >() );
1300 bResult = true;
1305 return bResult;
1309 sal_Bool SAL_CALL PasswordContainer::isDefaultMasterPasswordUsed()
1311 std::unique_lock aGuard( mMutex );
1313 if ( !m_xStorageFile )
1314 throw uno::RuntimeException();
1316 OUString aEncodedMP, aEncodedMPIV;
1317 return ( m_xStorageFile->useStorage() && m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) && aEncodedMP.isEmpty() );
1321 void SAL_CALL PasswordContainer::addUrl( const OUString& Url, sal_Bool MakePersistent )
1323 mUrlContainer.add( Url, MakePersistent );
1326 OUString SAL_CALL PasswordContainer::findUrl( const OUString& Url )
1328 return mUrlContainer.find( Url );
1331 void SAL_CALL PasswordContainer::removeUrl( const OUString& Url )
1333 mUrlContainer.remove( Url );
1336 uno::Sequence< OUString > SAL_CALL PasswordContainer::getUrls( sal_Bool OnlyPersistent )
1338 return mUrlContainer.list( OnlyPersistent );
1342 void PasswordContainer::Notify()
1344 std::unique_lock aGuard( mMutex );
1346 // remove the cached persistent values in the memory
1347 for( auto& rEntry : m_aContainer )
1349 for( std::vector< NamePasswordRecord >::iterator aNPIter = rEntry.second.begin(); aNPIter != rEntry.second.end(); )
1351 if( aNPIter->HasPasswords( PERSISTENT_RECORD ) )
1353 aNPIter->RemovePasswords( PERSISTENT_RECORD );
1355 if ( m_xStorageFile )
1356 m_xStorageFile->remove( rEntry.first, aNPIter->GetUserName() ); // remove record ( aURL, aName )
1359 if( !aNPIter->HasPasswords( MEMORY_RECORD ) )
1361 aNPIter = rEntry.second.erase(aNPIter);
1363 else
1364 ++aNPIter;
1368 PasswordMap addon;
1369 if( m_xStorageFile )
1370 addon = m_xStorageFile->getInfo();
1372 for( const auto& rEntry : addon )
1374 PasswordMap::iterator aSearchIter = m_aContainer.find( rEntry.first );
1375 if( aSearchIter != m_aContainer.end() )
1376 for (auto const& aNP : rEntry.second)
1377 UpdateVector( aSearchIter->first, aSearchIter->second, aNP, false );
1378 else
1379 m_aContainer.insert( PairUrlRecord( rEntry.first, rEntry.second ) );
1383 OUString SAL_CALL PasswordContainer::getImplementationName( )
1385 return "stardiv.svl.PasswordContainer";
1388 sal_Bool SAL_CALL PasswordContainer::supportsService( const OUString& ServiceName )
1390 return cppu::supportsService( this, ServiceName );
1393 Sequence< OUString > SAL_CALL PasswordContainer::getSupportedServiceNames( )
1395 return { "com.sun.star.task.PasswordContainer" };
1398 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
1399 svl_PasswordContainer_get_implementation(
1400 css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
1402 return cppu::acquire(new PasswordContainer(context));
1406 MasterPasswordRequest_Impl::MasterPasswordRequest_Impl( PasswordRequestMode Mode )
1408 MasterPasswordRequest aRequest;
1410 aRequest.Classification = InteractionClassification_ERROR;
1411 aRequest.Mode = Mode;
1413 setRequest( Any( aRequest ) );
1415 // Fill continuations...
1416 Sequence< RememberAuthentication > aRememberModes{ RememberAuthentication_NO };
1418 m_xAuthSupplier
1419 = new ::ucbhelper::InteractionSupplyAuthentication(
1420 this,
1421 false, // bCanSetRealm
1422 false, // bCanSetUserName
1423 true, // bCanSetPassword
1424 false, // bCanSetAccount
1425 aRememberModes, // rRememberPasswordModes
1426 RememberAuthentication_NO, // eDefaultRememberPasswordMode
1427 aRememberModes, // rRememberAccountModes
1428 RememberAuthentication_NO, // eDefaultRememberAccountMode
1429 false // bCanUseSystemCredentials
1432 Sequence<
1433 Reference< XInteractionContinuation > > aContinuations{
1434 new ::ucbhelper::InteractionAbort( this ),
1435 new ::ucbhelper::InteractionRetry( this ),
1436 m_xAuthSupplier
1439 setContinuations( aContinuations );
1444 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */