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.
645 lines
19 KiB
645 lines
19 KiB
/*++
|
|
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
tssec.hxx
|
|
|
|
Abstract:
|
|
This file declares security related classes and functions
|
|
|
|
Author:
|
|
|
|
Murali R. Krishnan ( MuraliK ) 11-Oct-1995
|
|
|
|
Environment:
|
|
|
|
Win32 User Mode
|
|
|
|
Project:
|
|
|
|
Internet Services Common DLL
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
# ifndef _TSSEC_HXX_
|
|
# define _TSSEC_HXX_
|
|
|
|
/************************************************************
|
|
* Include Headers
|
|
************************************************************/
|
|
|
|
# define SECURITY_WIN32
|
|
#include <sspi.h> // Security Support Provider APIs
|
|
|
|
#include <schnlsp.h>
|
|
|
|
#include <pudebug.h>
|
|
|
|
// forward declaration
|
|
class IIS_SERVER_INSTANCE;
|
|
class IIS_SSL_INFO;
|
|
|
|
/************************************************************
|
|
* Type Definitions
|
|
************************************************************/
|
|
|
|
typedef BOOL (WINAPI *SECURITY_CONTEXT_DELETE_FUNCTION)( CtxtHandle*, PVOID );
|
|
|
|
//
|
|
// Globals
|
|
//
|
|
|
|
extern HANDLE g_hProcessImpersonationToken;
|
|
extern HANDLE g_hProcessPrimaryToken;
|
|
extern BOOL g_fUseSingleToken;
|
|
|
|
//
|
|
// The TCP_AUTHENT_INFO structure is used as a shorthand to convey
|
|
// a bunch of authentication related information to a few routines that
|
|
// need it.
|
|
//
|
|
class TCP_AUTHENT_INFO
|
|
{
|
|
public:
|
|
TCP_AUTHENT_INFO( VOID )
|
|
: fDontUseAnonSubAuth( TRUE ),
|
|
dwLogonMethod ( LOGON32_LOGON_INTERACTIVE ),
|
|
cbAnonAcctDesc ( 0 ),
|
|
fPwdIsHashed ( FALSE )
|
|
{
|
|
}
|
|
|
|
TCP_AUTHENT_INFO( TCP_AUTHENT_INFO *pTAI )
|
|
: strAnonUserName ( pTAI->strAnonUserName),
|
|
strAnonUserPassword ( pTAI->strAnonUserPassword),
|
|
strDefaultLogonDomain ( pTAI->strDefaultLogonDomain),
|
|
dwLogonMethod ( pTAI->dwLogonMethod),
|
|
fDontUseAnonSubAuth ( pTAI->fDontUseAnonSubAuth),
|
|
fPwdIsHashed ( pTAI->fPwdIsHashed),
|
|
cbAnonAcctDesc ( pTAI->cbAnonAcctDesc),
|
|
bAnonAcctDesc ( pTAI->bAnonAcctDesc.QuerySize())
|
|
{
|
|
if (bAnonAcctDesc.QuerySize() >= pTAI->bAnonAcctDesc.QuerySize())
|
|
{
|
|
CopyMemory( bAnonAcctDesc.QueryPtr(),
|
|
pTAI->bAnonAcctDesc.QueryPtr(),
|
|
pTAI->bAnonAcctDesc.QuerySize());
|
|
}
|
|
}
|
|
|
|
STR strAnonUserName;
|
|
STR strAnonUserPassword;
|
|
STR strDefaultLogonDomain;
|
|
DWORD dwLogonMethod;
|
|
BOOL fDontUseAnonSubAuth;
|
|
BOOL fPwdIsHashed;
|
|
|
|
//
|
|
// Stores the anonymous account descriptor
|
|
//
|
|
|
|
BUFFER bAnonAcctDesc;
|
|
DWORD cbAnonAcctDesc;
|
|
};
|
|
|
|
typedef TCP_AUTHENT_INFO * PTCP_AUTHENT_INFO;
|
|
|
|
|
|
//
|
|
// The USER_AUTHENT_INFO holds authentication info for a domain user
|
|
//
|
|
class USER_AUTHENT_INFO
|
|
{
|
|
public:
|
|
StatStr<UNLEN+1> strName;
|
|
StatStr<DNLEN+1> strDomain;
|
|
StatStr<PWLEN+1> strPassword;
|
|
};
|
|
|
|
typedef USER_AUTHENT_INFO * PUSER_AUTHENT_INFO;
|
|
|
|
//
|
|
// Security functions.
|
|
//
|
|
|
|
#define IIS_DNLEN 256
|
|
#define MAX_ACCT_DESC_LEN (UNLEN+1+IIS_DNLEN+1+PWLEN+1)
|
|
|
|
class CACHED_CREDENTIAL
|
|
{
|
|
public:
|
|
CACHED_CREDENTIAL()
|
|
{
|
|
_ListEntry.Flink = NULL;
|
|
_fHaveCredHandle = FALSE;
|
|
}
|
|
~CACHED_CREDENTIAL();
|
|
BOOL
|
|
static CACHED_CREDENTIAL::GetCredential(
|
|
LPSTR pszPackage,
|
|
PIIS_SERVER_INSTANCE psi,
|
|
PTCP_AUTHENT_INFO pTAI,
|
|
CredHandle* prcred,
|
|
ULONG* pcbMaxToken
|
|
);
|
|
LIST_ENTRY _ListEntry;
|
|
|
|
private:
|
|
STR _PackageName;
|
|
STR _DefaultDomain;
|
|
CredHandle _hcred;
|
|
BOOL _fHaveCredHandle;
|
|
ULONG _cbMaxToken; // Used for SSP, max message token size
|
|
} ;
|
|
|
|
class CACHED_TOKEN
|
|
{
|
|
public:
|
|
CACHED_TOKEN( VOID )
|
|
: _hToken( NULL ),
|
|
_cRef ( 1 ),
|
|
_TTL ( 2 ),
|
|
m_hImpersonationToken( NULL ),
|
|
m_fGuest ( FALSE ),
|
|
m_dwLogonMethod ( 0 )
|
|
{
|
|
_ListEntry.Flink = NULL;
|
|
_liExpiry.HighPart = 0x7fffffff;
|
|
_liExpiry.LowPart = 0xffffffff;
|
|
}
|
|
|
|
~CACHED_TOKEN( VOID )
|
|
{
|
|
if ( !g_fUseSingleToken )
|
|
{
|
|
DBG_ASSERT( _ListEntry.Flink == NULL );
|
|
|
|
if ( m_hImpersonationToken) {
|
|
|
|
DBG_REQUIRE( CloseHandle( m_hImpersonationToken ));
|
|
m_hImpersonationToken = NULL;
|
|
}
|
|
|
|
if ( _hToken )
|
|
{
|
|
DBG_REQUIRE( CloseHandle( _hToken ) );
|
|
_hToken = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static VOID Reference( CACHED_TOKEN * pct )
|
|
{
|
|
DBG_ASSERT( pct->_cRef > 0 );
|
|
InterlockedIncrement( &pct->_cRef );
|
|
}
|
|
|
|
static VOID Dereference( CACHED_TOKEN * pct )
|
|
{
|
|
DBG_ASSERT( pct->_cRef > 0 );
|
|
|
|
if ( !InterlockedDecrement( &pct->_cRef ) )
|
|
{
|
|
delete pct;
|
|
}
|
|
}
|
|
|
|
HANDLE QueryImpersonationToken(VOID) const
|
|
{ return g_fUseSingleToken ? g_hProcessImpersonationToken : m_hImpersonationToken; }
|
|
|
|
HANDLE QueryPrimaryToken(VOID) const
|
|
{ return g_fUseSingleToken ? g_hProcessImpersonationToken : m_hImpersonationToken; }
|
|
|
|
VOID SetImpersonationToken(IN HANDLE hImpersonation)
|
|
{
|
|
DBG_ASSERT( m_hImpersonationToken == NULL);
|
|
if ( g_fUseSingleToken )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
}
|
|
else
|
|
{
|
|
m_hImpersonationToken = hImpersonation;
|
|
}
|
|
}
|
|
|
|
VOID SetExpiry( LARGE_INTEGER* pE )
|
|
{
|
|
if ( g_fUseSingleToken )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
}
|
|
|
|
if ( NULL != pE )
|
|
{
|
|
memcpy( &_liExpiry, pE, sizeof(LARGE_INTEGER) );
|
|
}
|
|
else
|
|
{
|
|
_liExpiry.HighPart = 0x7fffffff;
|
|
_liExpiry.LowPart = 0xffffffff;
|
|
}
|
|
}
|
|
|
|
LARGE_INTEGER* QueryExpiry() { return &_liExpiry; }
|
|
|
|
BOOL IsGuest(VOID) const { return (m_fGuest); }
|
|
VOID SetGuest(IN BOOL fGuest) { m_fGuest = fGuest; }
|
|
|
|
HANDLE _hToken; // Must be first data member
|
|
LIST_ENTRY _ListEntry;
|
|
LONG _cRef;
|
|
DWORD _TTL; // Gets decremented on each timeout, when zero,
|
|
// remove this item from the cache
|
|
BOOL m_fGuest; // Is this token a guest user?
|
|
HANDLE m_hImpersonationToken;
|
|
|
|
CHAR _achAcctDesc[MAX_ACCT_DESC_LEN];
|
|
DWORD m_dwAcctDescLen;
|
|
LARGE_INTEGER _liExpiry;
|
|
|
|
DWORD m_dwLogonMethod;
|
|
CHAR m_achUserName[ UNLEN ];
|
|
CHAR m_achDomainName[ IIS_DNLEN ];
|
|
};
|
|
|
|
typedef CACHED_TOKEN* TS_TOKEN; // Choose an incompatible type so warnings
|
|
// are produced
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// NT Authentication support
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// TCP Authenticator flags passed to init
|
|
//
|
|
|
|
#define TCPAUTH_SERVER 0x00000001 // This is the server side
|
|
#define TCPAUTH_CLIENT 0x00000002 // This is the client side
|
|
#define TCPAUTH_UUENCODE 0x00000004 // Input buffers are uudecoded,
|
|
// output buffers are uuencoded
|
|
#define TCPAUTH_BASE64 0x00000008 // uses base64 for uuenc/dec
|
|
|
|
#define CRED_STATUS_INVALID_TIME 0x00001000
|
|
#define CRED_STATUS_REVOKED 0x00002000
|
|
|
|
class TCP_AUTHENT
|
|
{
|
|
public:
|
|
|
|
dllexp TCP_AUTHENT( DWORD AuthFlags );
|
|
dllexp ~TCP_AUTHENT();
|
|
|
|
//
|
|
// Server side only: For clients that pass clear text, the server should
|
|
// authenticate with this method
|
|
//
|
|
|
|
dllexp BOOL ClearTextLogon( CHAR * pszUser,
|
|
CHAR * pszPassword,
|
|
BOOL * pfAsGuest,
|
|
BOOL * pfAsAnonymous,
|
|
IIS_SERVER_INSTANCE * pInstance,
|
|
PTCP_AUTHENT_INFO pTAI,
|
|
CHAR * pszWorkstation = NULL
|
|
);
|
|
|
|
#if 0
|
|
//
|
|
// Server side only : Digest logon
|
|
//
|
|
|
|
dllexp BOOL LogonDigestUser(
|
|
PSTR pszUserName,
|
|
PSTR pszRealm,
|
|
PSTR pszUri,
|
|
PSTR pszMethod,
|
|
PSTR pszNonce,
|
|
PSTR pszServerNonce,
|
|
PSTR pszDigest,
|
|
DWORD dwAlgo,
|
|
LPTSVC_INFO psi
|
|
);
|
|
#endif
|
|
|
|
//
|
|
// Server side only: For filters that set access tokens
|
|
//
|
|
|
|
dllexp BOOL SetAccessToken( HANDLE hPrimaryToken,
|
|
HANDLE hImpersonationToken
|
|
);
|
|
|
|
//
|
|
// Client calls this first to get the negotiation message which
|
|
// it then sends to the server. The server calls this with the
|
|
// client result and sends back the result. The conversation
|
|
// continues until *pcbBuffOut is zero and *pfNeedMoreData is FALSE.
|
|
//
|
|
// On the first call, pszPackage must point to the zero terminated
|
|
// authentication package name to be used and pszUser and pszPassword
|
|
// should point to the user name and password to authenticated with
|
|
// on the client side (server side will always be NULL).
|
|
//
|
|
|
|
dllexp BOOL Converse( VOID * pBuffIn,
|
|
DWORD cbBuffIn,
|
|
BUFFER * pbuffOut,
|
|
DWORD * pcbBuffOut,
|
|
BOOL * pfNeedMoreData,
|
|
PTCP_AUTHENT_INFO pTAI,
|
|
CHAR * pszPackage = NULL,
|
|
CHAR * pszUser = NULL,
|
|
CHAR * pszPassword = NULL,
|
|
PIIS_SERVER_INSTANCE psi = NULL );
|
|
|
|
dllexp BOOL TCP_AUTHENT::ConverseEx(
|
|
SecBufferDesc* pInSecBufDesc, // passed in by caller
|
|
BUFFER * pDecodedBuffer, // passed in by caller
|
|
BUFFER * pbuffOut,
|
|
DWORD * pcbBuffOut,
|
|
BOOL * pfNeedMoreData,
|
|
PTCP_AUTHENT_INFO pTAI,
|
|
CHAR * pszPackage,
|
|
CHAR * pszUser,
|
|
CHAR * pszPassword,
|
|
PIIS_SERVER_INSTANCE psi
|
|
);
|
|
|
|
//
|
|
// Server side only. Impersonates client after successful authentication
|
|
//
|
|
|
|
dllexp BOOL Impersonate( VOID );
|
|
dllexp BOOL RevertToSelf( VOID );
|
|
dllexp BOOL IsForwardable( VOID ) const;
|
|
|
|
dllexp BOOL StartProcessAsUser( LPCSTR lpApplicationName,
|
|
LPSTR lpCommandLine,
|
|
BOOL bInheritHandles,
|
|
DWORD dwCreationFlags,
|
|
LPVOID lpEnvironment,
|
|
LPCSTR lpCurrentDirectory,
|
|
LPSTARTUPINFOA lpStartupInfo,
|
|
LPPROCESS_INFORMATION lpProcessInformation
|
|
);
|
|
|
|
//
|
|
// Gives the name of all authentication packages in a double null
|
|
// terminated list. i.e.:
|
|
//
|
|
// NTLM\0
|
|
// MSKerberos\0
|
|
// \0
|
|
//
|
|
|
|
dllexp BOOL EnumAuthPackages( BUFFER * pBuff );
|
|
|
|
//
|
|
// Returns the user name associated with this context, not supported for
|
|
// clear text
|
|
//
|
|
|
|
dllexp BOOL QueryUserName( STR * pBuff, BOOL fImpersonated = FALSE );
|
|
dllexp BOOL QueryExpiry( PTimeStamp pExpiry );
|
|
|
|
dllexp TS_TOKEN GetToken( VOID ) const
|
|
{ return _hToken; }
|
|
|
|
//
|
|
// Gets actual impersonation token handle
|
|
//
|
|
|
|
dllexp HANDLE QueryPrimaryToken( VOID );
|
|
dllexp HANDLE QueryImpersonationToken( VOID );
|
|
|
|
dllexp HANDLE GetUserHandle( VOID )
|
|
{ return QueryPrimaryToken(); }
|
|
|
|
dllexp BOOL QueryFullyQualifiedUserName(
|
|
LPSTR pszUser,
|
|
STR * strU,
|
|
IIS_SERVER_INSTANCE * psi,
|
|
PTCP_AUTHENT_INFO pTAI
|
|
);
|
|
|
|
dllexp BOOL IsGuest( BOOL );
|
|
|
|
dllexp BOOL Reset( BOOL fSessionReset = TRUE );
|
|
|
|
dllexp CredHandle * QueryCredHandle( VOID )
|
|
{ return (_fHaveCredHandle ? &_hcred : NULL); }
|
|
|
|
dllexp CtxtHandle * QueryCtxtHandle( VOID )
|
|
{ return (_fHaveCtxtHandle ? &_hctxt : NULL); }
|
|
|
|
dllexp CtxtHandle * QuerySslCtxtHandle( VOID )
|
|
{ return _phSslCtxt; }
|
|
|
|
dllexp BOOL SetSecurityContextToken( CtxtHandle* pCtxt,
|
|
HANDLE hImpersonationToken,
|
|
SECURITY_CONTEXT_DELETE_FUNCTION pFn,
|
|
PVOID pArg,
|
|
IIS_SSL_INFO *pSslInfo );
|
|
|
|
dllexp BOOL IsSslCertPresent();
|
|
|
|
dllexp BOOL DeleteCachedTokenOnReset( VOID );
|
|
|
|
dllexp BOOL QueryCertificateIssuer( LPSTR ppIssuer, DWORD, LPBOOL );
|
|
dllexp BOOL QueryCertificateSubject( LPSTR ppSubject, DWORD, LPBOOL );
|
|
dllexp BOOL QueryCertificateFlags( LPDWORD pdwFlags, LPBOOL );
|
|
dllexp BOOL QueryCertificateSerialNumber( LPBYTE* pSerialNumber, LPDWORD pdwLen, LPBOOL );
|
|
|
|
dllexp BOOL QueryServerCertificateIssuer( LPSTR* ppIssuer, LPBOOL );
|
|
dllexp BOOL QueryServerCertificateSubject( LPSTR* ppSubject, LPBOOL );
|
|
dllexp BOOL QueryEncryptionKeySize( LPDWORD, LPBOOL );
|
|
dllexp BOOL QueryEncryptionServerPrivateKeySize( LPDWORD, LPBOOL );
|
|
|
|
dllexp BOOL
|
|
GetClientCertBlob(
|
|
IN DWORD cbAllocated,
|
|
OUT DWORD * pdwCertEncodingType,
|
|
OUT unsigned char * pbCertEncoded,
|
|
OUT DWORD * pcbCertEncoded,
|
|
OUT DWORD * pfCertificateVerified);
|
|
|
|
dllexp BOOL UpdateClientCertFlags( DWORD dwFlags, LPBOOL pfCert, LPBYTE pbCa, DWORD dwCa );
|
|
|
|
dllexp BOOL PackageSupportsEncoding( LPSTR pszPackage );
|
|
|
|
dllexp BOOL SetTargetName( LPSTR pszTarget );
|
|
|
|
private:
|
|
BOOL QueryCertificateInfo( LPBOOL );
|
|
BOOL QueryServerCertificateInfo( LPBOOL );
|
|
|
|
protected:
|
|
DWORD _fClient:1; // TRUE if client side, FALSE if SERVER side
|
|
DWORD _fNewConversation:1; // Forces initialization params for client side
|
|
DWORD _fUUEncodeData:1; // uuencode/decode input and output buffers
|
|
DWORD _fClearText:1; // Use the Gina APIs rather then the SSP APIs
|
|
DWORD _fHaveCredHandle:1; // _hcred contains a credential handle
|
|
DWORD _fHaveCtxtHandle:1; // _hctxt contains a context handle
|
|
DWORD _fBase64:1; // uses base64 for uuenc/dec
|
|
DWORD _fKnownToBeGuest:1; // TRUE if SSPI flag access token as "Guest"
|
|
DWORD _fHaveAccessTokens:1;// TRUE if access token set by caller
|
|
DWORD _fHaveExpiry:1; // TRUE if clear text logon has pwd expiry time
|
|
DWORD _fDelegate:1; // TRUE if security context forwardable
|
|
DWORD _fCertCheckForRevocation:1; // TRUE if we should revocation check
|
|
DWORD _fCertCheckCacheOnly:1; // TRUE if we should not go on wire
|
|
TS_TOKEN _hToken; // Used for clear text
|
|
CredHandle _hcred; // Used for SSP
|
|
ULONG _cbMaxToken; // Used for SSP, max message token size
|
|
HANDLE _hSSPToken; // Used for SSP, caches real token
|
|
HANDLE _hSSPPrimaryToken;// Used for SSP, caches duplicated token
|
|
CtxtHandle _hctxt; // Used for SSP
|
|
|
|
SECURITY_CONTEXT_DELETE_FUNCTION _pDeleteFunction;
|
|
PVOID _pDeleteArg;
|
|
LARGE_INTEGER _liPwdExpiry;
|
|
|
|
PCERT_CONTEXT _pClientCertContext;
|
|
DWORD _dwX509Flags;
|
|
IIS_SSL_INFO * _pSslInfo;
|
|
PX509Certificate _pServerX509Certificate;
|
|
DWORD _dwServerX509Flags;
|
|
DWORD _dwServerBitsInKey;
|
|
CtxtHandle * _phSslCtxt; // ptr to SSL sec context
|
|
STR _strTarget;
|
|
};
|
|
|
|
|
|
|
|
DWORD
|
|
InitializeSecurity(
|
|
HINSTANCE hDll
|
|
);
|
|
|
|
VOID
|
|
TerminateSecurity(
|
|
VOID
|
|
);
|
|
|
|
|
|
dllexp
|
|
BOOL
|
|
TsImpersonateUser(
|
|
TS_TOKEN hToken
|
|
);
|
|
|
|
dllexp
|
|
HANDLE
|
|
TsTokenToHandle(
|
|
TS_TOKEN hToken
|
|
);
|
|
|
|
dllexp
|
|
HANDLE
|
|
TsTokenToImpHandle(
|
|
TS_TOKEN hToken
|
|
);
|
|
|
|
dllexp
|
|
DWORD
|
|
TsApiAccessCheck(
|
|
ACCESS_MASK maskDesiredAccess
|
|
);
|
|
|
|
|
|
dllexp
|
|
BOOL
|
|
TsDeleteUserToken(
|
|
TS_TOKEN hToken
|
|
);
|
|
|
|
dllexp
|
|
TS_TOKEN
|
|
TsLogonUser(
|
|
CHAR * pszUser,
|
|
CHAR * pszPassword,
|
|
BOOL * pfAsGuest,
|
|
BOOL * pfAsAnonymous,
|
|
IIS_SERVER_INSTANCE * pInstance,
|
|
PTCP_AUTHENT_INFO pTAI,
|
|
CHAR * pszWorkstation = NULL,
|
|
LARGE_INTEGER * pExpiry = NULL,
|
|
BOOL * pfExpiry = NULL
|
|
);
|
|
|
|
dllexp
|
|
BOOL
|
|
TsGetSecretW(
|
|
WCHAR * pszSecretName,
|
|
BUFFER * pbufSecret
|
|
);
|
|
|
|
dllexp
|
|
DWORD
|
|
TsSetSecretW(
|
|
IN LPWSTR SecretName,
|
|
IN LPWSTR pSecret,
|
|
IN DWORD cbSecret
|
|
);
|
|
|
|
#ifdef CHICAGO
|
|
dllexp
|
|
BOOL
|
|
TsIsUserLevelPresent(VOID);
|
|
#endif
|
|
|
|
|
|
dllexp
|
|
BOOL
|
|
uudecode(
|
|
char * bufcoded,
|
|
BUFFER * pbuffdecoded,
|
|
DWORD * pcbDecoded = NULL,
|
|
BOOL fBase64 = FALSE
|
|
);
|
|
|
|
dllexp
|
|
BOOL
|
|
uuencode(
|
|
BYTE * pchData,
|
|
DWORD cbData,
|
|
BUFFER * pbuffEncoded,
|
|
BOOL fBase64 = FALSE
|
|
);
|
|
|
|
dllexp
|
|
BOOL
|
|
BuildAnonymousAcctDesc(
|
|
PTCP_AUTHENT_INFO pTAI
|
|
);
|
|
|
|
dllexp
|
|
QuerySingleAccessToken(
|
|
VOID
|
|
);
|
|
|
|
extern TS_TOKEN g_pctProcessToken;
|
|
|
|
#include <tslogon.hxx>
|
|
|
|
#include <wintrust.h>
|
|
typedef
|
|
LONG (WINAPI *PFN_WinVerifyTrust)(IN OPTIONAL HWND hwnd,
|
|
IN GUID *pgActionID,
|
|
IN LPVOID pWintrustData);
|
|
|
|
extern HINSTANCE g_hWinTrust;
|
|
extern PFN_WinVerifyTrust g_pfnWinVerifyTrust;
|
|
|
|
|
|
#endif
|
|
|
|
/************************ End of File ***********************/
|
|
|
|
|