diff options
Diffstat (limited to 'private/net/svcdlls/ntlmssp/server/api.c')
-rw-r--r-- | private/net/svcdlls/ntlmssp/server/api.c | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/private/net/svcdlls/ntlmssp/server/api.c b/private/net/svcdlls/ntlmssp/server/api.c new file mode 100644 index 000000000..c58706dde --- /dev/null +++ b/private/net/svcdlls/ntlmssp/server/api.c @@ -0,0 +1,859 @@ +/*++ + +Copyright (c) 1993 Microsoft Corporation + +Module Name: + + api.c + +Abstract: + + NtLmSsp Service API dispatch routines. + +Author: + + Cliff Van Dyke (CliffV) 26-Jun-1993 + +Revision History: + +--*/ + + +// +// Common include files. +// + +#include <ntlmssps.h> // Include files common to server side of service + +// +// Include files specific to this .c file +// + +SECURITY_STATUS +SspApiAcquireCredentialHandle( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + +Routine Description: + + + This API allows applications to acquire a handle to pre-existing + credentials associated with the user on whose behalf the call is made + i.e. under the identity this application is running. These pre-existing + credentials have been established through a system logon not described + here. Note that this is different from "login to the network" and does + not imply gathering of credentials. + + + This API returns a handle to the credentials of a principal (user, client) + as used by a specific security package. This handle can then be used + in subsequent calls to the Context APIs. This API will not let a + process obtain a handle to credentials that are not related to the + process; i.e. we won't allow a process to grab the credentials of + another user logged into the same machine. There is no way for us + to determine if a process is a trojan horse or not, if it is executed + by the user. + +Arguments: + + ClientConnection - Describes the client process. + + Message - Message from the caller. Returns the response to be passed to + the caller. The message contains all of the following fields: + + + PrincipalName - Name of the principal for whose credentials the handle + will reference. Note, if the process requesting the handle does + not have access to the credentials, an error will be returned. + A null string indicates that the process wants a handle to the + credentials of the user under whose security it is executing. + + PackageName - Name of the package with which these credentials will + be used. + + CredentialUseFlags - Flags indicating the way with which these + credentials will be used. + + #define CRED_INBOUND 0x00000001 + #define CRED_OUTBOUND 0x00000002 + #define CRED_BOTH 0x00000003 + + The credentials created with CRED_INBOUND option can only be used + for (validating incoming calls and can not be used for making accesses. + + LogonId - Pointer to NT style Logon Id which is a LUID. (Provided for + file system ; processes such as network redirectors.) + + CredentialHandle - Returned credential handle. + + Lifetime - Time that these credentials expire. The value returned in + this field depends on the security package. + +Return Value: + + STATUS_SUCCESS -- Call completed successfully + + SEC_E_INSUFFICIENT_MEMORY -- Not enough memory + SEC_E_PRINCIPAL_UNKNOWN -- No such principal + SEC_E_NOT_OWNER -- caller does not own the specified credentials + +--*/ + +{ + SECURITY_STATUS SecStatus; + PSSP_ACQUIRE_CREDENTIAL_HANDLE_ARGS Args; + HANDLE ClientTokenHandle = NULL; + LUID LogonId; + + // + // Simply call the worker routine. + // + + Args = &Message->Arguments.AcquireCredentialHandleArgs; + + + SecStatus = SspLpcGetLogonId( + ClientConnection, + Message, + &LogonId, + &ClientTokenHandle + ); + + if ( !NT_SUCCESS(SecStatus) ) { + SspPrint(( SSP_API, + "SspApiAcquireCredentialHandle: " + "GetLogonId returns 0x%lx\n", + SecStatus )); + } else { + + SecStatus = SsprAcquireCredentialHandle( + ClientConnection, + &ClientTokenHandle, + &LogonId, + Args->CredentialUseFlags, + &Args->CredentialHandle, + &Args->Lifetime, + Args->DomainName, + Args->DomainNameSize, + Args->UserName, + Args->UserNameSize, + Args->Password, + Args->PasswordSize ); + } + + if ( ClientTokenHandle != NULL) { + NtClose(ClientTokenHandle); + } + return SecStatus; + +} + +SECURITY_STATUS +SspApiFreeCredentialHandle( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + + + This API is used to notify the security system that the credentials are + no longer needed and allows the application to free the handle acquired + in the call described above. When all references to this credential + set has been removed then the credentials may themselves be removed. + +Arguments: + + ClientConnection - Describes the client process. + + Message - Message from the caller. Returns the response to be passed to + the caller. The message contains all of the following fields: + + CredentialHandle - Credential Handle obtained through + AcquireCredentialHandle. + +Return Value: + + STATUS_SUCCESS -- Call completed successfully + + SEC_E_INVALID_HANDLE -- Credential Handle is invalid + +--*/ + +{ + SECURITY_STATUS SecStatus; + PSSP_FREE_CREDENTIAL_HANDLE_ARGS Args; + + // + // Simply call the worker routine. + // + + Args = &Message->Arguments.FreeCredentialHandleArgs; + + + SecStatus = SsprFreeCredentialHandle( + ClientConnection, + &Args->CredentialHandle ); + + return SecStatus; +} + + +SECURITY_STATUS +SspApiInitializeSecurityContext( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + +Routine Description: + + This routine initiates the outbound security context from a credential + handle. This results in the establishment of a security context + between the application and a remote peer. The routine returns a token + which must be passed to the remote peer which in turn submits it to the + local security implementation via the AcceptSecurityContext() call. + The token generated should be considered opaque by all callers. + + This function is used by a client to initialize an outbound context. + For a two leg security package, the calling sequence is as follows: The + client calls the function with OldContextHandle set to NULL and + InputToken set either to NULL or to a pointer to a security package + specific data structure. The package returns a context handle in + NewContextHandle and a token in OutputToken. The handle can then be + used for message APIs if desired. + + The OutputToken returned here is sent across to target server which + calls AcceptSecuirtyContext() with this token as an input argument and + may receive a token which is returned to the initiator so it can call + InitializeSecurityContext() again. + + For a three leg (mutual authentication) security package, the calling + sequence is as follows: The client calls the function as above, but the + package will return SEC_I_CALLBACK_NEEDED. The client then sends the + output token to the server and waits for the server's reply. Upon + receipt of the server's response, the client calls this function again, + with OldContextHandle set to the handle that was returned from the + first call. The token received from the server is supplied in the + InputToken parameter. If the server has successfully responded, then + the package will respond with success, or it will invalidate the + context. + + Initialization of security context may require more than one call to + this function depending upon the underlying authentication mechanism as + well as the "choices" indicated via ContextReqFlags. The + ContextReqFlags and ContextAttributes are bit masks representing + various context level functions viz. delegation, mutual + authentication, confidentiality, replay detection and sequence + detection. + + When ISC_REQ_PROMPT_FOR_CREDS flag is set the security package always + prompts the user for credentials, irrespective of whether credentials + are present or not. If user indicated that the supplied credentials be + used then they will be stashed (overwriting existing ones if any) for + future use. The security packages will always prompt for credentials + if none existed, this optimizes for the most common case before a + credentials database is built. But the security packages can be + configured to not do that. Security packages will ensure that they + only prompt to the interactive user, for other logon sessions, this + flag is ignored. + + When ISC_REQ_USE_SUPPLIED_CREDS flag is set the security package always + uses the credentials supplied in the InitializeSecurityContext() call + via InputToken parameter. If the package does not have any credentials + available it will prompt for them and record it as indicated above. + + It is an error to set both these flags simultaneously. + + If the ISC_REQ_ALLOCATE_MEMORY was specified then the caller must free + the memory pointed to by OutputToken by calling FreeContextBuffer(). + + For example, the InputToken may be the challenge from a LAN Manager or + NT file server. In this case, the OutputToken would be the NTLM + encrypted response to the challenge. The caller of this API can then + take the appropriate response (case-sensitive v. case-insensitive) and + return it to the server for an authenticated connection. + + + +Arguments: + + ClientConnection - Describes the client process. + + Message - Message from the caller. Returns the response to be passed to + the caller. The message contains all of the following fields: + + CredentialHandle - Handle to the credentials to be used to + create the context. + + ContextHandle - On input, the handle to the partially formed context, if this is + a second call (see above) or NULL if this is the first call. + + On output, new context handle. If this is a second call, this + can be the same as phContext. + + ContextReqFlags - Requirements of the context, package specific. + + #define ISC_REQ_DELEGATE 0x00000001 + #define ISC_REQ_MUTUAL_AUTH 0x00000002 + #define ISC_REQ_REPLAY_DETECT 0x00000004 + #define ISC_REQ_SEQUENCE_DETECT 0x00000008 + #define ISC_REQ_CONFIDENTIALITY 0x00000010 + #define ISC_REQ_USE_SESSION_KEY 0x00000020 + #define ISC_REQ_PROMT_FOR__CREDS 0x00000040 + #define ISC_REQ_USE_SUPPLIED_CREDS 0x00000080 + #define ISC_REQ_ALLOCATE_MEMORY 0x00000100 + #define ISC_REQ_USE_DCE_STYLE 0x00000200 + + InputTokenSize - Size of the input token, in bytes. + + InputToken - Pointer to the input token. In the first call this + token can either be NULL or may contain security package specific + information. + + OutputTokenSize - Size of the output token, in bytes. + + OutputToken - Buffer to receive the output token. + + ContextAttributes -Attributes of the context established. + + #define ISC_RET_DELEGATE 0x00000001 + #define ISC_RET_MUTUAL_AUTH 0x00000002 + #define ISC_RET_REPLAY_DETECT 0x00000004 + #define ISC_RET_SEQUENCE_DETECT 0x00000008 + #define ISC_REP_CONFIDENTIALITY 0x00000010 + #define ISC_REP_USE_SESSION_KEY 0x00000020 + #define ISC_REP_USED_COLLECTED_CREDS 0x00000040 + #define ISC_REP_USED_SUPPLIED_CREDS 0x00000080 + #define ISC_REP_ALLOCATED_MEMORY 0x00000100 + #define ISC_REP_USED_DCE_STYLE 0x00000200 + + ExpirationTime - Expiration time of the context. + + SessionKey - Session key to be used for this context. + + NegotiateFlags - Flags negotiated for this context. + +Return Value: + + STATUS_SUCCESS - Message handled + SEC_I_CALLBACK_NEEDED -- Caller should call again later + + SEC_E_INVALID_TOKEN -- Token improperly formatted + SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid + SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough + SEC_E_NO_CREDENTIALS -- There are no credentials for this client + SEC_E_INSUFFICIENT_MEMORY -- Not enough memory + +--*/ + +{ + SECURITY_STATUS SecStatus = SEC_E_OK; + PSSP_INITIALIZE_SECURITY_CONTEXT_ARGS Args; + + // + // Simply call the worker routine. + // + + SspPrint(( SSP_API, "SspApiInitializeSecurityContext Entered\n" )); + Args = &Message->Arguments.InitializeSecurityContextArgs; + + + // + // Handle the call differently depending on whether this is the first + // or second call. + // + + if ( Args->ContextHandle.dwUpper == 0 && Args->ContextHandle.dwLower == 0 ){ + SecStatus = SsprHandleFirstCall( + ClientConnection, + &Args->CredentialHandle, + &Args->ContextHandle, + Args->ContextReqFlags, + Args->InputTokenSize, + Args->InputToken, + &Args->OutputTokenSize, + Args->OutputToken, + &Args->ContextAttributes, + &Args->ExpirationTime, + Args->SessionKey, + &Args->NegotiateFlags ); + } else { + + if (Args->ClientTokenHandle != NULL) { + SecStatus = SspLpcDuplicateHandle( + ClientConnection, + TRUE, // from client + FALSE, // don't close source + Args->ClientTokenHandle, // input handle + &Args->ClientTokenHandle // output handle + ); + + } + if (NT_SUCCESS(SecStatus)) { + + SecStatus = SsprHandleChallengeMessage( + ClientConnection, + &Args->CredentialHandle, + &Args->ContextHandle, + Args->ClientTokenHandle, + &Args->LogonId, + Args->ContextReqFlags, + Args->DomainName, + Args->DomainNameSize, + Args->UserName, + Args->UserNameSize, + Args->Password, + Args->PasswordSize, + Args->InputTokenSize, + Args->InputToken, + &Args->OutputTokenSize, + Args->OutputToken, + &Args->ContextAttributes, + &Args->ExpirationTime, + Args->SessionKey, + &Args->NegotiateFlags, + Args->ContextNames ); + } + + if (Args->ClientTokenHandle != NULL) { + (VOID) NtClose( Args->ClientTokenHandle ); + } + + } + + SspPrint(( SSP_API, "SspApiInitializeSecurityContext returns 0x%lx\n", SecStatus )); + return SecStatus; +} + + +SECURITY_STATUS +SspApiAcceptSecurityContext( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + +Routine Description: + + Allows a remotely initiated security context between the application + and a remote peer to be established. To complete the establishment of + context one or more reply tokens may be required from remote peer. + + This function is the server counterpart to the + InitializeSecurityContext API. The ContextAttributes is a bit mask + representing various context level functions viz. delegation, mutual + authentication, confidentiality, replay detection and sequence + detection. This API is used by the server side. When a request comes + in, the server uses the ContextReqFlags parameter to specify what + it requires of the session. In this fashion, a server can specify that + clients must be capable of using a confidential or integrity checked + session, and fail clients that can't meet that demand. Alternatively, + a server can require nothing, and whatever the client can provide or + requires is returned in the pfContextAttributes parameter. For a + package that supports 3 leg mutual authentication, the calling sequence + would be: Client provides a token, server calls Accept the first time, + generating a reply token. The client uses this in a second call to + InitializeSecurityContext, and generates a final token. This token is + then used in the final call to Accept to complete the session. Another + example would be the LAN Manager/NT authentication style. The client + connects to negotiate a protocol. The server calls Accept to set up a + context and generate a challenge to the client. The client calls + InitializeSecurityContext and creates a response. The server then + calls Accept the final time to allow the package to verify the response + is appropriate for the challenge. + +Arguments: + + ClientConnection - Describes the client process. + + Message - Message from the caller. Returns the response to be passed to + the caller. The message contains all of the following fields: + + CredentialHandle - Handle to the credentials to be used to + create the context. + + + ContextHandle - On input, the handle to the partially formed context, if this is + a second call (see above) or NULL if this is the first call. + + On output, new context handle. If this is a second call, this + can be the same as phContext. + + InputTokenSize - Size of the input token, in bytes. + + InputToken - Pointer to the input token. In the first call this + token can either be NULL or may contain security package specific + information. + + ContextReqFlags - Requirements of the context, package specific. + + #define ASC_REQ_DELEGATE 0x00000001 + #define ASC_REQ_MUTUAL_AUTH 0x00000002 + #define ASC_REQ_REPLAY_DETECT 0x00000004 + #define ASC_REQ_SEQUENCE_DETECT 0x00000008 + #define ASC_REQ_CONFIDENTIALITY 0x00000010 + #define ASC_REQ_USE_SESSION_KEY 0x00000020 + #define ASC_REQ_ALLOCATE_MEMORY 0x00000100 + #define ASC_REQ_USE_DCE_STYLE 0x00000200 + + OutputTokenSize - Size of the output token, in bytes. + + OutputToken - Buffer to receive the output token. + + ContextAttributes -Attributes of the context established. + + #define ASC_RET_DELEGATE 0x00000001 + #define ASC_RET_MUTUAL_AUTH 0x00000002 + #define ASC_RET_REPLAY_DETECT 0x00000004 + #define ASC_RET_SEQUENCE_DETECT 0x00000008 + #define ASC_RET_CONFIDENTIALITY 0x00000010 + #define ASC_RET_USE_SESSION_KEY 0x00000020 + #define ASC_RET_ALLOCATED_BUFFERS 0x00000100 + #define ASC_RET_USED_DCE_STYLE 0x00000200 + + ExpirationTime - Expiration time of the context. + + SessionKey - Session key to be used for this context. + + NegotiateFlags - Flags negotiated for this context. + +Return Value: + + STATUS_SUCCESS - Message handled + SEC_I_CALLBACK_NEEDED -- Caller should call again later + + SEC_E_INVALID_TOKEN -- Token improperly formatted + SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid + SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough + SEC_E_LOGON_DENIED -- User is no allowed to logon to this server + SEC_E_INSUFFICIENT_MEMORY -- Not enough memory + +--*/ + +{ + SECURITY_STATUS SecStatus; + PSSP_ACCEPT_SECURITY_CONTEXT_ARGS Args; + + // + // Simply call the worker routine. + // + + SspPrint(( SSP_API, "SspApiAcceptSecurityContext Entered\n" )); + Args = &Message->Arguments.AcceptSecurityContextArgs; + + + // + // Handle the call differently depending on whether this is the first + // or second call. + // + + if ( Args->ContextHandle.dwUpper == 0 && Args->ContextHandle.dwLower == 0 ){ + SecStatus = SsprHandleNegotiateMessage( + ClientConnection, + &Args->CredentialHandle, + &Args->ContextHandle, + Args->ContextReqFlags, + Args->InputTokenSize, + Args->InputToken, + &Args->OutputTokenSize, + Args->OutputToken, + &Args->ContextAttributes, + &Args->ExpirationTime ); + } else { + SecStatus = SsprHandleAuthenticateMessage( + ClientConnection, + &Args->CredentialHandle, + &Args->ContextHandle, + Args->ContextReqFlags, + Args->InputTokenSize, + Args->InputToken, + &Args->OutputTokenSize, + Args->OutputToken, + &Args->ContextAttributes, + &Args->ExpirationTime, + Args->SessionKey, + &Args->NegotiateFlags, + &Args->TokenHandle, + &Args->SubStatus, + Args->ContextNames, + &Args->PasswordExpiry ); + + // + // If that succeeded and we have a token handle, duplicate + // it into the client process. + // + + if ((SecStatus == S_OK) && (Args->TokenHandle != NULL)) { + SecStatus = SspLpcDuplicateHandle( + ClientConnection, + FALSE, // not from client + TRUE, // close source + Args->TokenHandle, // input handle + &Args->TokenHandle // output handle + ); + + + } + } + + SspPrint(( SSP_API, "SspApiAcceptSecurityContext returns 0x%lx\n", SecStatus )); + return SecStatus; +} + + + +SECURITY_STATUS +SspApiQueryContextAttributes( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + +Routine Description: + + This API allows a customer of the security services to determine + certain attributes of the context. These are: sizes, names, and + lifespan. + +Arguments: + + ClientConnection - Describes the client process. + + Message - Message from the caller. Returns the response to be passed to + the caller. The message contains all of the following fields: + + ContextHandle - Handle to the context to query. + + Attribute - Attribute to query. + + #define SECPKG_ATTR_SIZES 0 + #define SECPKG_ATTR_NAMES 1 + #define SECPKG_ATTR_LIFESPAN 2 + + Buffer - Buffer to copy the data into. The buffer must be large enough + to fit the queried attribute. + +Return Value: + + STATUS_SUCCESS - Call completed successfully + + SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid + SEC_E_UNSUPPORTED_FUNCTION -- Function code is not supported + +--*/ + +{ + SECURITY_STATUS SecStatus; + PSSP_QUERY_CONTEXT_ATTRIBUTES_ARGS Args; + + // + // Simply call the worker routine. + // + + Args = &Message->Arguments.QueryContextAttributesArgs; + + + SecStatus = SsprQueryContextAttributes( + ClientConnection, + &Args->ContextHandle, + Args->Attribute, + Args->Buffer ); + + return SecStatus; + +} + + + +SECURITY_STATUS +SspApiDeleteSecurityContext( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + +Routine Description: + + Deletes the local data structures associated with the specified + security context and generates a token which is passed to a remote peer + so it too can remove the corresponding security context. + + This API terminates a context on the local machine, and optionally + provides a token to be sent to the other machine. The OutputToken + generated by this call is to be sent to the remote peer (initiator or + acceptor). If the context was created with the I _REQ_ALLOCATE_MEMORY + flag, then the package will allocate a buffer for the output token. + Otherwise, it is the responsibility of the caller. + +Arguments: + + ContextHandle - Handle to the context to delete + + TokenLength - Size of the output token (if any) that should be sent to + the process at the other end of the session. + + Token - Pointer to the token to send. + +Return Value: + + STATUS_SUCCESS - Call completed successfully + + SEC_E_NO_SPM -- Security Support Provider is not running + SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid + +--*/ + +{ + SECURITY_STATUS SecStatus; + PSSP_DELETE_SECURITY_CONTEXT_ARGS Args; + + // + // Simply call the worker routine. + // + + Args = &Message->Arguments.DeleteSecurityContextArgs; + + + SecStatus = SsprDeleteSecurityContext( + ClientConnection, + &Args->ContextHandle ); + + return SecStatus; + +} + + +SECURITY_STATUS +SspApiNoop( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + +Routine Description: + + This is a no-operation dispatch procedure. It is passed to the LPC + thread when no action is required. + + For instance, it is used during service shutdown to awaken the LPC thread + so it can recognize that it should exit. + + +Arguments: + +Return Value: + + Status of the operation. + +--*/ + +{ + NTSTATUS Status; + SspPrint(( SSP_API, "SspApiNoop Entered\n" )); + Status = STATUS_SUCCESS; + SspPrint(( SSP_API, "SspApiNoop returns 0x%lx\n", Status )); + return Status; + UNREFERENCED_PARAMETER( Message ); + UNREFERENCED_PARAMETER( ClientConnection ); +} + +SECURITY_STATUS +SspApiNtLmSspControl( + IN PSSP_CLIENT_CONNECTION ClientConnection, + IN OUT PSSP_API_MESSAGE Message + ) + +/*++ + +Routine Description: + + This is a no-operation dispatch procedure. It is passed to the LPC + thread when no action is required. + + For instance, it is used during service shutdown to awaken the LPC thread + so it can recognize that it should exit. + + +Arguments: + +Return Value: + + STATUS_SUCCESS -- Call completed successfully + + SEC_E_UNSUPPORTED_FUNCTION -- Function code is not supported + +--*/ + +{ +#if DBG + SECURITY_STATUS SecStatus; + PSSP_NTLMSSP_CONTROL_ARGS Args; + + // + // Initialization + // + + SspPrint(( SSP_API, "SspApiNtLmSspControl Entered\n" )); + Args = &Message->Arguments.NtLmSspControlArgs; + + + // + // Force a breakpoint + // + + switch ( Args->FunctionCode ) { + case NTLMSSP_BREAKPOINT: + KdPrint(( "NtLmSsp Break Point\n")); + DbgBreakPoint(); + break; + + // + // Change the debug flags + // + + case NTLMSSP_DBFLAG: + SspGlobalDbflag = Args->Data; + SspPrint((SSP_MISC,"SspGlobalDbflag is set to %lx\n", SspGlobalDbflag )); + break; + + // + // Truncate the log file + // + + case NTLMSSP_TRUNCATE: + + SspOpenDebugFile( TRUE ); + SspPrint((SSP_MISC, "TRUNCATE_LOG function received.\n" )); + break; + + + // + // All other function codes are invalid. + // + + default: + SecStatus = SEC_E_UNSUPPORTED_FUNCTION; + goto Cleanup; + } + + + SecStatus = STATUS_SUCCESS; + +Cleanup: + SspPrint(( SSP_API, "SspApiNtLmSspControl returns 0x%lx\n", SecStatus )); + return SecStatus; +#else // DBG + return SEC_E_UNSUPPORTED_FUNCTION; + UNREFERENCED_PARAMETER( Message ); +#endif // DBG + UNREFERENCED_PARAMETER( ClientConnection ); +} |