VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxCredProv/VBoxCredProvCredential.cpp@ 103914

Last change on this file since 103914 was 98103, checked in by vboxsync, 2 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.2 KB
Line 
1/* $Id: VBoxCredProvCredential.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBoxCredProvCredential - Class for keeping and handling the passed credentials.
4 */
5
6/*
7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#ifndef WIN32_NO_STATUS
33# include <ntstatus.h>
34# define WIN32_NO_STATUS
35#endif
36#include <iprt/win/intsafe.h>
37
38#include "VBoxCredentialProvider.h"
39
40#include "VBoxCredProvProvider.h"
41#include "VBoxCredProvCredential.h"
42#include "VBoxCredProvUtils.h"
43
44#include <lm.h>
45
46#include <iprt/initterm.h>
47#include <iprt/mem.h>
48#include <iprt/string.h>
49#include <iprt/utf16.h>
50
51
52
53
54VBoxCredProvCredential::VBoxCredProvCredential(void)
55 : m_cRefs(1)
56 , m_enmUsageScenario(CPUS_INVALID)
57 , m_pEvents(NULL)
58 , m_fHaveCreds(false)
59{
60 VBoxCredProvVerbose(0, "VBoxCredProvCredential: Created\n");
61 VBoxCredentialProviderAcquire();
62
63 for (unsigned i = 0; i < VBOXCREDPROV_NUM_FIELDS; i++)
64 {
65 const VBOXCREDPROV_FIELD *pField = &s_VBoxCredProvDefaultFields[i];
66
67 m_apwszFields[i] = RTUtf16Dup(pField->desc.pszLabel ? pField->desc.pszLabel : L"");
68 AssertPtr(m_apwszFields[i]);
69 }
70}
71
72
73VBoxCredProvCredential::~VBoxCredProvCredential(void)
74{
75 VBoxCredProvVerbose(0, "VBoxCredProvCredential: Destroying\n");
76
77 Reset();
78
79 for (unsigned i = 0; i < VBOXCREDPROV_NUM_FIELDS; i++)
80 {
81 if (m_apwszFields[i])
82 {
83 RTUtf16Free(m_apwszFields[i]);
84 m_apwszFields[i] = NULL;
85 }
86 }
87
88 VBoxCredentialProviderRelease();
89}
90
91
92ULONG VBoxCredProvCredential::AddRef(void)
93{
94 LONG cRefs = InterlockedIncrement(&m_cRefs);
95 VBoxCredProvVerbose(0, "VBoxCredProvCredential::AddRef: Returning refcount=%ld\n",
96 cRefs);
97 return cRefs;
98}
99
100
101ULONG VBoxCredProvCredential::Release(void)
102{
103 LONG cRefs = InterlockedDecrement(&m_cRefs);
104 VBoxCredProvVerbose(0, "VBoxCredProvCredential::Release: Returning refcount=%ld\n",
105 cRefs);
106 if (!cRefs)
107 {
108 VBoxCredProvVerbose(0, "VBoxCredProvCredential: Calling destructor\n");
109 delete this;
110 }
111 return cRefs;
112}
113
114
115HRESULT VBoxCredProvCredential::QueryInterface(REFIID interfaceID, void **ppvInterface)
116{
117 HRESULT hr = S_OK;;
118 if (ppvInterface)
119 {
120 if ( IID_IUnknown == interfaceID
121 || IID_ICredentialProviderCredential == interfaceID)
122 {
123 *ppvInterface = static_cast<IUnknown*>(this);
124 reinterpret_cast<IUnknown*>(*ppvInterface)->AddRef();
125 }
126 else
127 {
128 *ppvInterface = NULL;
129 hr = E_NOINTERFACE;
130 }
131 }
132 else
133 hr = E_INVALIDARG;
134
135 return hr;
136}
137
138
139/**
140 * Assigns or copies a RTUTF16 string to a UNICODE_STRING.
141 *
142 * When fCopy is false, this does *not* copy its contents
143 * and only assigns its code points to the destination!
144 * When fCopy is true, the actual string buffer gets copied.
145 *
146 * Does not take terminating \0 into account.
147 *
148 * @return HRESULT
149 * @param pUnicodeDest Unicode string assigning the UTF16 string to.
150 * @param pwszSource UTF16 string to assign.
151 * @param fCopy Whether to just assign or copy the actual buffer
152 * contents from source -> dest.
153 */
154HRESULT VBoxCredProvCredential::RTUTF16ToUnicode(PUNICODE_STRING pUnicodeDest, PRTUTF16 pwszSource, bool fCopy)
155{
156 AssertPtrReturn(pUnicodeDest, E_POINTER);
157 AssertPtrReturn(pwszSource, E_POINTER);
158
159 size_t cbLen = RTUtf16Len(pwszSource) * sizeof(RTUTF16);
160 AssertReturn(cbLen <= USHORT_MAX, E_INVALIDARG);
161
162 HRESULT hr;
163
164 if (fCopy)
165 {
166 if (cbLen <= pUnicodeDest->MaximumLength)
167 {
168 memcpy(pUnicodeDest->Buffer, pwszSource, cbLen);
169 pUnicodeDest->Length = (USHORT)cbLen;
170 hr = S_OK;
171 }
172 else
173 hr = E_INVALIDARG;
174 }
175 else /* Just assign the buffer. */
176 {
177 pUnicodeDest->Buffer = pwszSource;
178 pUnicodeDest->Length = (USHORT)cbLen;
179 hr = S_OK;
180 }
181
182 return hr;
183}
184
185
186/**
187 * Copies an UTF16 string into a PUNICODE_STRING by allocating space for it.
188 *
189 * @return HRESULT
190 * @param pUnicodeDest Where to store the copied (allocated) unicode string.
191 * @param pwszSource UTF16 string to copy.
192 */
193HRESULT VBoxCredProvCredential::RTUTF16ToUnicodeA(PUNICODE_STRING pUnicodeDest, PRTUTF16 pwszSource)
194{
195 AssertPtrReturn(pUnicodeDest, E_POINTER);
196 AssertPtrReturn(pwszSource, E_POINTER);
197
198 size_t cbLen = RTUtf16Len(pwszSource) * sizeof(RTUTF16);
199
200 pUnicodeDest->Buffer = (LPWSTR)CoTaskMemAlloc(cbLen);
201
202 if (!pUnicodeDest->Buffer)
203 return E_OUTOFMEMORY;
204
205 pUnicodeDest->MaximumLength = (USHORT)cbLen;
206 pUnicodeDest->Length = 0;
207
208 return RTUTF16ToUnicode(pUnicodeDest, pwszSource, true /* fCopy */);
209}
210
211
212/**
213 * Frees a formerly allocated PUNICODE_STRING.
214 *
215 * @param pUnicode String to free.
216 */
217void VBoxCredProvCredential::UnicodeStringFree(PUNICODE_STRING pUnicode)
218{
219 if (!pUnicode)
220 return;
221
222 if (pUnicode->Buffer)
223 {
224 Assert(pUnicode->MaximumLength);
225
226 /* Make sure to wipe contents before free'ing. */
227 RTMemWipeThoroughly(pUnicode->Buffer, pUnicode->MaximumLength /* MaximumLength is bytes! */, 3 /* Passes */);
228
229 CoTaskMemFree(pUnicode->Buffer);
230 pUnicode->Buffer = NULL;
231 }
232
233 pUnicode->Length = 0;
234 pUnicode->MaximumLength = 0;
235}
236
237
238/**
239 * Creates a KERB_INTERACTIVE_LOGON structure with the given parameters.
240 * Must be destroyed with kerberosLogonDestroy().
241 *
242 * @return HRESULT
243 * @param pLogon Structure to create.
244 * @param enmUsage Intended usage of the structure.
245 * @param pwszUser User name to use.
246 * @param pwszPassword Password to use.
247 * @param pwszDomain Domain to use. Optional and can be NULL.
248 */
249HRESULT VBoxCredProvCredential::kerberosLogonCreate(KERB_INTERACTIVE_LOGON *pLogon,
250 CREDENTIAL_PROVIDER_USAGE_SCENARIO enmUsage,
251 PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain)
252{
253 AssertPtrReturn(pLogon, E_INVALIDARG);
254 AssertPtrReturn(pwszUser, E_INVALIDARG);
255 AssertPtrReturn(pwszPassword, E_INVALIDARG);
256 /* pwszDomain is optional. */
257
258 HRESULT hr;
259
260 /* Do we have a domain name set? */
261 if ( pwszDomain
262 && RTUtf16Len(pwszDomain))
263 {
264 hr = RTUTF16ToUnicodeA(&pLogon->LogonDomainName, pwszDomain);
265 }
266 else /* No domain (FQDN) given, try local computer name. */
267 {
268 WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH + 1];
269 DWORD cch = ARRAYSIZE(wszComputerName);
270 if (GetComputerNameW(wszComputerName, &cch))
271 {
272 /* Is a domain name missing? Then use the name of the local computer. */
273 hr = RTUTF16ToUnicodeA(&pLogon->LogonDomainName, wszComputerName);
274
275 VBoxCredProvVerbose(0, "VBoxCredProvCredential::kerberosLogonInit: Local computer name=%ls\n",
276 wszComputerName);
277 }
278 else
279 hr = HRESULT_FROM_WIN32(GetLastError());
280 }
281
282 /* Fill in the username and password. */
283 if (SUCCEEDED(hr))
284 {
285 hr = RTUTF16ToUnicodeA(&pLogon->UserName, pwszUser);
286 if (SUCCEEDED(hr))
287 {
288 hr = RTUTF16ToUnicodeA(&pLogon->Password, pwszPassword);
289 if (SUCCEEDED(hr))
290 {
291 /* Set credential type according to current usage scenario. */
292 switch (enmUsage)
293 {
294 case CPUS_UNLOCK_WORKSTATION:
295 pLogon->MessageType = KerbWorkstationUnlockLogon;
296 break;
297
298 case CPUS_LOGON:
299 pLogon->MessageType = KerbInteractiveLogon;
300 break;
301
302 case CPUS_CREDUI:
303 pLogon->MessageType = (KERB_LOGON_SUBMIT_TYPE)0; /* No message type required here. */
304 break;
305
306 default:
307 VBoxCredProvVerbose(0, "VBoxCredProvCredential::kerberosLogonInit: Unknown usage scenario=%ld\n",
308 enmUsage);
309 hr = E_FAIL;
310 break;
311 }
312 }
313 }
314 }
315
316 return hr;
317}
318
319
320/**
321 * Destroys a formerly created KERB_INTERACTIVE_LOGON structure.
322 *
323 * @param pLogon Structure to destroy.
324 */
325void VBoxCredProvCredential::kerberosLogonDestroy(KERB_INTERACTIVE_LOGON *pLogon)
326{
327 if (!pLogon)
328 return;
329
330 UnicodeStringFree(&pLogon->UserName);
331 UnicodeStringFree(&pLogon->Password);
332 UnicodeStringFree(&pLogon->LogonDomainName);
333}
334
335
336HRESULT VBoxCredProvCredential::kerberosLogonSerialize(const KERB_INTERACTIVE_LOGON *pLogonIn,
337 PBYTE *ppPackage, DWORD *pcbPackage)
338{
339 AssertPtrReturn(pLogonIn, E_INVALIDARG);
340 AssertPtrReturn(ppPackage, E_INVALIDARG);
341 AssertPtrReturn(pcbPackage, E_INVALIDARG);
342
343 /*
344 * First, allocate enough space for the logon structure itself and separate
345 * string buffers right after it to store the actual user, password and domain
346 * credentials.
347 */
348 DWORD cbLogon = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON)
349 + pLogonIn->LogonDomainName.Length
350 + pLogonIn->UserName.Length
351 + pLogonIn->Password.Length;
352
353#ifdef DEBUG /* Do not reveal any hints to credential data in release mode. */
354 VBoxCredProvVerbose(1, "VBoxCredProvCredential::AllocateLogonPackage: Allocating %ld bytes (%zu bytes credentials)\n",
355 cbLogon, cbLogon - sizeof(KERB_INTERACTIVE_UNLOCK_LOGON));
356#endif
357
358 KERB_INTERACTIVE_UNLOCK_LOGON *pLogon = (KERB_INTERACTIVE_UNLOCK_LOGON*)CoTaskMemAlloc(cbLogon);
359 if (!pLogon)
360 return E_OUTOFMEMORY;
361
362 /* Make sure to zero everything first. */
363 RT_BZERO(pLogon, cbLogon);
364
365 /* Let our byte buffer point to the end of our allocated structure so that it can
366 * be used to store the credential data sequentially in a binary blob
367 * (without terminating \0). */
368 PBYTE pbBuffer = (PBYTE)pLogon + sizeof(KERB_INTERACTIVE_UNLOCK_LOGON);
369
370 /* The buffer of the packed destination string does not contain the actual
371 * string content but a relative offset starting at the given
372 * KERB_INTERACTIVE_UNLOCK_LOGON structure. */
373#define KERB_CRED_INIT_PACKED(StringDst, StringSrc, LogonOffset) \
374 StringDst.Length = StringSrc.Length; \
375 StringDst.MaximumLength = StringSrc.Length; \
376 if (StringDst.Length) \
377 { \
378 StringDst.Buffer = (PWSTR)pbBuffer; \
379 memcpy(StringDst.Buffer, StringSrc.Buffer, StringDst.Length); \
380 StringDst.Buffer = (PWSTR)(pbBuffer - (PBYTE)LogonOffset); \
381 pbBuffer += StringDst.Length; \
382 }
383
384 KERB_INTERACTIVE_LOGON *pLogonOut = &pLogon->Logon;
385
386 pLogonOut->MessageType = pLogonIn->MessageType;
387
388 KERB_CRED_INIT_PACKED(pLogonOut->LogonDomainName, pLogonIn->LogonDomainName, pLogon);
389 KERB_CRED_INIT_PACKED(pLogonOut->UserName , pLogonIn->UserName, pLogon);
390 KERB_CRED_INIT_PACKED(pLogonOut->Password , pLogonIn->Password, pLogon);
391
392 *ppPackage = (PBYTE)pLogon;
393 *pcbPackage = cbLogon;
394
395#undef KERB_CRED_INIT_PACKED
396
397 return S_OK;
398}
399
400
401/**
402 * Returns the current value of a specific credential provider field.
403 *
404 * @return Pointer (const) to the credential provider field requested, or NULL if not found / invalid.
405 * @param dwFieldID Field ID of the credential provider field to get.
406 */
407PCRTUTF16 VBoxCredProvCredential::getField(DWORD dwFieldID)
408{
409 if (dwFieldID >= VBOXCREDPROV_NUM_FIELDS)
410 return NULL;
411
412 /* Paranoia: Don't ever reveal passwords. */
413 if (dwFieldID == VBOXCREDPROV_FIELDID_PASSWORD)
414 return NULL;
415
416 return m_apwszFields[dwFieldID];
417}
418
419
420/**
421 * Sets a credential provider field by first zero'ing out its current content in a (hopefully) secure manner,
422 * then applying either the field's default or a new value.
423 *
424 * @return HRESULT
425 * @param dwFieldID Field ID of the credential provider field to reset.
426 * @param pcwszString String to set for the given field. Specify NULL for setting the provider's default value.
427 * @param fNotifyUI Whether to notify the LogonUI about the reset.
428 */
429HRESULT VBoxCredProvCredential::setField(DWORD dwFieldID, const PRTUTF16 pcwszString, bool fNotifyUI)
430{
431 if (dwFieldID >= VBOXCREDPROV_NUM_FIELDS)
432 return E_INVALIDARG;
433
434 HRESULT hr = S_OK;
435
436 PRTUTF16 pwszField = m_apwszFields[dwFieldID];
437 if (pwszField)
438 {
439 /* First, wipe the existing value thoroughly. */
440 RTMemWipeThoroughly(pwszField, (RTUtf16Len(pwszField) + 1) * sizeof(RTUTF16), 3 /* Passes */);
441
442 /* Second, free the string. */
443 RTUtf16Free(pwszField);
444 }
445
446 /* Either fill in the default value or the one specified in pcwszString. */
447 pwszField = RTUtf16Dup(pcwszString ? pcwszString : s_VBoxCredProvDefaultFields[dwFieldID].desc.pszLabel);
448 if (pwszField)
449 {
450 m_apwszFields[dwFieldID] = pwszField; /* Update the pointer. */
451
452 if ( m_pEvents
453 && fNotifyUI) /* Let the logon UI know if wanted. */
454 {
455 hr = m_pEvents->SetFieldString(this, dwFieldID, pwszField);
456 }
457 }
458 else
459 hr = E_OUTOFMEMORY;
460
461 VBoxCredProvVerbose(0, "VBoxCredProvCredential::setField: Setting field dwFieldID=%ld to '%ls', fNotifyUI=%RTbool, hr=0x%08x\n",
462 dwFieldID,
463#ifdef DEBUG
464 pwszField,
465#else
466 /* Don't show any passwords in release mode. */
467 dwFieldID == VBOXCREDPROV_FIELDID_PASSWORD ? L"XXX" : pwszField,
468#endif
469 fNotifyUI, hr);
470 return hr;
471}
472
473/**
474 * Resets (wipes) stored credentials.
475 *
476 * @return HRESULT
477 */
478HRESULT VBoxCredProvCredential::Reset(void)
479{
480 VBoxCredProvVerbose(0, "VBoxCredProvCredential::Reset: Wiping credentials user=%ls, pw=%ls, domain=%ls\n",
481 m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME] ? m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME] : L"<NULL>",
482#ifdef DEBUG
483 m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD] ? m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD] : L"<NULL>",
484#else
485 L"XXX" /* Don't show any passwords in release mode. */,
486#endif
487 m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME] ? m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME] : L"<NULL>");
488
489 /* Note: Do not reset the user name and domain name here,
490 * as they could still being queried (again) by LogonUI on failed login attempts. */
491 HRESULT hr = setField(VBOXCREDPROV_FIELDID_PASSWORD, NULL /* Use default value */, true /* fNotifyUI */);
492
493 m_fIsSelected = false;
494
495 VBoxCredProvVerbose(0, "VBoxCredProvCredential::Reset\n");
496 return hr;
497}
498
499
500/**
501 * Checks and retrieves credentials provided by the host + does account lookup on eventually
502 * renamed user accounts.
503 *
504 * @return IPRT status code.
505 */
506int VBoxCredProvCredential::RetrieveCredentials(void)
507{
508 PRTUTF16 pwszUser = NULL;
509 PRTUTF16 pwszPassword = NULL;
510 PRTUTF16 pwszDomain = NULL;
511
512 int rc = VbglR3CredentialsQueryAvailability();
513 if (RT_SUCCESS(rc))
514 {
515 /*
516 * Set status to "terminating" to let the host know this module now
517 * tries to receive and use passed credentials so that credentials from
518 * the host won't be sent twice.
519 */
520 VBoxCredProvReportStatus(VBoxGuestFacilityStatus_Terminating);
521
522 rc = VbglR3CredentialsRetrieveUtf16(&pwszUser, &pwszPassword, &pwszDomain);
523
524 VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Retrieved credentials with rc=%Rrc\n", rc);
525 }
526
527 if (RT_SUCCESS(rc))
528 {
529 VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Received credentials for user '%ls'\n", pwszUser);
530
531 /*
532 * In case we got a "display name" (e.g. "John Doe")
533 * instead of the real user name (e.g. "jdoe") we have
534 * to translate the data first ...
535 */
536 PWSTR pwszExtractedName = NULL;
537 if ( TranslateAccountName(pwszUser, &pwszExtractedName)
538 && pwszExtractedName)
539 {
540 VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Translated account name '%ls' -> '%ls'\n",
541 pwszUser, pwszExtractedName);
542
543 RTMemWipeThoroughly(pwszUser, (RTUtf16Len(pwszUser) + 1) * sizeof(RTUTF16), 3 /* Passes */);
544 RTUtf16Free(pwszUser);
545
546 pwszUser = RTUtf16Dup(pwszExtractedName);
547
548 CoTaskMemFree(pwszExtractedName);
549 pwszExtractedName = NULL;
550 }
551 else
552 {
553 /*
554 * Okay, no display name, but maybe it's a
555 * principal name from which we have to extract the domain from?
556 * (jdoe@my-domain.sub.net.com -> jdoe in domain my-domain.sub.net.com.)
557 */
558 PWSTR pwszExtractedDomain = NULL;
559 if (ExtractAccountData(pwszUser, &pwszExtractedName, &pwszExtractedDomain))
560 {
561 /* Update user name. */
562 if (pwszExtractedName)
563 {
564 if (pwszUser)
565 {
566 RTMemWipeThoroughly(pwszUser, (RTUtf16Len(pwszUser) + 1) * sizeof(RTUTF16), 3 /* Passes */);
567 RTUtf16Free(pwszUser);
568 }
569
570 pwszUser = RTUtf16Dup(pwszExtractedName);
571
572 CoTaskMemFree(pwszExtractedName);
573 pwszExtractedName = NULL;
574 }
575
576 /* Update domain. */
577 if (pwszExtractedDomain)
578 {
579 if (pwszDomain)
580 {
581 RTMemWipeThoroughly(pwszDomain, (RTUtf16Len(pwszDomain) + 1) * sizeof(RTUTF16), 3 /* Passes */);
582 RTUtf16Free(pwszDomain);
583 }
584
585 pwszDomain = RTUtf16Dup(pwszExtractedDomain);
586
587 CoTaskMemFree(pwszExtractedDomain);
588 pwszExtractedDomain = NULL;
589 }
590
591 VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Extracted account name '%ls' + domain '%ls'\n",
592 pwszUser ? pwszUser : L"<NULL>", pwszDomain ? pwszDomain : L"<NULL>");
593 }
594 }
595
596 m_fHaveCreds = true;
597 }
598
599 if (m_fHaveCreds)
600 {
601 VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Setting fields\n");
602
603 setField(VBOXCREDPROV_FIELDID_USERNAME, pwszUser, true /* fNotifyUI */);
604 setField(VBOXCREDPROV_FIELDID_PASSWORD, pwszPassword, true /* fNotifyUI */);
605 setField(VBOXCREDPROV_FIELDID_DOMAINNAME, pwszDomain, true /* fNotifyUI */);
606 }
607
608 VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Wiping ...\n");
609
610 VbglR3CredentialsDestroyUtf16(pwszUser, pwszPassword, pwszDomain, 3 /* cPasses */);
611
612 VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Returned rc=%Rrc\n", rc);
613 return rc;
614}
615
616
617/**
618 * Initializes this credential with the current credential provider
619 * usage scenario.
620 */
621HRESULT VBoxCredProvCredential::Initialize(CREDENTIAL_PROVIDER_USAGE_SCENARIO enmUsageScenario)
622{
623 VBoxCredProvVerbose(0, "VBoxCredProvCredential::Initialize: enmUsageScenario=%ld\n", enmUsageScenario);
624 m_enmUsageScenario = enmUsageScenario;
625 return S_OK;
626}
627
628
629/**
630 * Called by LogonUI when it needs this credential's advice.
631 *
632 * At the moment we only grab the credential provider events so that we can
633 * trigger a re-enumeration of the credentials later.
634 */
635HRESULT VBoxCredProvCredential::Advise(ICredentialProviderCredentialEvents *pEvents)
636{
637 VBoxCredProvVerbose(0, "VBoxCredProvCredential::Advise: pEvents=0x%p\n", pEvents);
638
639 if (m_pEvents)
640 {
641 m_pEvents->Release();
642 m_pEvents = NULL;
643 }
644
645 return pEvents->QueryInterface(IID_PPV_ARGS(&m_pEvents));
646}
647
648
649/**
650 * Called by LogonUI when it's finished with handling this credential.
651 *
652 * We only need to release the credential provider events, if any.
653 */
654HRESULT VBoxCredProvCredential::UnAdvise(void)
655{
656 VBoxCredProvVerbose(0, "VBoxCredProvCredential::UnAdvise\n");
657
658 if (m_pEvents)
659 {
660 m_pEvents->Release();
661 m_pEvents = NULL;
662 }
663
664 return S_OK;
665}
666
667
668/**
669 * Called by LogonUI when a user profile (tile) has been selected.
670 *
671 * As we don't want Winlogon to try logging in immediately we set pfAutoLogon
672 * to FALSE (if set).
673 */
674HRESULT VBoxCredProvCredential::SetSelected(PBOOL pfAutoLogon)
675{
676 VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetSelected\n");
677
678 /*
679 * Don't do auto logon here because it would retry too often with
680 * every credential field (user name, password, domain, ...) which makes
681 * winlogon wait before new login attempts can be made.
682 */
683 if (pfAutoLogon)
684 *pfAutoLogon = FALSE;
685
686 m_fIsSelected = true;
687
688 return S_OK;
689}
690
691
692/**
693 * Called by LogonUI when a user profile (tile) has been unselected again.
694 */
695HRESULT VBoxCredProvCredential::SetDeselected(void)
696{
697 VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetDeselected\n");
698
699 Reset();
700
701 return S_OK;
702}
703
704
705/**
706 * Called by LogonUI to retrieve the (interactive) state of a UI field.
707 */
708HRESULT VBoxCredProvCredential::GetFieldState(DWORD dwFieldID, CREDENTIAL_PROVIDER_FIELD_STATE *pFieldState,
709 CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *pFieldstateInteractive)
710{
711 VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetFieldState: dwFieldID=%ld\n", dwFieldID);
712
713 HRESULT hr = S_OK;
714
715 if (dwFieldID < VBOXCREDPROV_NUM_FIELDS)
716 {
717 if (pFieldState)
718 *pFieldState = s_VBoxCredProvDefaultFields[dwFieldID].state;
719
720 if (pFieldstateInteractive)
721 *pFieldstateInteractive = s_VBoxCredProvDefaultFields[dwFieldID].stateInteractive;
722 }
723 else
724 hr = E_INVALIDARG;
725
726 return hr;
727}
728
729
730/**
731 * Searches the account name based on a display (real) name (e.g. "John Doe" -> "jdoe").
732 *
733 * @return TRUE if translation of the account name was successful, FALSE if not.
734 * @param pwszDisplayName Display name to extract account name from.
735 * @param ppwszAccoutName Where to store the extracted account name on success.
736 * Needs to be free'd with CoTaskMemFree().
737 */
738BOOL VBoxCredProvCredential::TranslateAccountName(PWSTR pwszDisplayName, PWSTR *ppwszAccoutName)
739{
740 AssertPtrReturn(pwszDisplayName, FALSE);
741 VBoxCredProvVerbose(0, "VBoxCredProvCredential::TranslateAccountName: Getting account name for \"%ls\" ...\n",
742 pwszDisplayName);
743
744 /** @todo Do we need ADS support (e.g. TranslateNameW) here? */
745 BOOL fFound = FALSE; /* Did we find the desired user? */
746 NET_API_STATUS rcStatus;
747 DWORD dwLevel = 2; /* Detailed information about user accounts. */
748 DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH;
749 DWORD dwEntriesRead = 0;
750 DWORD dwTotalEntries = 0;
751 DWORD dwResumeHandle = 0;
752 LPUSER_INFO_2 pBuf = NULL;
753 LPUSER_INFO_2 pCurBuf = NULL;
754 do
755 {
756 rcStatus = NetUserEnum(NULL, /* Server name, NULL for localhost. */
757 dwLevel,
758 FILTER_NORMAL_ACCOUNT,
759 (LPBYTE*)&pBuf,
760 dwPrefMaxLen,
761 &dwEntriesRead,
762 &dwTotalEntries,
763 &dwResumeHandle);
764 if ( rcStatus == NERR_Success
765 || rcStatus == ERROR_MORE_DATA)
766 {
767 if ((pCurBuf = pBuf) != NULL)
768 {
769 for (DWORD i = 0; i < dwEntriesRead; i++)
770 {
771 /*
772 * Search for the "display name" - that might be
773 * "John Doe" or something similar the user recognizes easier
774 * and may not the same as the "account" name (e.g. "jdoe").
775 */
776 if ( pCurBuf
777 && pCurBuf->usri2_full_name
778 && StrCmpI(pwszDisplayName, pCurBuf->usri2_full_name) == 0)
779 {
780 /*
781 * Copy the real user name (e.g. "jdoe") to our
782 * output buffer.
783 */
784 LPWSTR pwszTemp;
785 HRESULT hr = SHStrDupW(pCurBuf->usri2_name, &pwszTemp);
786 if (hr == S_OK)
787 {
788 *ppwszAccoutName = pwszTemp;
789 fFound = TRUE;
790 }
791 else
792 VBoxCredProvVerbose(0, "VBoxCredProvCredential::TranslateAccountName: Error copying data, hr=%08x\n", hr);
793 break;
794 }
795 pCurBuf++;
796 }
797 }
798 if (pBuf != NULL)
799 {
800 NetApiBufferFree(pBuf);
801 pBuf = NULL;
802 }
803 }
804 } while (rcStatus == ERROR_MORE_DATA && !fFound);
805
806 if (pBuf != NULL)
807 {
808 NetApiBufferFree(pBuf);
809 pBuf = NULL;
810 }
811
812 VBoxCredProvVerbose(0, "VBoxCredProvCredential::TranslateAccountName returned rcStatus=%ld, fFound=%RTbool\n",
813 rcStatus, fFound);
814 return fFound;
815
816#if 0
817 DWORD dwErr = NO_ERROR;
818 ULONG cbLen = 0;
819 if ( TranslateNameW(pwszName, NameUnknown, NameUserPrincipal, NULL, &cbLen)
820 && cbLen > 0)
821 {
822 VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetAccountName: Translated ADS name has %u characters\n", cbLen));
823
824 ppwszAccoutName = (PWSTR)RTMemAlloc(cbLen * sizeof(WCHAR));
825 AssertPtrReturn(pwszName, FALSE);
826 if (TranslateNameW(pwszName, NameUnknown, NameUserPrincipal, ppwszAccoutName, &cbLen))
827 {
828 VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetAccountName: Real ADS account name of '%ls' is '%ls'\n",
829 pwszName, ppwszAccoutName));
830 }
831 else
832 {
833 RTMemFree(ppwszAccoutName);
834 dwErr = GetLastError();
835 }
836 }
837 else
838 dwErr = GetLastError();
839 /* The above method for looking up in ADS failed, try another one. */
840 if (dwErr != NO_ERROR)
841 {
842 dwErr = NO_ERROR;
843
844 }
845#endif
846}
847
848
849/**
850 * Extracts the actual account name & domain from a (raw) account data string.
851 *
852 * This might be a principal or FQDN string.
853 *
854 * @return success indicator. Will fail if input not in a user\@domain format.
855 * @param pwszAccountData Raw account data string to extract data from.
856 * @param ppwszAccountName Where to store the extracted account name on
857 * success. Needs to be freed with CoTaskMemFree().
858 * @param ppwszDomain Where to store the extracted domain name on
859 * success. Needs to be freed with CoTaskMemFree().
860 */
861/*static*/ bool VBoxCredProvCredential::ExtractAccountData(PWSTR pwszAccountData, PWSTR *ppwszAccountName, PWSTR *ppwszDomain)
862{
863 AssertPtrReturn(pwszAccountData, FALSE);
864 VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccoutData: Getting account name for \"%ls\" ...\n",
865 pwszAccountData);
866
867/** @todo r=bird: The original code seemed a little confused about whether
868 * the domain stuff was optional or not, as it declared pwszDomain
869 * very early and freed it in the error path. Not entirely sure what
870 * to make of that... */
871
872 /* Try to figure out whether this is a principal name (user@domain). */
873 LPWSTR const pwszAt = StrChrW(pwszAccountData, L'@');
874 if (pwszAt && pwszAt != pwszAccountData)
875 {
876 if (pwszAt[1])
877 {
878 size_t cwcUser = (size_t)(pwszAt - pwszAccountData) + 1;
879 LPWSTR pwszName = (LPWSTR)CoTaskMemAlloc(cwcUser * sizeof(WCHAR));
880 if (pwszName)
881 {
882 int rc = RTUtf16CopyEx(pwszName, cwcUser, pwszAccountData, cwcUser - 1);
883 if (RT_SUCCESS(rc))
884 {
885 LPWSTR pwszDomain = NULL;
886 HRESULT hr = SHStrDupW(&pwszAt[1], &pwszDomain);
887 if (SUCCEEDED(hr))
888 {
889 *ppwszAccountName = pwszName;
890 *ppwszDomain = pwszDomain;
891 return true;
892 }
893
894 VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: Error copying domain data, hr=%08x\n", hr);
895 }
896 else
897 VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: Error copying account data, rc=%Rrc\n", rc);
898 CoTaskMemFree(pwszName);
899 }
900 else
901 VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: allocation failure.\n");
902 }
903 else
904 VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: No domain name found!\n");
905 }
906 else
907 VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: No valid principal account name found!\n");
908
909 return false;
910}
911
912
913/**
914 * Returns the current value of a specified LogonUI field.
915 *
916 * @return IPRT status code.
917 * @param dwFieldID Field ID to get value for.
918 * @param ppwszString Pointer that receives the actual value of the specified field.
919 */
920HRESULT VBoxCredProvCredential::GetStringValue(DWORD dwFieldID, PWSTR *ppwszString)
921{
922 HRESULT hr;
923
924 PWSTR pwszString = NULL;
925
926 if (dwFieldID < VBOXCREDPROV_NUM_FIELDS)
927 {
928 switch (dwFieldID)
929 {
930 case VBOXCREDPROV_FIELDID_SUBMIT_BUTTON:
931 {
932 /* Fill in standard value to make Winlogon happy. */
933 hr = SHStrDupW(L"Submit", &pwszString);
934 break;
935 }
936
937 default:
938 {
939 if ( m_apwszFields[dwFieldID]
940 && m_apwszFields[dwFieldID][0])
941 hr = SHStrDupW(m_apwszFields[dwFieldID], &pwszString);
942 else /* Fill in an empty value. */
943 hr = SHStrDupW(L"", &pwszString);
944 break;
945 }
946 }
947 }
948 else
949 hr = E_INVALIDARG;
950
951 VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetStringValue: m_fIsSelected=%RTbool, dwFieldID=%ld, pwszString=%ls, hr=%Rhrc\n",
952 m_fIsSelected, dwFieldID,
953#ifdef DEBUG
954 pwszString ? pwszString : L"<NULL>",
955#else
956 /* Don't show any passwords in release mode. */
957 dwFieldID == VBOXCREDPROV_FIELDID_PASSWORD ? L"XXX" : (pwszString ? pwszString : L"<NULL>"),
958#endif
959 hr);
960
961 if (ppwszString)
962 *ppwszString = pwszString;
963 else if (pwszString)
964 CoTaskMemFree(pwszString);
965
966 return hr;
967}
968
969
970/**
971 * Returns back the field ID of which the submit button should be put next to.
972 *
973 * We always want to be the password field put next to the submit button
974 * currently.
975 *
976 * @return HRESULT
977 * @param dwFieldID Field ID of the submit button.
978 * @param pdwAdjacentTo Field ID where to put the submit button next to.
979 */
980HRESULT VBoxCredProvCredential::GetSubmitButtonValue(DWORD dwFieldID, DWORD *pdwAdjacentTo)
981{
982 VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSubmitButtonValue: dwFieldID=%ld\n",
983 dwFieldID);
984
985 HRESULT hr = S_OK;
986
987 /* Validate parameters. */
988 if ( dwFieldID == VBOXCREDPROV_FIELDID_SUBMIT_BUTTON
989 && pdwAdjacentTo)
990 {
991 /* pdwAdjacentTo is a pointer to the fieldID you want the submit button to appear next to. */
992 *pdwAdjacentTo = VBOXCREDPROV_FIELDID_PASSWORD;
993 VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSubmitButtonValue: dwFieldID=%ld, *pdwAdjacentTo=%ld\n",
994 dwFieldID, *pdwAdjacentTo);
995 }
996 else
997 hr = E_INVALIDARG;
998
999 return hr;
1000}
1001
1002
1003/**
1004 * Sets the value of a specified field. Currently not used.
1005 *
1006 * @return HRESULT
1007 * @param dwFieldID Field to set value for.
1008 * @param pwszValue Actual value to set.
1009 */
1010HRESULT VBoxCredProvCredential::SetStringValue(DWORD dwFieldID, PCWSTR pwszValue)
1011{
1012 RT_NOREF(dwFieldID, pwszValue);
1013
1014 /* Do more things here later. */
1015 HRESULT hr = S_OK;
1016
1017 VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetStringValue: dwFieldID=%ld, pcwzString=%ls, hr=%Rhrc\n",
1018 dwFieldID,
1019#ifdef DEBUG
1020 pwszValue ? pwszValue : L"<NULL>",
1021#else /* Never show any (sensitive) data in release mode! */
1022 L"XXX",
1023#endif
1024 hr);
1025
1026 return hr;
1027}
1028
1029
1030HRESULT VBoxCredProvCredential::GetBitmapValue(DWORD dwFieldID, HBITMAP *phBitmap)
1031{
1032 NOREF(dwFieldID);
1033 NOREF(phBitmap);
1034
1035 /* We don't do own bitmaps. */
1036 return E_NOTIMPL;
1037}
1038
1039
1040HRESULT VBoxCredProvCredential::GetCheckboxValue(DWORD dwFieldID, BOOL *pfChecked, PWSTR *ppwszLabel)
1041{
1042 NOREF(dwFieldID);
1043 NOREF(pfChecked);
1044 NOREF(ppwszLabel);
1045 return E_NOTIMPL;
1046}
1047
1048
1049HRESULT VBoxCredProvCredential::GetComboBoxValueCount(DWORD dwFieldID, DWORD *pcItems, DWORD *pdwSelectedItem)
1050{
1051 NOREF(dwFieldID);
1052 NOREF(pcItems);
1053 NOREF(pdwSelectedItem);
1054 return E_NOTIMPL;
1055}
1056
1057
1058HRESULT VBoxCredProvCredential::GetComboBoxValueAt(DWORD dwFieldID, DWORD dwItem, PWSTR *ppwszItem)
1059{
1060 NOREF(dwFieldID);
1061 NOREF(dwItem);
1062 NOREF(ppwszItem);
1063 return E_NOTIMPL;
1064}
1065
1066
1067HRESULT VBoxCredProvCredential::SetCheckboxValue(DWORD dwFieldID, BOOL fChecked)
1068{
1069 NOREF(dwFieldID);
1070 NOREF(fChecked);
1071 return E_NOTIMPL;
1072}
1073
1074
1075HRESULT VBoxCredProvCredential::SetComboBoxSelectedValue(DWORD dwFieldId, DWORD dwSelectedItem)
1076{
1077 NOREF(dwFieldId);
1078 NOREF(dwSelectedItem);
1079 return E_NOTIMPL;
1080}
1081
1082
1083HRESULT VBoxCredProvCredential::CommandLinkClicked(DWORD dwFieldID)
1084{
1085 NOREF(dwFieldID);
1086 return E_NOTIMPL;
1087}
1088
1089
1090/**
1091 * Does the actual authentication stuff to attempt a login.
1092 *
1093 * @return HRESULT
1094 * @param pcpGetSerializationResponse Credential serialization response.
1095 * @param pcpCredentialSerialization Details about the current credential.
1096 * @param ppwszOptionalStatusText Text to set. Optional.
1097 * @param pcpsiOptionalStatusIcon Status icon to set. Optional.
1098 */
1099HRESULT VBoxCredProvCredential::GetSerialization(CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *pcpGetSerializationResponse,
1100 CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpCredentialSerialization,
1101 PWSTR *ppwszOptionalStatusText,
1102 CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon)
1103{
1104 NOREF(ppwszOptionalStatusText);
1105 NOREF(pcpsiOptionalStatusIcon);
1106
1107 KERB_INTERACTIVE_UNLOCK_LOGON KerberosUnlockLogon;
1108 RT_BZERO(&KerberosUnlockLogon, sizeof(KerberosUnlockLogon));
1109
1110 /* Save a pointer to the interactive logon struct. */
1111 KERB_INTERACTIVE_LOGON *pLogon = &KerberosUnlockLogon.Logon;
1112
1113#ifdef DEBUG /* Note: NEVER print this in release mode! */
1114 VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSerialization: Username=%ls, Password=%ls, Domain=%ls\n",
1115 m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME],
1116 m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD],
1117 m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME]);
1118#endif
1119
1120 HRESULT hr = kerberosLogonCreate(pLogon,
1121 m_enmUsageScenario,
1122 m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME],
1123 m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD],
1124 m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME]);
1125 if (SUCCEEDED(hr))
1126 {
1127 hr = kerberosLogonSerialize(pLogon,
1128 &pcpCredentialSerialization->rgbSerialization,
1129 &pcpCredentialSerialization->cbSerialization);
1130 if (SUCCEEDED(hr))
1131 {
1132 HANDLE hLSA;
1133 NTSTATUS s = LsaConnectUntrusted(&hLSA);
1134 hr = HRESULT_FROM_NT(s);
1135
1136 if (SUCCEEDED(hr))
1137 {
1138#if 0 /* eeek. leaving this as an example of how not to handle a string constant. */
1139 size_t cchKerberosName;
1140 hr = StringCchLengthA(NEGOSSP_NAME_A, USHORT_MAX, &cchKerberosName);
1141 if (SUCCEEDED(hr))
1142 {
1143 USHORT usLength;
1144 hr = SizeTToUShort(cchKerberosName, &usLength);
1145 if (SUCCEEDED(hr))
1146#endif
1147 {
1148 LSA_STRING lsaszKerberosName;
1149 lsaszKerberosName.Buffer = (PCHAR)NEGOSSP_NAME_A;
1150 lsaszKerberosName.Length = sizeof(NEGOSSP_NAME_A) - 1;
1151 lsaszKerberosName.MaximumLength = sizeof(NEGOSSP_NAME_A);
1152
1153 ULONG ulAuthPackage = 0;
1154
1155 s = LsaLookupAuthenticationPackage(hLSA, &lsaszKerberosName, &ulAuthPackage);
1156 hr = HRESULT_FROM_NT(s);
1157
1158 if (SUCCEEDED(hr))
1159 {
1160 pcpCredentialSerialization->ulAuthenticationPackage = ulAuthPackage;
1161 pcpCredentialSerialization->clsidCredentialProvider = CLSID_VBoxCredProvider;
1162
1163 /* We're done -- let the logon UI know. */
1164 *pcpGetSerializationResponse = CPGSR_RETURN_CREDENTIAL_FINISHED;
1165
1166 VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: Finished for user '%ls' (domain '%ls')\n",
1167 m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME],
1168 m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME]);
1169 }
1170 else
1171 VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: LsaLookupAuthenticationPackage failed with ntStatus=%ld\n", s);
1172 }
1173#if 0
1174 }
1175#endif
1176 LsaDeregisterLogonProcess(hLSA);
1177 }
1178 else
1179 VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: LsaConnectUntrusted failed with ntStatus=%ld\n", s);
1180 }
1181 else
1182 VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: kerberosLogonSerialize failed with hr=0x%08x\n", hr);
1183
1184 kerberosLogonDestroy(pLogon);
1185 pLogon = NULL;
1186 }
1187 else
1188 VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: kerberosLogonCreate failed with hr=0x%08x\n", hr);
1189
1190 VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization returned hr=0x%08x\n", hr);
1191 return hr;
1192}
1193
1194
1195/**
1196 * Called by LogonUI after a logon attempt was made -- here we could set an additional status
1197 * text and/or icon.
1198 *
1199 * Currently not used.
1200 *
1201 * @return HRESULT
1202 * @param ntStatus NT status of logon attempt reported by Winlogon.
1203 * @param ntSubStatus NT substatus of logon attempt reported by Winlogon.
1204 * @param ppwszOptionalStatusText Pointer that receives the optional status text.
1205 * @param pcpsiOptionalStatusIcon Pointer that receives the optional status icon.
1206 */
1207HRESULT VBoxCredProvCredential::ReportResult(NTSTATUS ntStatus,
1208 NTSTATUS ntSubStatus,
1209 PWSTR *ppwszOptionalStatusText,
1210 CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon)
1211{
1212 RT_NOREF(ntStatus, ntSubStatus, ppwszOptionalStatusText, pcpsiOptionalStatusIcon);
1213 VBoxCredProvVerbose(0, "VBoxCredProvCredential::ReportResult: ntStatus=%ld, ntSubStatus=%ld\n",
1214 ntStatus, ntSubStatus);
1215 return E_NOTIMPL;
1216}
1217
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette