mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1696 lines
60 KiB
1696 lines
60 KiB
// AuthCtl.cpp : Implementation of the CCertAuthCtrl OLE control class.
|
|
|
|
// THIS FEATURE USED TO BE SOMETHING DIFFERENT, now it has meaning of:
|
|
// Doing CertificateRequest and Authority Mapping/management.
|
|
|
|
#include "stdafx.h"
|
|
//#include "Util.h"
|
|
#include "certmap.h"
|
|
#include "AuthCtl.h"
|
|
#include "AuthPpg.h"
|
|
|
|
/*
|
|
|
|
#include "Reg.h"
|
|
#include "nLocEnrl.h"
|
|
|
|
#ifdef USE_COMPROP_WIZ97
|
|
# include "wizard.h" // implement Wizard97 look and feel
|
|
#endif
|
|
|
|
#include <windows.h>
|
|
#include <wincrypt.h>
|
|
//#include <unicode.h>
|
|
//#include <base64.h>
|
|
//#include <pvk.h>
|
|
|
|
//need the CLSID and IID for xEnroll
|
|
|
|
#include <ole2.h>
|
|
#include "xenroll.h"
|
|
#include <certrpc.h>
|
|
#include <winsock.h>
|
|
|
|
#include <wintrust.h>
|
|
|
|
#include "wrapmb.h"
|
|
|
|
|
|
#include "NKWelcom.h" // for class CNKWelcome
|
|
#include "NKMuxPg.h" // for class CNKMuxPg -- for new certs
|
|
#include "NKMuxPg2.h" // for class CNKMuxPg2 -- for OOB finish ops
|
|
#include "NKMuxPg3.h" // for class CNKMuxPg3 -- for Mod Existing ops
|
|
|
|
|
|
|
|
#define _CRYPT32_
|
|
// persistence and mapping includes
|
|
extern "C"
|
|
{
|
|
#include <wincrypt.h>
|
|
#include <sslsp.h>
|
|
}
|
|
#include "Iismap.hxx"
|
|
#include "Iiscmr.hxx"
|
|
|
|
#include "CrackCrt.h"
|
|
#include "ListRow.h"
|
|
#include "ChkLstCt.h"
|
|
#include "IssueDlg.h"
|
|
|
|
|
|
|
|
// include header files for the dialog pages
|
|
//
|
|
#include "KeyObjs.h"
|
|
|
|
//#include "WizSheet.h"
|
|
#include "NKChseCA.h"
|
|
#include "NKDN.h"
|
|
#include "NKDN1.h"
|
|
#include "NKDN2.h"
|
|
#include "NKFlInfo.h"
|
|
#include "NKKyInfo.h"
|
|
#include "NKUsrInf.h"
|
|
#include "CertSel.h"
|
|
#include "CompPage.h"
|
|
#include "WizError.h"
|
|
|
|
#include "Creating.h"
|
|
|
|
#include "CTLWelc.h"
|
|
#include "CTLMuxPg.h"
|
|
|
|
|
|
#include "NKChseCA.h"
|
|
|
|
#include "NKWelcom.h" // for class CNKWelcome
|
|
#include "NKMuxPg.h" // for class CNKMuxPg
|
|
#include "KeyRImpt.h" // for class CKeyImpt -- the 1st KeyRing Import page
|
|
#include "KeyRFile.h" // for the second KeyRing Import page that asks
|
|
// the user to confirm they want to do the import
|
|
|
|
#include "FinCImpt.h" // for the first Cert-Finish an offline-request
|
|
// Wizard page -- class CFinCertImport
|
|
#include "FinCFin.h" // for the second Cert-Finish an offline-request
|
|
// Wizard page -- class CFinCertImport
|
|
|
|
|
|
#include "NKMuxPg3.h"
|
|
#include "DelCert.h"
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
|
|
IMPLEMENT_DYNCREATE(CCertAuthCtrl, COleControl)
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Message map
|
|
|
|
BEGIN_MESSAGE_MAP(CCertAuthCtrl, COleControl)
|
|
//{{AFX_MSG_MAP(CCertAuthCtrl)
|
|
// NOTE - ClassWizard will add and remove message map entries
|
|
// DO NOT EDIT what you see in these blocks of generated code !
|
|
//}}AFX_MSG_MAP
|
|
ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Dispatch map
|
|
|
|
BEGIN_DISPATCH_MAP(CCertAuthCtrl, COleControl)
|
|
//{{AFX_DISPATCH_MAP(CCertAuthCtrl)
|
|
DISP_FUNCTION(CCertAuthCtrl, "SetMachineName", SetMachineName, VT_EMPTY, VTS_BSTR)
|
|
DISP_FUNCTION(CCertAuthCtrl, "SetServerInstance", SetServerInstance, VT_EMPTY, VTS_BSTR)
|
|
DISP_STOCKPROP_FONT()
|
|
DISP_STOCKPROP_BORDERSTYLE()
|
|
DISP_STOCKPROP_ENABLED()
|
|
DISP_STOCKPROP_CAPTION()
|
|
DISP_FUNCTION_ID(CCertAuthCtrl, "DoClick", DISPID_DOCLICK, DoClick, VT_EMPTY, VTS_I4)
|
|
//}}AFX_DISPATCH_MAP
|
|
DISP_FUNCTION_ID(CCertAuthCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
|
|
END_DISPATCH_MAP()
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Event map
|
|
|
|
BEGIN_EVENT_MAP(CCertAuthCtrl, COleControl)
|
|
//{{AFX_EVENT_MAP(CCertAuthCtrl)
|
|
EVENT_STOCK_CLICK()
|
|
//}}AFX_EVENT_MAP
|
|
END_EVENT_MAP()
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Property pages
|
|
|
|
// TODO: Add more property pages as needed. Remember to increase the count!
|
|
BEGIN_PROPPAGEIDS(CCertAuthCtrl, 2)
|
|
PROPPAGEID(CCertAuthPropPage::guid)
|
|
PROPPAGEID(CLSID_CFontPropPage)
|
|
END_PROPPAGEIDS(CCertAuthCtrl)
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Initialize class factory and guid
|
|
|
|
IMPLEMENT_OLECREATE_EX(CCertAuthCtrl, "CERTMAP.CertmapCtrl.2",
|
|
0x996ff6f, 0xb6a1, 0x11d0, 0x92, 0x92, 0, 0xc0, 0x4f, 0xb6, 0x67, 0x8b)
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Type library ID and version
|
|
|
|
IMPLEMENT_OLETYPELIB(CCertAuthCtrl, _tlid, _wVerMajor, _wVerMinor)
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Interface IDs
|
|
|
|
const IID BASED_CODE IID_DCertAuth =
|
|
{ 0x996ff6d, 0xb6a1, 0x11d0, { 0x92, 0x92, 0, 0xc0, 0x4f, 0xb6, 0x67, 0x8b } };
|
|
const IID BASED_CODE IID_DCertAuthEvents =
|
|
{ 0x996ff6e, 0xb6a1, 0x11d0, { 0x92, 0x92, 0, 0xc0, 0x4f, 0xb6, 0x67, 0x8b } };
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Control type information
|
|
|
|
static const DWORD BASED_CODE _dwCertAuthOleMisc =
|
|
OLEMISC_ACTIVATEWHENVISIBLE |
|
|
OLEMISC_SETCLIENTSITEFIRST |
|
|
OLEMISC_INSIDEOUT |
|
|
OLEMISC_CANTLINKINSIDE |
|
|
OLEMISC_RECOMPOSEONRESIZE;
|
|
|
|
IMPLEMENT_OLECTLTYPE(CCertAuthCtrl, IDS_CERTAUTH, _dwCertAuthOleMisc)
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl::CCertAuthCtrlFactory::UpdateRegistry -
|
|
// Adds or removes system registry entries for CCertAuthCtrl
|
|
|
|
BOOL CCertAuthCtrl::CCertAuthCtrlFactory::UpdateRegistry(BOOL bRegister)
|
|
{
|
|
// TODO: Verify that your control follows apartment-model threading rules.
|
|
// Refer to MFC TechNote 64 for more information.
|
|
// If your control does not conform to the apartment-model rules, then
|
|
// you must modify the code below, changing the 6th parameter from
|
|
// afxRegApartmentThreading to 0.
|
|
|
|
|
|
if (bRegister)
|
|
return AfxOleRegisterControlClass(
|
|
AfxGetInstanceHandle(),
|
|
m_clsid,
|
|
m_lpszProgID,
|
|
IDS_CERTAUTH,
|
|
IDB_CERTAUTH,
|
|
afxRegApartmentThreading,
|
|
_dwCertAuthOleMisc,
|
|
_tlid,
|
|
_wVerMajor,
|
|
_wVerMinor);
|
|
else
|
|
return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl::CCertAuthCtrl - Constructor
|
|
|
|
CCertAuthCtrl::CCertAuthCtrl():
|
|
m_fUpdateFont( FALSE ),
|
|
m_hAccel( NULL ),
|
|
m_cAccel( 0 )
|
|
{
|
|
InitializeIIDs(&IID_DCertAuth, &IID_DCertAuthEvents);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl::~CCertAuthCtrl - Destructor
|
|
|
|
CCertAuthCtrl::~CCertAuthCtrl()
|
|
{
|
|
if ( m_hAccel )
|
|
DestroyAcceleratorTable( m_hAccel );
|
|
|
|
m_hAccel = NULL;
|
|
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl::OnDraw - Drawing function
|
|
|
|
void CCertAuthCtrl::OnDraw(
|
|
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
|
|
{
|
|
CFont* pOldFont;
|
|
|
|
// select the stock font, recording the old one
|
|
pOldFont = SelectStockFont( pdc );
|
|
|
|
// do the superclass draw
|
|
DoSuperclassPaint(pdc, rcBounds);
|
|
|
|
// restore the old font - sneakily getting the correct font object
|
|
pOldFont = pdc->SelectObject(pOldFont);
|
|
|
|
// we want the button window to continue drawing in the correct font even
|
|
// when we are not using OnDraw. i.e. when it is being pushed down. This
|
|
// means we need to set the CWnd::SetFont() method.
|
|
if ( m_fUpdateFont )
|
|
{
|
|
m_fUpdateFont = FALSE;
|
|
CWnd::SetFont( pOldFont );
|
|
}
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl::DoPropExchange - Persistence support
|
|
|
|
void CCertAuthCtrl::DoPropExchange(CPropExchange* pPX)
|
|
{
|
|
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
|
|
COleControl::DoPropExchange(pPX);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl::OnResetState - Reset control to default state
|
|
|
|
void CCertAuthCtrl::OnResetState()
|
|
{
|
|
COleControl::OnResetState(); // Resets defaults found in DoPropExchange
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl::AboutBox - Display an "About" box to the user
|
|
|
|
void CCertAuthCtrl::AboutBox()
|
|
{
|
|
// CDialog dlgAbout(IDD_ABOUTBOX_CERTAUTH);
|
|
// dlgAbout.DoModal();
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCertAuthCtrl message handlers
|
|
|
|
//---------------------------------------------------------------------------
|
|
BOOL CCertAuthCtrl::PreCreateWindow(CREATESTRUCT& cs)
|
|
{
|
|
if ( cs.style & WS_CLIPSIBLINGS )
|
|
cs.style ^= WS_CLIPSIBLINGS;
|
|
cs.lpszClass = _T("BUTTON");
|
|
return COleControl::PreCreateWindow(cs);
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAppsCtrl::IsSubclassedControl - This is a subclassed control
|
|
|
|
BOOL CCertAuthCtrl::IsSubclassedControl()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// OnOcmCommand - Handle command messages
|
|
|
|
LRESULT CCertAuthCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
#ifdef _WIN32
|
|
WORD wNotifyCode = HIWORD(wParam);
|
|
#else
|
|
WORD wNotifyCode = HIWORD(lParam);
|
|
#endif
|
|
|
|
// TODO: Switch on wNotifyCode here.
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*dddddddddd
|
|
#ifdef FUTURE_USE
|
|
HRESULT CCertAuthCtrl::LaunchCommonCTLDialog (CCTL* pCTL)
|
|
{
|
|
ASSERT (pCTL);
|
|
CRYPTUI_VIEWCTL_STRUCT vcs;
|
|
HWND hwndParent=0;
|
|
// //
|
|
// // Success codes defined for HRESULTS:
|
|
// //
|
|
// #define S_OK ((HRESULT)0x00000000L)
|
|
// #define S_FALSE ((HRESULT)0x00000001L)
|
|
|
|
HRESULT hResult= S_OK;
|
|
|
|
::ZeroMemory (&vcs, sizeof (vcs));
|
|
vcs.dwSize = sizeof (vcs);
|
|
vcs.hwndParent = hwndParent;
|
|
vcs.dwFlags = 0;
|
|
vcs.pCTLContext = pCTL->GetCTLContext ();
|
|
|
|
BOOL bResult = ::CryptUIDlgViewCTL (&vcs);
|
|
if ( bResult )
|
|
{
|
|
}
|
|
|
|
return hResult;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
//-----------------------------------------------------------------------
|
|
// checkSanityMFC is used only for debugging some strange MFC problem
|
|
// where MFC loses it state!
|
|
//
|
|
// Method: 20 times ask MFC to get out application name that
|
|
// should be "certmap" --> if we fail we throw assert + return FALSE
|
|
// OTHERWISE we return TRUE;
|
|
//-----------------------------------------------------------------------
|
|
BOOL checkSanityMFC()
|
|
{
|
|
|
|
for (int i=0; i<20; i++) {
|
|
CString szCaption = AfxGetAppName(); // for debug, we now print it out
|
|
ASSERT( STRICMP(szCaption, _T("certmap"))==0);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
*/
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------
|
|
// OnClick - process a click event. This will be called by the IIS admin
|
|
// API to launch us! [people get here by clicking on our control]
|
|
//
|
|
// PARAMS: iButton an IN param. tells what "logical button" fired our control:
|
|
//
|
|
// value
|
|
// ------
|
|
// 1 Call CertWiz
|
|
// 2 Call CTL Wiz
|
|
//-------------------------------------------------------------------------------
|
|
extern void test__non2Rons_WizClasses();
|
|
|
|
void CCertAuthCtrl::OnClick(USHORT iButton)
|
|
{
|
|
|
|
/*ddddddddddddddd
|
|
AFX_MANAGE_STATE (AfxGetStaticModuleState ());
|
|
|
|
#if defined(DEBUGGING)
|
|
if (DebugTestEnv(_T("MFC")) && YesNoMsgBox(_T("Shall we run: test__non2Rons_WizClasses"))) {
|
|
test__non2Rons_WizClasses();
|
|
}
|
|
#endif
|
|
|
|
#if defined(DEBUGGING) && defined(SHOW_TRACE_INFO)
|
|
Trace t( _T("CCertAuthCtrl::OnClick"));
|
|
#endif
|
|
|
|
#if defined(DEBUGGING)
|
|
checkSanityMFC();
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
|
|
(void) ShallWeShowDebugMsgs(); // the purpose of this call is to make sure
|
|
// that we properly initialize [if we are
|
|
// to emit debug stmts, even though we have
|
|
// a debug build]. We currently look in Reg:
|
|
// "Software\\Microsoft\\CertMap\\Debug" under
|
|
// HKEY_CURRENT_USER for a Key with:
|
|
// name: "Enabled" and set to "TRUE".
|
|
// If we find that we produce debug stmts.
|
|
// See file Debug.cpp and Debug.h for more info
|
|
#endif
|
|
|
|
// m_szMachineName currently has the machine we were told to operate upon
|
|
// "We currently do not support remote administration of Web servers."
|
|
|
|
// ComputerNameMatchesOurMachineName(IN OUT CString& remoteHostname)
|
|
//
|
|
// After trimming [remoteHostname] see if its naming this
|
|
// machine. We look for a direct case-independent match
|
|
// or "localhost"
|
|
//
|
|
// RETURNS: T: iff the name passed in matches the machine we are
|
|
// running on.
|
|
//
|
|
// NOTE: remoteHostname is an IN OUT. OUT since we trim any white
|
|
// space off of it.
|
|
//------------------------------------------------------------------
|
|
if (FALSE == ComputerNameMatchesOurMachineName(m_szMachineName))
|
|
{
|
|
if (FALSE == m_szMachineName.IsEmpty() ) {
|
|
MessageBeep(0);
|
|
MsgBox( IDS_WE_CURRENTLY_DONT_SUPPORT_REMOTE_ADMIN_OF_SERVERS );
|
|
return ;
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// The following bucnch of code's sole purpose is to set the application's
|
|
// name properly
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// in case there are any errors, prepare the error string
|
|
//
|
|
// We store the following in our class: CString m_szOurApplicationTitle;
|
|
|
|
// set the name of the application correctly
|
|
// Use our Fancy Debugging "Load String" found in Easy::CString
|
|
Easy::CString::LoadStringInto( m_szOurApplicationTitle, IDS_ERR_CERTWIZ_TITLE );
|
|
// tompop: tjp: do we need a different string than
|
|
// what CertMap uses? They use: IDS_ERR_CERTMAP_TITLE
|
|
// should we use the same? They say "Certificate Mapping"
|
|
// as the title; we say "Server Certificate Wizard"
|
|
|
|
|
|
// Testing needs different titles for the error popups.
|
|
// IDS_ERR_CERTWIZ_TITLE="Server Certificate Wizard" our std title by design.
|
|
// So if somebody has set Env=TitleCertWizError, we give CertWiz as the title
|
|
// so that we can pick them off in automatic tests
|
|
//----------------------------------------------------------
|
|
if (DebugTestEnv(_T("TitleCertWizError"))) {
|
|
m_szOurApplicationTitle = _T("CertWiz"); // used in automatic tests
|
|
}
|
|
|
|
// free the existing name, and copy in the new one
|
|
// tjp: you should compare if the old name matches the current name
|
|
// and only then free and malloc the new name -- chances are that
|
|
// the names are the same +++ all the free/malloc can fragment mem.
|
|
free((void*)AfxGetApp()->m_pszAppName);
|
|
AfxGetApp()->m_pszAppName = _tcsdup(m_szOurApplicationTitle);
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// this is why we are here
|
|
// this is the whole purpose of the control
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
RunDialogs4OnClick(iButton);
|
|
|
|
#if defined(DEBUGGING)
|
|
DODBG MsgBox( _T("DBG: finished our call to RunDialogs4OnClick") );
|
|
#endif
|
|
*/
|
|
// we are not in the business of telling the host to do
|
|
// something here, so just don't fire anything off.
|
|
COleControl::OnClick(iButton);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
////////////////////////////////////////////////////////////////////////
|
|
// main handler for our dialogs:
|
|
// 1. setup the metabase and then,
|
|
// 2. call the routine that actually handles the dialogs
|
|
// trapping exceptions to protect the metaBase
|
|
//
|
|
// Parms: iButton: tells what "logical button" fired our control:
|
|
// 0=Get-Cert 1=Edit
|
|
////////////////////////////////////////////////////////////////////////
|
|
BOOL CCertAuthCtrl::RunDialogs4OnClick(USHORT iButton)
|
|
{
|
|
#if defined(DEBUGGING) && defined(SHOW_TRACE_INFO)
|
|
Trace t( _T("CCertAuthCtrl::RunDialogs4OnClick"));
|
|
#endif
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
checkSanityMFC();
|
|
#endif
|
|
////////////////////////////////////////////////////////////////////
|
|
/////////////////////// Do a Boydm style m_szServerInstance //
|
|
/////////////////////// normalization operation //
|
|
////////////////////////////////////////////////////////////////////
|
|
// if there is nothing in the MB_Path, default to the first instance
|
|
|
|
|
|
if ( m_szServerInstance.IsEmpty() )
|
|
{
|
|
// we might want to assign a default. We do so in testing.
|
|
// otherwise we fail hard!
|
|
MsgBox( IDS_NO_SERVER_INSTANCE_SET );
|
|
//"The metabase path is not set for the virtual Web server instance. Internal error; exiting"
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
if ( DebugTestEnv() ) // if we are in a debug env, we are forgiving
|
|
// if the user forgot to set the instance
|
|
{
|
|
m_szServerInstance = _T("/LM/W3SVC/1"); // hack!
|
|
// let it be the default web site!
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
#else
|
|
// production env, this instance path needs to be set
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
#if defined(DEBUGGING)
|
|
DODBG {
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Below we some testing hooks for manipulating the machine/path
|
|
// that we operate upon.
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
CEditDialog dlgMach(m_szMachineName,
|
|
_T("Below is the machine name of the server instance; it corresponds"
|
|
" to which machine's metabase we will be administrating.\n"
|
|
"\nFor testing purposes you"
|
|
" can change it to whatever you want, now. BLANK means local machine."));
|
|
dlgMach.DoModal();
|
|
|
|
CEditDialog dlgPath(m_szServerInstance,
|
|
_T("Below is the server instance path; it corresponds"
|
|
" with the path in the metabase. For testing purposes you"
|
|
" can change it to whatever you want, now\n"
|
|
"\n"
|
|
"Note if it is BLANK we work on \"/LM/W3SVC/1\"" ));
|
|
|
|
dlgPath.DoModal();
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
//DebugBreak();
|
|
// prepare the machine name pointer
|
|
OLECHAR* poch = NULL;
|
|
if ( ! m_szMachineName.IsEmpty() )
|
|
{
|
|
// allocate the name buffer
|
|
poch = (OLECHAR*)GlobalAlloc( GPTR, MAX_COMPUTERNAME_LENGTH * 2 );
|
|
|
|
// unicodize the name into the buffer
|
|
if ( poch )
|
|
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, m_szMachineName, -1,
|
|
poch, MAX_COMPUTERNAME_LENGTH * 2 );
|
|
}
|
|
|
|
// initialize the metabase wrappings - pass in the name of the target machine
|
|
// if one has been specified
|
|
|
|
IMSAdminBase* pMB = FInitMetabaseWrapper( poch );
|
|
if ( !pMB )
|
|
{
|
|
HRESULT hr = GetLastError(); // the bad hr is in GetLastError()
|
|
|
|
MessageBeep(0);
|
|
if ( poch )
|
|
GlobalFree( poch );
|
|
|
|
const BOOL bDoErrorMsgPopup = TRUE;
|
|
if (bDoErrorMsgPopup) {
|
|
CString szError;
|
|
|
|
Easy::Load(szError, IDS_COULD_NOT_ACCESS_THE_METABASE);
|
|
// "Could not access the metabase, where we store state information"
|
|
|
|
AppendMachineName(szError, m_szMachineName );
|
|
|
|
AppendHResultErrorString(szError, hr);
|
|
|
|
MsgBox( szError );
|
|
}
|
|
|
|
return FALSE; // <<=== we failed!
|
|
}
|
|
|
|
// clean up
|
|
if ( poch )
|
|
{
|
|
GlobalFree( poch );
|
|
poch = NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// prepare the metabase wrapper
|
|
/////////////////////////////////////////////////////////////////////
|
|
CWrapMetaBase mbWrap;
|
|
BOOL f = mbWrap.FInit(pMB);
|
|
if ( FALSE == f )
|
|
{
|
|
return FALSE; // <<=== we failed!
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Things could (potentially maybe) throw here, so better protect the
|
|
// metabase
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// //-----------------------------------------------------------------------------
|
|
// // here is an example of making a CMetaWrapper call,
|
|
// // we will typically use GetData calls and grab the meta data Unicode
|
|
// // strings also that way since casting a (CONST WCHAR*) into a CString
|
|
// // will give us a FREE conversion from WideChar to MultiByte.
|
|
// //-----------------------------------------------------------------------------
|
|
// BOOL CAFX_MetaWrapper::GetString( const CHAR* pszPath,
|
|
// DWORD dwPropID, DWORD dwUserType,
|
|
// CString &sz, DWORD dwFlags )
|
|
// {
|
|
//
|
|
// // first, get the size of the data that we are looking for - it
|
|
// // will fail because of the NULL,
|
|
// // but, the size we need should be in cbData;
|
|
// f = GetData( pszPath, dwPropID, dwUserType, STRING_METADATA, NULL, &cbData );
|
|
//
|
|
// // check the error - it should be some sort of memory error
|
|
//
|
|
// }
|
|
// //-----------------------------------------------------------------------------
|
|
|
|
|
|
try
|
|
{
|
|
|
|
|
|
// I am assuming that the last character is NOT a '/' Thus, if that is what is
|
|
// there, we need to remove it. Otherwise, the path gets messed up later
|
|
if ( m_szServerInstance.Right(1) == '/' )
|
|
m_szServerInstance = m_szServerInstance.Left( m_szServerInstance.GetLength()-1 );
|
|
|
|
|
|
ADMIN_INFO info(mbWrap); // the info object is the central data
|
|
// repository. It also has the meta
|
|
// base wrapper in it.
|
|
|
|
info.szMetaBasePath = m_szServerInstance; // need to store the path
|
|
// because we need it for generating
|
|
// unique Key Container names, will
|
|
// be done in file nLocalEnrl.cpp
|
|
|
|
|
|
info.szMachineName_MetaBaseTarget = m_szMachineName;
|
|
|
|
// NOTE that this routine will either install or DE-install the metabase
|
|
// GUID strings based on its decision that CertServer is present.
|
|
// If we find GUID strings in the metabase but can not do a
|
|
// CoCreateInstance on ICertConfig:
|
|
// we remove them so that the rest of CertWizard will see
|
|
// MB Guids iff we can use CertServer.
|
|
//---------------------------------------------------------------
|
|
|
|
BOOL bFoundCertServer = MigrateGUIDS( info );
|
|
|
|
|
|
BOOL success = _RunDialogs4OnClick(info,0); // this runs the dialogs
|
|
// note that we are passing
|
|
// mbWrap not pMB
|
|
// save the metabase
|
|
mbWrap.Save();
|
|
|
|
#if 0
|
|
// close the base object
|
|
// ==> we dont really have to do this -- every time we
|
|
// go to the MB we open and close it. If closing it again
|
|
// a second time does not hurt we can do it -- tompop ToDo
|
|
mbWrap.Close();
|
|
#endif
|
|
|
|
|
|
}
|
|
catch ( CException e )
|
|
{
|
|
}
|
|
|
|
// close the metabase wrappings
|
|
FCloseMetabaseWrapper(pMB);
|
|
|
|
return (TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
void test_Reg_GetSetName()
|
|
{
|
|
CString newValue=_T("Tommy Boy");
|
|
BOOL bSet =
|
|
Reg::HKEYLM::SetNameValue("Software\\Microsoft\\Keyring\\Parameters",
|
|
"UserName",
|
|
newValue);
|
|
ASSERT( bSet==TRUE );
|
|
|
|
CString readValue;
|
|
BOOL bGet =
|
|
Reg::HKEYLM::GetNameValue("Software\\Microsoft\\Keyring\\Parameters",
|
|
"UserName",
|
|
readValue);
|
|
ASSERT( bSet==TRUE );
|
|
|
|
DEBUG_MSGBOX( (buff, "After calling SetNameValue/SetNameValue; val=%s"
|
|
"bSet=%d, bGet=%d",
|
|
(const char*) readValue, (int)bSet, (int)bGet ));
|
|
// the above will test the GetName value and read the
|
|
}
|
|
#endif
|
|
|
|
|
|
void test__CEasyFileDialog();
|
|
|
|
void test__itemsWeDependOn(IN OUT ADMIN_INFO& info, USHORT iButton)
|
|
{
|
|
|
|
#if defined(DEBUGGING)
|
|
// note that if you need the meta base wrapper directly
|
|
// its available as [CWrapMetaBase&] info.mbWrap
|
|
|
|
|
|
|
|
|
|
|
|
#if 0 // ----- use this for testing
|
|
BOOL bTest_Reg_GetSetName = FALSE;
|
|
if (bTest_Reg_GetSetName)
|
|
{
|
|
test_Reg_GetSetName();
|
|
}
|
|
#endif //0 ----- use this for testing
|
|
|
|
|
|
#if 0 // ----- use this for testing
|
|
/////////////////// this starts the test of Boydm's MetaBaseWrapper ///
|
|
|
|
BOOL fAnswer = FALSE;
|
|
PVOID pData = NULL;
|
|
DWORD cbData = 0;
|
|
|
|
// attempt to open the object we want to store into
|
|
CWrapMetaBase& mbWrap = info.meta.m_mbWrap;
|
|
BOOL f = mbWrap.Open( m_szServerInstance, METADATA_PERMISSION_READ );
|
|
|
|
// if opening the metabase worked, load the data
|
|
if ( f )
|
|
{
|
|
// first, get the size of the data that we are looking for
|
|
pData = mbWrap.GetData( "", 1002, IIS_MD_UT_SERVER, STRING_METADATA, &cbData );
|
|
// "keytype" is 1002 -- this is just a test
|
|
// It uses a BINARY_METADATA is another type
|
|
// BINARY_METADATA is another type -- all types are shown
|
|
// in the comments below, see file MdDefW.h for details
|
|
//
|
|
//
|
|
// enum METADATATYPES
|
|
// { ALL_METADATA = 0,
|
|
// DWORD_METADATA = ALL_METADATA + 1,
|
|
// STRING_METADATA = DWORD_METADATA + 1,
|
|
// BINARY_METADATA = STRING_METADATA + 1,
|
|
// EXPANDSZ_METADATA = BINARY_METADATA + 1,
|
|
// MULTISZ_METADATA = EXPANDSZ_METADATA + 1,
|
|
// INVALID_END_METADATA = MULTISZ_METADATA + 1
|
|
// };
|
|
|
|
// if you want you could use our MBPdata_Wrapper to protect
|
|
// it automatically
|
|
//
|
|
// Util::MBPdata_Wrapper a(mbWrap, pData );
|
|
|
|
// if we successfully got the data, unserialize it
|
|
if ( pData )
|
|
{
|
|
// === the following stuff + the mbWrap setup code came
|
|
// from an example in file 'Issudlg.cpp' see that for
|
|
// another example.
|
|
//
|
|
//PUCHAR pchData = (PUCHAR)pData;
|
|
//fAnswer = m_Store.Unserialize( &pchData, &cbData );
|
|
}
|
|
|
|
// close the object
|
|
f = mbWrap.Close();
|
|
|
|
// cleanup
|
|
if ( pData )
|
|
mbWrap.FreeWrapData( pData );
|
|
}
|
|
/////////////////// this ends the test of Boydm's MetaBaseWrapper ///
|
|
#endif //0 ----- use this for testing
|
|
|
|
|
|
|
|
#if 0 // ----- use this for testing
|
|
// next we will test our Set/Get Strings on mb prop: MD_SSL_CTL_CONTAINER
|
|
|
|
{ //MD_SSL_CTL_CONTAINER
|
|
|
|
const TCHAR* szDATA = _T("Tom");
|
|
BOOL f1= SetMetaBaseString ( info, MD_SSL_CTL_CONTAINER, szDATA);
|
|
|
|
CString szReadTom;
|
|
BOOL f2= GetMetaBaseString ( info, MD_SSL_CTL_CONTAINER, szReadTom);
|
|
|
|
BOOL f3= (szReadTom == szDATA);
|
|
|
|
if ( !( f1 && f2 && f3)) {
|
|
DEBUG_MSGBOX( (buff, _T("MD_SSL_CTL_CONTAINER prop get/set failed --"
|
|
"you have trouble in the MB; Details: %s %s %s"),
|
|
Map::Boolean2String(f1),
|
|
Map::Boolean2String(f2),
|
|
Map::Boolean2String(f3) ));
|
|
}
|
|
}
|
|
#endif //0 ----- use this for testing
|
|
|
|
|
|
|
|
#if 0 // ----- use this for testing
|
|
// lets see if IIS is up by looking for the following metabase props:
|
|
//
|
|
// MD_SECURE_BINDINGS is for SECURE server address/port
|
|
// MD_SERVER_BINDINGS is for non secure server address/port
|
|
//
|
|
{
|
|
CString szSecureBindings;
|
|
CString szServerBindings;
|
|
if ( (TRUE == GetMetaBaseString ( info, MD_SECURE_BINDINGS, szSecureBindings))
|
|
&& (TRUE == GetMetaBaseString ( info, MD_SERVER_BINDINGS, szServerBindings)))
|
|
{
|
|
DEBUG_MSGBOX( (buff, _T("Looks like IIS is on the machine since we have"
|
|
"SERVER_BINDINGS of [%s] and SECURE_BINDINGS of [%s]"),
|
|
szServerBindings, szSecureBindings
|
|
//Util::StringOrNil(szServerBindings), Util::StringOrNil(szSecureBindings)
|
|
));
|
|
}
|
|
}
|
|
|
|
DODBG if (NoYesMsgBox(_T("Do You Want to CLEAR out the MetaBase History to GROUND ZERO?") ))
|
|
{
|
|
ClearHistoryState(info);
|
|
DisplayInfoStruct(info);
|
|
|
|
if (NoYesMsgBox(_T("Do You Want to SET ALL the bits in the history that deal"
|
|
"with historyStateSSS bits? Then see the info printout?") ))
|
|
{
|
|
ClearHistoryState(info);
|
|
|
|
SetCertInstalled(info);
|
|
SetOOBCertReq(info);
|
|
SetCTLInstalled(info);
|
|
SetOutstandingRenewalReq(info);
|
|
|
|
DisplayInfoStruct(info);
|
|
|
|
if (NoYesMsgBox(_T("NEXT Do You Want to CLEAR ALL the bits in the history that deal"
|
|
"with historyStateSSS bits? Then see the info printout?") ))
|
|
{
|
|
ClearCertInstalled(info);
|
|
ClearOOBCertReq(info);
|
|
ClearCTLInstalled(info);
|
|
ClearOutstandingRenewalReq(info);
|
|
DisplayInfoStruct(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL bTest_LoadStrings = FALSE;
|
|
if (bTest_LoadStrings) Easy::CString::Test_LoadString();
|
|
#endif //0 ----- use this for testing
|
|
|
|
|
|
#if 0 // ----- use this for testing
|
|
|
|
if (YesNoMsgBox(_T("Do You Want to Run the CTL Wizard") ) )
|
|
{
|
|
// Run the CTL Wizard
|
|
// v--- was setup in the routine that called RunDialogs4OnClick
|
|
// info(mbWrap); // main status board in the
|
|
// wizards
|
|
|
|
//run the create CTL wizard. We start by declaring all the pieces of it
|
|
CPropertySheet propsheet_mux(IDS_TITLE_CREATE_CTL_WIZ);
|
|
CTLWelcome page_welcome;
|
|
CTLMuxPg page_multiplexer(info);
|
|
CPropertyPage stubTipsPage(IDD_TIPS_BEFORE__CTL_WIZARD);
|
|
// clear the help button bits
|
|
if ( propsheet_mux.m_psh.dwFlags & PSH_HASHELP )
|
|
propsheet_mux.m_psh.dwFlags &= ~PSH_HASHELP;
|
|
page_welcome.m_psp.dwFlags &= ~(PSP_HASHELP);
|
|
page_multiplexer.m_psp.dwFlags &= ~(PSP_HASHELP);
|
|
stubTipsPage.m_psp.dwFlags &= ~(PSP_HASHELP);
|
|
|
|
// add the pages to the property sheet that says Welcome, and the
|
|
// Mux (aka multiplexing) page...
|
|
propsheet_mux.AddPage( &page_welcome );
|
|
propsheet_mux.AddPage( &page_multiplexer );
|
|
propsheet_mux.AddPage( &stubTipsPage );
|
|
|
|
// set the wizard property
|
|
propsheet_mux.SetWizardMode();
|
|
|
|
// run the property sheet
|
|
int i = IDCANCEL;
|
|
i = propsheet_mux.DoModal();
|
|
if ( i != IDCANCEL )
|
|
{
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
DODBG MsgBox( _T("CTL muxer dialog box works and returned OK" ) );
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (bCall_test__CEasyFileDialog) test__CEasyFileDialog();
|
|
#endif //0 ----- use this for testing
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------
|
|
// NKAddPageToWizard -- just like WizMngr::AddPageToWizard in that
|
|
// it adds CPropertyPage [CNKPages nkpg2Add] to Wizard [psWizard]
|
|
// making sure that the [pg2Add] has its help button
|
|
// turned OFF!
|
|
//
|
|
// It also binds the wizard property sheet page [psWizard]
|
|
// to member [m_pPropSheet] of the CNKPages object.
|
|
//
|
|
// This is used to add our pages to the Wizard below
|
|
//------------------------------------------------------------------
|
|
void CCertAuthCtrl::NKAddPageToWizard(IN ADMIN_INFO& info, IN CNKPages* nkpg2Add, IN OUT CPropertySheet* psWizard)
|
|
{
|
|
// add the pages to the wizard property sheet AND make no help button
|
|
AddPageToWizard(nkpg2Add, psWizard);
|
|
|
|
nkpg2Add->m_pPropSheet = psWizard;
|
|
}
|
|
|
|
#if defined(DEBUGGING)
|
|
void test__Rons_WizClasses(IN ADMIN_INFO& info)
|
|
{
|
|
CIISWizardSheet propsheet_mux(IDB_BITMAP_STD_LEFT,
|
|
IDB_BITMAP_TOP);
|
|
CNKWelcome page_welcome(info);
|
|
CNKMuxPg page_multiplexer(info);
|
|
propsheet_mux.AddPage( &page_welcome );
|
|
propsheet_mux.AddPage( &page_multiplexer );
|
|
|
|
DEBUG_MSGBOX( (buff, _T("Get Ready using real Ron's CIISWizardSheet\n")));
|
|
|
|
int i = propsheet_mux.GetPageCount();
|
|
void* p = propsheet_mux.GetPage(i-1);
|
|
|
|
|
|
DEBUG_MSGBOX( (buff, _T("%s %d -- using real Ron's\n"
|
|
" GetPageCount()=%d GetPage(%d)=%#lx"), __FILE__, __LINE__,
|
|
i, i-1, (DWORD)p));
|
|
}
|
|
#endif //defined(DEBUGGING)
|
|
|
|
#if defined(DEBUGGING)
|
|
void test__nonRons_WizClasses(IN ADMIN_INFO& info)
|
|
{
|
|
CPropertySheet propsheet_mux;
|
|
|
|
CNKWelcome page_welcome(info);
|
|
CNKMuxPg page_multiplexer(info);
|
|
propsheet_mux.AddPage( &page_welcome );
|
|
propsheet_mux.AddPage( &page_multiplexer );
|
|
|
|
int i = propsheet_mux.GetPageCount();
|
|
void* p = propsheet_mux.GetPage(i-1);
|
|
|
|
|
|
DEBUG_MSGBOX( (buff, _T("%s %d -- using real CPropertySheet\n"
|
|
" GetPageCount()=%d GetPage(%d)=%#lx\n"), __FILE__, __LINE__,
|
|
i, i-1, (DWORD)p));
|
|
}
|
|
#endif //defined(DEBUGGING)
|
|
|
|
#if defined(DEBUGGING)
|
|
void test__non2Rons_WizClasses()
|
|
{
|
|
DEBUG_MSGBOX( (buff, _T("Sizeof CPropertySheet=%d,CPropertyPage=%d\n"),
|
|
sizeof(CPropertySheet),sizeof(CPropertyPage)));
|
|
|
|
CPropertySheet propsheet_mux;
|
|
|
|
CPropertyPage page_welcome;
|
|
CPropertyPage page_multiplexer;
|
|
propsheet_mux.AddPage( &page_welcome );
|
|
propsheet_mux.AddPage( &page_multiplexer );
|
|
|
|
int i = propsheet_mux.GetPageCount();
|
|
DEBUG_MSGBOX( (buff, _T("Get Ready using real CPropertySheet,CPropertyPage\n")));
|
|
void* p = propsheet_mux.GetPage(i-1);
|
|
|
|
DEBUG_MSGBOX( (buff, _T("%s %d -- using real CPropertySheet,CPropertyPage\n"
|
|
" GetPageCount()=%d GetPage(%d)=%#lx\n"), __FILE__, __LINE__,
|
|
i, i-1, (DWORD)p));
|
|
}
|
|
#endif //defined(DEBUGGING)
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// _RunDialogs4OnClick -- main handler for our dialogs
|
|
//
|
|
// Parms: mbWrap: ref to a MetaBase Wrapper that is properly initialized
|
|
// and points to the SERVER node that we are operating in.
|
|
// iButton: tells what "logical button" fired our control:
|
|
// 0=Get-Cert 1=Edit
|
|
////////////////////////////////////////////////////////////////////////
|
|
BOOL CCertAuthCtrl::_RunDialogs4OnClick(IN OUT ADMIN_INFO& info, USHORT iButton)
|
|
{
|
|
|
|
#if defined(DEBUGGING) && defined(SHOW_TRACE_INFO)
|
|
Trace t( _T("CCertAuthCtrl::_RunDialogs4OnClick"));
|
|
#endif
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
checkSanityMFC();
|
|
#endif
|
|
|
|
// EnumCertServerObjects is now our std locate-all-cert-servers available method
|
|
// it enumerates CertServers the metabase for CertServers
|
|
// that are configured correctly and stores a mapping to GUID
|
|
// strings in our 'info.map_CertServerNameToGUIDmap' structure
|
|
EnumCertServerObjects(info);
|
|
|
|
// The following will call is used to debug what we depend upon
|
|
test__itemsWeDependOn(info, iButton);
|
|
|
|
#if defined(DEBUGGING)
|
|
if (DebugTestEnv(_T("MFC")) && YesNoMsgBox(_T("Shall we run: test__Rons_WizClasses"))) {
|
|
test__Rons_WizClasses(info);
|
|
}
|
|
#endif
|
|
|
|
|
|
// ToDo: we need to change this to buttons
|
|
// in a particular call
|
|
// we should do something like: if (iButton==1)
|
|
// for testing you can do YesNoMsgBox(_T("Do You Want to Run the CertWizard"))
|
|
{
|
|
#if defined(DEBUGGING) && defined(SHOW_TRACE_INFO)
|
|
Trace t( _T("In AuthCtl.cpp -- stack frame launching the propsheet_mux wiz."));
|
|
#endif
|
|
|
|
|
|
// v--- was setup in the routine that called RunDialogs4OnClick
|
|
//ADMIN_INFO info(mbWrap); // main status board in the
|
|
// // wizards
|
|
|
|
//run the create key wizard. We start by declaring all the pieces of it
|
|
#ifdef USE_COMPROP_WIZ97
|
|
CIISWizardSheet propsheet_mux(IDB_BITMAP_STD_LEFT,
|
|
IDB_BITMAP_TOP);
|
|
// CString szIDS_TITLE_CREATE_WIZ;
|
|
// Easy::Load(szIDS_TITLE_CREATE_WIZ, IDS_TITLE_CREATE_WIZ);
|
|
// propsheet_mux.SetTitle(szIDS_TITLE_CREATE_WIZ);
|
|
#else
|
|
CPropertySheet propsheet_mux( IDS_TITLE_CREATE_WIZ );
|
|
#endif
|
|
|
|
CNKWelcome page_welcome(info);
|
|
CNKMuxPg page_multiplexer(info);
|
|
|
|
// Living within info is 'wizMngr' -- this manages the Wizard. See
|
|
// file WizMngr.cpp for more info. We need to instruct it what the
|
|
// property sheet is and also where the error page lives. At this
|
|
// time the only valid pages are the Welcome + Completion + Error
|
|
|
|
WizMngr& wizMngr = info.wizMngr;
|
|
|
|
wizMngr.Attach( &propsheet_mux );
|
|
|
|
|
|
wizMngr.SetErrorPageIndex( WZ_PAGE__GENERIC_ERROR_PAGE );
|
|
wizMngr.SetDonePageIndex( WZ_PAGE__GENERIC_COMPLETION_PAGE );
|
|
// then later info.wizMngr.ShowDonePage(); will display this completion pg.
|
|
wizMngr.Enable(0,0); // enable welcome page
|
|
wizMngr.Enable(WZ_PAGE__GENERIC_COMPLETION_PAGE, // Enable our generic
|
|
WZ_PAGE__GENERIC_ERROR_PAGE); // Completion-Error page
|
|
|
|
|
|
// the following define the new Server pages
|
|
CNKChooseCA page_Choose_CA(info);
|
|
CNKKeyInfo page_Key_Info(info);
|
|
CNKDistinguishedName page_DN(info);
|
|
CNKDistinguishedName1 page_DN1(info);
|
|
CNKDistinguisedName2 page_DN2(info);
|
|
CNKUserInfo page_User_Info(info);
|
|
CNKFileInfo page_File_Info(info);
|
|
|
|
|
|
// lets warn the user that crytpo is not enabled, GetMaxKeySize will
|
|
// return 0 if no ENCRYPTION is ALLOWED
|
|
//----------------------------------------------
|
|
KeySize keySize; // fncts GetMaxKeySize and ComputeKeyAlgMinMaxStep return
|
|
// this as an OUT parameter.
|
|
if (0 == page_Key_Info.GetMaxKeySize(OUT keySize) ) {
|
|
MsgBox( IDS_ENCRYPTION_NOT_ALLOWED_BASED_ON_THE_MACHINE_CONFIG_DATA );
|
|
//"Encryption is not allowed based on the machine configuration data."END
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// the following 2 Classes contain PropertyPages for the Finish the offline/OOB
|
|
// cert request
|
|
//
|
|
// class CFinCertImport is the 1st & only Property Sheet for "Finish OOB"
|
|
// (gathers data). it also verifies and asks the user:
|
|
// are you "ready to "FINISH" and do the conversion
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
CFinCertImport getFileInfoPage(info);
|
|
|
|
|
|
// End the above list of "all possible wizard pages" with the
|
|
// completion page then the Error page. Error always has to be last!
|
|
// Note also that this completion page is the "default" completion
|
|
// page. Some sub-wizard might have a more appropriate specialized
|
|
// page for completion
|
|
CCompletionPage page_GenericCompletion(info);
|
|
CWizError page_GenericErrorPage(info);
|
|
|
|
info.wizErrorDlg = &page_GenericErrorPage;
|
|
|
|
// clear the help button bits on the PropertySheet and all Prop
|
|
// pages. We handle the clearing for each page in the funct AddPageToWizard
|
|
if ( propsheet_mux.m_psh.dwFlags & PSH_HASHELP )
|
|
propsheet_mux.m_psh.dwFlags &= ~PSH_HASHELP;
|
|
|
|
// add the pages to the property sheet that says Welcome, and the
|
|
// Mux (aka multiplexing) page... The AddPageToWizard will do an
|
|
// AddPage on the property sheet and also specify that we dont want
|
|
// any help buttons
|
|
|
|
|
|
NKAddPageToWizard(info, &page_welcome, &propsheet_mux ); //index:0:WZ_PAGE__WELCOME
|
|
|
|
//this is for the New Cert Wizard
|
|
NKAddPageToWizard(info, &page_multiplexer, &propsheet_mux ); //index:1:WZ_PAGE__CERTMUX
|
|
NKAddPageToWizard(info, &page_Choose_CA, &propsheet_mux );
|
|
NKAddPageToWizard(info, &page_Key_Info, &propsheet_mux );
|
|
NKAddPageToWizard(info, &page_DN, &propsheet_mux );
|
|
NKAddPageToWizard(info, &page_DN1, &propsheet_mux );
|
|
NKAddPageToWizard(info, &page_DN2, &propsheet_mux );
|
|
NKAddPageToWizard(info, &page_User_Info, &propsheet_mux );
|
|
NKAddPageToWizard(info, &page_File_Info, &propsheet_mux ); //index:8:WZ_PAGE__SUCCESS_PAGE
|
|
//this is for finishing the SELECT_EXISTING_CERT page -- request
|
|
CCertSelect page_CertSelect(info);
|
|
NKAddPageToWizard(info, &page_CertSelect, &propsheet_mux ); //index:9:WZ_PAGE__SELECT_EXISTING_CERT_PAGE
|
|
//this is for finishing the IMPORT_KEYRING page -- request
|
|
CKeyImpt gatherData(info);
|
|
NKAddPageToWizard(info, &gatherData, &propsheet_mux ); //index:10:WZ_PAGE__IMPORT_KEYRING_CERT_PAGE
|
|
|
|
|
|
//this is for finishing the OOB request
|
|
CNKMuxPg2 page_OOBmultiplexer(info);
|
|
CNKMuxPg FAKE8page_multiplexer(info);
|
|
NKAddPageToWizard(info, &page_OOBmultiplexer, &propsheet_mux );//index:11:WZ_PAGE__OOB_CERTMUX
|
|
NKAddPageToWizard(info, &getFileInfoPage, &propsheet_mux ); //index:12:WZ_PAGE__OOB_FINCERT_GETFILENM
|
|
NKAddPageToWizard(info, &FAKE8page_multiplexer, &propsheet_mux ); //index:13:WZ_PAGE__OOB_FINCERT_DISREGARD_OOB_REQ
|
|
// tompop: we dont need the FAKE8page_multiplexer and need to
|
|
// shift everything down
|
|
|
|
//this is for finishing the MODIFYEXISTING request
|
|
CNKMuxPg3 page_modifyExisting_multiplexer(info);
|
|
//--- this is part of the 3rd mux page for "ModifyExisting|Renew"
|
|
CNKChooseCA renewPage_Choose_CA(info);//reuse a pg from ncw
|
|
CNKUserInfo renewPage_User_Info(info);//reuse a pg from ncw
|
|
// note that the above page plays double duty as it senses
|
|
// that its used to renew a cert and displays a finish
|
|
// page behavior.
|
|
CNKFileInfo renewPage_File_Info(info);// page:WZ_PAGE__MODIFYEXISTING_CERTRENEW_OOB_INSTRUCTIONS
|
|
//--- this is part of 3rd mux page for "ModifyExisting|delete-cert"
|
|
CDeleteCert page_deleteCert(info);
|
|
//--- the last part of 3rd mux page, enables the new Cert Wizard!
|
|
// it does not have any pages
|
|
NKAddPageToWizard(info, &page_modifyExisting_multiplexer, &propsheet_mux ); //index:13:WZ_PAGE__MODIFYEXISTING_CERTMUX
|
|
NKAddPageToWizard(info, &renewPage_Choose_CA, &propsheet_mux ); //index:14:WZ_PAGE__MODIFYEXISTING_CERTRENEW_CA
|
|
NKAddPageToWizard(info, &renewPage_User_Info, &propsheet_mux ); //index:15:WZ_PAGE__MODIFYEXISTING_CERTRENEW_Contact
|
|
NKAddPageToWizard(info, &renewPage_File_Info, &propsheet_mux ); //index:15:WZ_PAGE__MODIFYEXISTING_CERTRENEW_Contact
|
|
NKAddPageToWizard(info, &page_deleteCert, &propsheet_mux ); //index:16:WZ_PAGE__MODIFYEXISTING_CERTDELETE
|
|
|
|
NKAddPageToWizard(info, &page_GenericCompletion, &propsheet_mux );//index:17:WZ_PAGE__GENERIC_COMPLETION_PAGE
|
|
NKAddPageToWizard(info, &page_GenericErrorPage , &propsheet_mux );//index:18:WZ_PAGE__GENERIC_ERROR_PAGE
|
|
|
|
|
|
// set the wizard property
|
|
propsheet_mux.SetWizardMode();
|
|
|
|
//before we start running the Wizard we need to enable exactly
|
|
//one of the possible SubWizards
|
|
pick_Correct_SubWizard(info);
|
|
|
|
// run the property sheet
|
|
#define HR_FAIL_SOME_EXCEPTION_HAPPENED 0x666 // very unlikely value
|
|
int i = HR_FAIL_SOME_EXCEPTION_HAPPENED;
|
|
|
|
|
|
if ( DebugTestEnv(DebugTestEnv_noTRY) ) {
|
|
|
|
|
|
#if defined(DEBUGGING) && defined(SHOW_TRACE_INFO)
|
|
Trace t( _T("propsheet_mux.DoModal() -- running the wizard"));
|
|
#endif
|
|
|
|
|
|
i = propsheet_mux.DoModal();
|
|
|
|
} else {
|
|
try {
|
|
|
|
#if defined(DEBUGGING) && defined(SHOW_TRACE_INFO)
|
|
Trace t( _T("propsheet_mux.DoModal() -- running the wizard"));
|
|
#endif
|
|
|
|
|
|
i = propsheet_mux.DoModal();
|
|
}
|
|
catch (...) {
|
|
if (i==HR_FAIL_SOME_EXCEPTION_HAPPENED) i=IDCANCEL;
|
|
|
|
//----------------------------------------------------
|
|
DoDebugBreak(); // see if we should do a debug break
|
|
|
|
MsgBox( IDS_CAUGHT_AN_UNKNOWN_EXCEPTION );
|
|
}
|
|
}
|
|
|
|
if ( i == IDCANCEL )
|
|
{
|
|
#if defined(DEBUGGING)
|
|
DODBG MsgBox( _T("Cert muxer dialog box works and returned IDCANCEL" ) );
|
|
#endif
|
|
|
|
} else if ( i != IDOK )
|
|
{
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
DODBG MsgBox( _T("Cert muxer dialog box works and returned OK" ) );
|
|
#endif
|
|
} else
|
|
{
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
TCHAR szTmp[200];
|
|
wsprintf(szTmp, _T("Cert muxer dialog box returned strange return value"
|
|
" of %d=%#x"), i, i );
|
|
DODBG MsgBox( szTmp );
|
|
#endif
|
|
}
|
|
//MsgBox( "DBG: done w/ Cert Wiz block");
|
|
|
|
|
|
// // if we want to do a completion page now... [HOWEVER THIS DOES NOT PRODUCE A GOOD LOOKING COMPL.PG]
|
|
// int dialogID = IDD_COMPLETION_PAGE;
|
|
//
|
|
// {
|
|
// CDialog dlg( dialogID );
|
|
// dlg.DoModal();
|
|
// }
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
// test the CryptUIWizBuildCTL functions...
|
|
|
|
//From CryptUI.h// //-----------------------------------------------------------------------
|
|
//From CryptUI.h// //
|
|
//From CryptUI.h// // CryptUIWizBuildCTL
|
|
//From CryptUI.h// //
|
|
//From CryptUI.h// // Build a new CTL or modify an existing CTL. The UI for wizard will
|
|
//From CryptUI.h// // always show in this case
|
|
//From CryptUI.h// //
|
|
//From CryptUI.h// //
|
|
//From CryptUI.h// // dwFlags: IN Optional: Can be set to the following:
|
|
//From CryptUI.h// // CRYPTUI_WIZ_BUILDCTL_SKIP_DESTINATION. This flag
|
|
//From CryptUI.h// // is valid only when pBuildCTLDest is set
|
|
//From CryptUI.h// // hwndParnet: IN Optional: The parent window handle
|
|
//From CryptUI.h// // pwszWizardTitle: IN Optional: The title of the wizard
|
|
//From CryptUI.h// // If NULL, the default will be IDS_BUILDCTL_WIZARD_TITLE
|
|
//From CryptUI.h// // pBuildCTLSrc: IN Optional: The source from which the CTL will be built
|
|
//From CryptUI.h// // pBuildCTLDest: IN Optional: The desination where the newly
|
|
//From CryptUI.h// // built CTL will be stored
|
|
//From CryptUI.h// // ppCTLContext: OUT Optaionl: The newly build CTL
|
|
//From CryptUI.h// //
|
|
//From CryptUI.h// //------------------------------------------------------------------------
|
|
//From CryptUI.h// BOOL
|
|
//From CryptUI.h// WINAPI
|
|
//From CryptUI.h// CryptUIWizBuildCTL(
|
|
//From CryptUI.h// IN DWORD dwFlags,
|
|
//From CryptUI.h// IN OPTIONAL HWND hwndParent,
|
|
//From CryptUI.h// IN OPTIONAL LPCWSTR pwszWizardTitle,
|
|
//From CryptUI.h// IN OPTIONAL PCCRYPTUI_WIZ_BUILDCTL_SRC_INFO pBuildCTLSrc,
|
|
//From CryptUI.h// IN OPTIONAL PCCRYPTUI_WIZ_BUILDCTL_DEST_INFO pBuildCTLDest,
|
|
//From CryptUI.h// OUT OPTIONAL PCCTL_CONTEXT *ppCTLContext
|
|
//From CryptUI.h// );
|
|
|
|
//From CryptUI.h//
|
|
//From CryptUI.h// Bugs:
|
|
//From CryptUI.h//
|
|
//From CryptUI.h// After calling LocalEnroll we get a HRESULT return value of
|
|
//From CryptUI.h// 0x80004005
|
|
//From CryptUI.h//
|
|
|
|
|
|
// when we finish the enrollment we should see that the Cert gets installed into:
|
|
//
|
|
// HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\Trust\CTLs
|
|
|
|
|
|
class CCertAuthCtrl;
|
|
|
|
|
|
|
|
#if defined(DEBUGGING)
|
|
void test__CEasyFileDialog()
|
|
{
|
|
|
|
CString msg(_T("This will test a Reading EASY CFileDialog"));
|
|
AfxMessageBox( msg );
|
|
|
|
CEasyFileDialog readDlg(CEasyFileDialog::Reading,
|
|
CEasyFileDialog::FileOpen,
|
|
IDS_KEY_OR_CERT_FILE_FILTER);
|
|
// The IDS_KEY_OR_CERT_FILE_FILTER string is defined as:
|
|
// "Certificate Import or backup file!*.cbk!KeyRing backup!*.krb!Any Certificate File Type!*.*!!"
|
|
// and provides a way to locate a keyRing/Cert file to import.
|
|
// Users could be ad-hock in how they named the files...
|
|
|
|
readDlg.SetTitle(_T("This is a test of the CEasyFileDialog:Reading"));
|
|
|
|
if (readDlg.DoModal() == IDOK) AfxMessageBox( readDlg.GetPathName() );
|
|
|
|
|
|
CEasyFileDialog writeDlg(CEasyFileDialog::Writing,
|
|
CEasyFileDialog::FileSaveAs,
|
|
_T("My C Files(*.cpp)!*.cpp!My H Files(*.h)!*.h!!"));
|
|
|
|
// ::MessageBox(NULL, "Body-- after we constructed the writeDlg: via msgBox", "title", MB_OK );
|
|
|
|
|
|
AfxMessageBox( _T("In this test we are calling 'writeDlg.DoModal()' and"
|
|
"have coded it so that it will grab the extension of the first"
|
|
"extension in the list of file specs, i.e. *.cpp so that if you"
|
|
"dont enter an extension it will use .cpp and append this for you"
|
|
"give it a try tjp tompop bug: for some reason the dialog is"
|
|
"not showing the \"Default Extension\" part of the FileDialog box"
|
|
"\n"
|
|
"Note also that the default ext is pretty brain dead in that if"
|
|
"the default extension is *.cpp and you enter a FileName of story.book"
|
|
"the Dialog will return a file name of \"story.book.cpp\"!!!"
|
|
"\n\n"
|
|
"Try picking a .cpp or swich the type and grab a *.h as we use"
|
|
"the following spec for the file types"
|
|
"\n"
|
|
"\t\"My C Files(*.cpp)!*.cpp!My H Files(*.h)!*.h!\""
|
|
"\n"
|
|
"Try entering: foo.h foo.cpp and foo.bak"
|
|
"\n"
|
|
"Try entering: foo.h foo.cpp and foo.bak"
|
|
"\n\n"
|
|
"Then try just foo once with the \"My H Files\" and"
|
|
"once w/ your C files and ALL SHOULD WORK."));
|
|
|
|
if (writeDlg.DoModal() == IDOK) AfxMessageBox( writeDlg.GetPathName() );
|
|
|
|
}
|
|
#endif //defined(DEBUGGING)
|
|
|
|
*/
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
void CCertAuthCtrl::SetServerInstance(LPCTSTR szServerInstance)
|
|
{
|
|
m_szServerInstance = szServerInstance;
|
|
//MsgBox( m_szServerInstance, _T("m_szServerInstance") );
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
void CCertAuthCtrl::SetMachineName(LPCTSTR szMachine)
|
|
{
|
|
m_szMachineName = szMachine;
|
|
//MsgBox( m_szMachineName, _T("m_szMachineName") );
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
void CCertAuthCtrl::OnFontChanged()
|
|
{
|
|
m_fUpdateFont = TRUE;
|
|
COleControl::OnFontChanged();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void CCertAuthCtrl::OnAmbientPropertyChange(DISPID dispid)
|
|
{
|
|
BOOL flag;
|
|
UINT style;
|
|
|
|
// do the right thing depending on the dispid
|
|
switch ( dispid )
|
|
{
|
|
case DISPID_AMBIENT_DISPLAYASDEFAULT:
|
|
if ( GetAmbientProperty( DISPID_AMBIENT_DISPLAYASDEFAULT, VT_BOOL, &flag ) )
|
|
{
|
|
style = GetWindowLong(
|
|
GetSafeHwnd(), // handle of window
|
|
GWL_STYLE // offset of value to retrieve
|
|
);
|
|
if ( flag )
|
|
style |= BS_DEFPUSHBUTTON;
|
|
else
|
|
style ^= BS_DEFPUSHBUTTON;
|
|
SetWindowLong(
|
|
GetSafeHwnd(), // handle of window
|
|
GWL_STYLE, // offset of value to retrieve
|
|
style
|
|
);
|
|
Invalidate(TRUE);
|
|
}
|
|
break;
|
|
};
|
|
|
|
COleControl::OnAmbientPropertyChange(dispid);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// an important method where we tell the container how to deal with us.
|
|
// pControlInfo is passed in by the container, although we are responsible
|
|
// for maintining the hAccel structure
|
|
void CCertAuthCtrl::OnGetControlInfo(LPCONTROLINFO pControlInfo)
|
|
{
|
|
// do a rudimentary check to see if we understand pControlInfo
|
|
if ( !pControlInfo || pControlInfo->cb < sizeof(CONTROLINFO) )
|
|
return;
|
|
|
|
// set the accelerator handle into place
|
|
pControlInfo->hAccel = m_hAccel;
|
|
pControlInfo->cAccel = m_cAccel;
|
|
|
|
// when we have focus, we do want the enter key
|
|
pControlInfo->dwFlags = CTRLINFO_EATS_RETURN;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// the ole control container object specifically filters out the space
|
|
// key so we do not get it as a OnMnemonic call. Thus we need to look
|
|
// for it ourselves
|
|
void CCertAuthCtrl::OnKeyUpEvent(USHORT nChar, USHORT nShiftState)
|
|
{
|
|
if ( nChar == _T(' ') )
|
|
{
|
|
OnClick((USHORT)GetDlgCtrlID());
|
|
}
|
|
COleControl::OnKeyUpEvent(nChar, nShiftState);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
void CCertAuthCtrl::OnMnemonic(LPMSG pMsg)
|
|
{
|
|
OnClick((USHORT)GetDlgCtrlID());
|
|
COleControl::OnMnemonic(pMsg);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
void CCertAuthCtrl::OnTextChanged()
|
|
{
|
|
DWORD i;
|
|
ACCEL accel;
|
|
BOOL f;
|
|
BOOL flag;
|
|
int iAccel;
|
|
|
|
// get the new text
|
|
CString sz = InternalGetText();
|
|
sz.MakeLower();
|
|
|
|
// if the handle has already been allocated, free it
|
|
if ( m_hAccel )
|
|
{
|
|
DestroyAcceleratorTable( m_hAccel );
|
|
m_hAccel = NULL;
|
|
m_cAccel = 0;
|
|
}
|
|
|
|
// if there is a & character, then declare the accelerator
|
|
iAccel = sz.Find(_T('&'));
|
|
if ( iAccel >= 0 )
|
|
{
|
|
// fill in the accererator record
|
|
accel.fVirt = FALT;
|
|
accel.key = sz.GetAt(iAccel + 1);
|
|
accel.cmd = (WORD)GetDlgCtrlID();
|
|
|
|
m_hAccel = CreateAcceleratorTable( &accel, 1 );
|
|
if ( m_hAccel )
|
|
m_cAccel = 1;
|
|
}
|
|
|
|
// finish with the default handling.
|
|
COleControl::OnTextChanged();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// FYI ONLY //
|
|
// This RunAuthoritiesDialog() is Boydm's old starting point for the //
|
|
// original AuthoritiesDialog. //
|
|
// //
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// //---------------------------------------------------------------------------
|
|
// // run the dialog
|
|
// void CCertAuthCtrl::RunAuthoritiesDialog()
|
|
// {
|
|
// //DebugBreak();
|
|
// // prepare the machine name pointer
|
|
// OLECHAR* poch = NULL;
|
|
// if ( !m_szMachineName.IsEmpty() )
|
|
// {
|
|
// // allocate the name buffer
|
|
// poch = (OLECHAR*)GlobalAlloc( GPTR, MAX_COMPUTERNAME_LENGTH * 2 );
|
|
//
|
|
// // unicodize the name into the buffer
|
|
// if ( poch )
|
|
// MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, m_szMachineName, -1,
|
|
// poch, MAX_COMPUTERNAME_LENGTH * 2 );
|
|
// }
|
|
//
|
|
// // initialize the metabase wrappings - pass in the name of the target machine
|
|
// // if one has been specified
|
|
// IMSAdminBase* pMB = FInitMetabaseWrapper( poch );
|
|
// if ( !pMB )
|
|
// {
|
|
// MessageBeep(0);
|
|
// if ( poch ) GlobalFree( poch );
|
|
// return;
|
|
// }
|
|
//
|
|
// // clean up
|
|
// if ( poch )
|
|
// {
|
|
// GlobalFree( poch );
|
|
// poch = NULL;
|
|
// }
|
|
//
|
|
// // if there is nothing in the MB_Path, default to the first instance
|
|
// if ( m_szServerInstance.IsEmpty() )
|
|
// m_szServerInstance = "/LM/W3SVC/1";
|
|
//
|
|
// // I am assuming that the last character is NOT a '/' Thus, if that is what is
|
|
// // there, we need to remove it. Otherwise, the path gets messed up later
|
|
// if ( m_szServerInstance.Right(1) == '/' )
|
|
// m_szServerInstance = m_szServerInstance.Left( m_szServerInstance.GetLength()-1 );
|
|
//
|
|
// // set up the dialog
|
|
// CSelectIssuersDlg dlg(pMB);
|
|
// dlg.m_pRule = NULL;
|
|
// dlg.m_szMBPath = m_szServerInstance + SZ_NAMESPACE_EXTENTION;
|
|
//
|
|
// // Use our Fancy Debugging "Load String" found in Easy::CString
|
|
// Easy::CString::LoadStringInto(dlg.m_sz_caption, IDS_TRUSTED_AUTHORITIES );
|
|
//
|
|
// // run the propdsheet dialog
|
|
// // let the host container know that we are putting up a modal dialog
|
|
// PreModalDialog();
|
|
// // run the dialog
|
|
// dlg.DoModal();
|
|
// // let the host container know we are done with the modality
|
|
// PostModalDialog();
|
|
//
|
|
// // close the metabase wrappings
|
|
// FCloseMetabaseWrapper(pMB);
|
|
// }
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------
|
|
// DoClick - process a click event. This will be called by the IIS admin
|
|
// API to launch us! [people get here by clicking on our control]
|
|
//
|
|
// PARAMS: dwButtonNumber an IN param. tells what "logical button" fired our control:
|
|
//
|
|
// value
|
|
// ------
|
|
// 1 Call CertWiz
|
|
// 2 Call CTL Wiz
|
|
//-------------------------------------------------------------------------------
|
|
void CCertAuthCtrl::DoClick(IN long dwButtonNumber)
|
|
{
|
|
// TODO: Add your dispatch handler code here
|
|
|
|
|
|
OnClick( (short) dwButtonNumber );
|
|
}
|
|
|
|
|