Leaked source code of windows server 2003
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.
 
 
 
 
 
 

742 lines
23 KiB

/*
Copyright (c) 1992 Microsoft Corporation
Module Name:
client.c
Abstract:
This module contains the client impersonation code.
Author:
Jameel Hyder (microsoft!jameelh)
Revision History:
16 Jun 1992 Initial Version
Notes: Tab stop: 4
--*/
#define FILENUM FILE_CLIENT
#include <afp.h>
#include <client.h>
#include <access.h>
#include <secutil.h>
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, AfpImpersonateClient)
#pragma alloc_text( PAGE, AfpRevertBack)
#pragma alloc_text( PAGE, AfpGetChallenge)
#pragma alloc_text( PAGE, AfpLogonUser)
#endif
/*** AfpImpersonateClient
*
* Impersonates the remote client. The token representing the remote client
* is available in the SDA. If the SDA is NULL (i.e. server context) then
* impersonate the token that we have created for ourselves.
*/
VOID
AfpImpersonateClient(
IN PSDA pSda OPTIONAL
)
{
NTSTATUS Status = STATUS_SUCCESS;
HANDLE Token;
PAGED_CODE( );
if (pSda != NULL)
{
Token = pSda->sda_UserToken;
}
else Token = AfpFspToken;
ASSERT(Token != NULL);
Status = NtSetInformationThread(NtCurrentThread(),
ThreadImpersonationToken,
(PVOID)&Token,
sizeof(Token));
ASSERT(NT_SUCCESS(Status));
}
/*** AfpRevertBack
*
* Revert back to the default thread context.
*/
VOID
AfpRevertBack(
VOID
)
{
NTSTATUS Status = STATUS_SUCCESS;
HANDLE Handle = NULL;
PAGED_CODE( );
Status = NtSetInformationThread(NtCurrentThread(),
ThreadImpersonationToken,
(PVOID)&Handle,
sizeof(Handle));
ASSERT(NT_SUCCESS(Status));
}
/*** AfpGetChallenge
*
* Obtain a challenge token from the MSV1_0 package. This token is used by
* AfpLogin call.
*
* The following function modified so that we generate the challenge ourselves
* instead of making a call. This routine borrowed almost verbatim from
* the LM server code.
*/
PBYTE
AfpGetChallenge(
IN VOID
)
{
PMSV1_0_LM20_CHALLENGE_REQUEST ChallengeRequest;
PMSV1_0_LM20_CHALLENGE_RESPONSE ChallengeResponse;
ULONG Length;
PBYTE pRetBuf;
NTSTATUS Status, StatusX;
union
{
LARGE_INTEGER time;
UCHAR bytes[8];
} u;
ULONG seed;
ULONG challenge[2];
ULONG result3;
PAGED_CODE( );
ChallengeRequest = NULL;
//
// Create a pseudo-random 8-byte number by munging the system time
// for use as a random number seed.
//
// Start by getting the system time.
//
ASSERT( MSV1_0_CHALLENGE_LENGTH == 2 * sizeof(ULONG) );
KeQuerySystemTime( &u.time );
//
// To ensure that we don't use the same system time twice, add in the
// count of the number of times this routine has been called. Then
// increment the counter.
//
// *** Since we don't use the low byte of the system time (it doesn't
// take on enough different values, because of the timer
// resolution), we increment the counter by 0x100.
//
// *** We don't interlock the counter because we don't really care
// if it's not 100% accurate.
//
u.time.LowPart += EncryptionKeyCount;
EncryptionKeyCount += 0x100;
//
// Now use parts of the system time as a seed for the random
// number generator.
//
// *** Because the middle two bytes of the low part of the system
// time change most rapidly, we use those in forming the seed.
//
seed = ((u.bytes[1] + 1) << 0) |
((u.bytes[2] + 0) << 8) |
((u.bytes[2] - 1) << 16) |
((u.bytes[1] + 0) << 24);
//
// Now get two random numbers. RtlRandom does not return negative
// numbers, so we pseudo-randomly negate them.
//
challenge[0] = RtlRandom( &seed );
challenge[1] = RtlRandom( &seed );
result3 = RtlRandom( &seed );
if ( (result3 & 0x1) != 0 )
{
challenge[0] |= 0x80000000;
}
if ( (result3 & 0x2) != 0 )
{
challenge[1] |= 0x80000000;
}
// Allocate a buffer to hold the challenge and copy it in
if ((pRetBuf = AfpAllocNonPagedMemory(MSV1_0_CHALLENGE_LENGTH)) != NULL)
{
RtlCopyMemory(pRetBuf, challenge, MSV1_0_CHALLENGE_LENGTH);
}
return (pRetBuf);
}
/*** AfpLogonUser
*
* Attempt to login the user. The password is either encrypted or cleartext
* based on the UAM used. The UserName and domain is extracted out of the Sda.
*
* LOCKS: AfpStatisticsLock (SPIN)
*/
AFPSTATUS
AfpLogonUser(
IN PSDA pSda,
IN PANSI_STRING UserPasswd
)
{
NTSTATUS Status, SubStatus;
PUNICODE_STRING WSName;
ULONG ulUnused;
ULONG NtlmInTokenSize;
PNTLM_AUTHENTICATE_MESSAGE NtlmInToken = NULL;
PAUTHENTICATE_MESSAGE InToken = NULL;
ULONG InTokenSize;
PNTLM_ACCEPT_RESPONSE OutToken = NULL;
ULONG OutTokenSize;
SIZE_T AllocateSize;
SecBufferDesc InputToken;
SecBuffer InputBuffers[2];
SecBufferDesc OutputToken;
SecBuffer OutputBuffer;
CtxtHandle hNewContext;
TimeStamp Expiry;
ULONG BufferOffset;
PCHAR pTmp;
PRAS_SUBAUTH_INFO pRasSubAuthInfo;
PARAP_SUBAUTH_REQ pSfmSubAuthInfo;
PARAP_SUBAUTH_RESP pSfmResp;
DWORD ResponseHigh;
DWORD ResponseLow;
DWORD dwTmpLen;
PAGED_CODE( );
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
#ifdef OPTIMIZE_GUEST_LOGONS
// 11/28/94 SueA: Now that there is a License Service to track the number
// of sessions via LsaLogonUser, we can no longer fake the guest tokens.
// Optimization for subsequent guest logons
// After the first guest logon, we save the token and do not free it till the
// server stops. All subsequent guest logons 'share' that token.
if (pSda->sda_ClientType == SDA_CLIENT_GUEST)
{
AfpSwmrAcquireExclusive(&AfpEtcMapLock);
if (AfpGuestToken != NULL)
{
pSda->sda_UserToken = AfpGuestToken;
pSda->sda_UserSid = &AfpSidWorld;
pSda->sda_GroupSid = &AfpSidWorld; // Primary group of Guest is also 'World'
#ifdef INHERIT_DIRECTORY_PERMS
pSda->sda_UID = AfpIdWorld;
pSda->sda_GID = AfpIdWorld;
#else
ASSERT (AfpGuestSecDesc != NULL);
pSda->sda_pSecDesc = AfpGuestSecDesc;
#endif
AfpSwmrRelease(&AfpEtcMapLock);
return AFP_ERR_NONE;
}
else
{
AfpSwmrRelease(&AfpEtcMapLock);
}
}
#endif // OPTIMIZE_GUEST_LOGONS
WSName = &AfpDefaultWksta;
if (pSda->sda_WSName.Length != 0)
WSName = &pSda->sda_WSName;
//
// Figure out how big a buffer we need. We put all the messages
// in one buffer for efficiency's sake.
//
NtlmInTokenSize = sizeof(NTLM_AUTHENTICATE_MESSAGE);
// alignment needs to be correct based on 32/64 bit addressing!!!
NtlmInTokenSize = (NtlmInTokenSize + 7) & 0xfffffff8;
InTokenSize = sizeof(AUTHENTICATE_MESSAGE) +
pSda->sda_UserName.Length +
WSName->Length +
(sizeof(RAS_SUBAUTH_INFO) + sizeof(ARAP_SUBAUTH_REQ)) +
pSda->sda_DomainName.Length +
UserPasswd->Length +
24; // extra for byte aligning
InTokenSize = (InTokenSize + 7) & 0xfffffff8;
OutTokenSize = sizeof(NTLM_ACCEPT_RESPONSE);
OutTokenSize = (OutTokenSize + 7) & 0xfffffff8;
//
// Round this up to 8 byte boundary becaus the out token needs to be
// quad word aligned for the LARGE_INTEGER.
//
AllocateSize = ((NtlmInTokenSize + InTokenSize + 7) & 0xfffffff8) + OutTokenSize;
Status = NtAllocateVirtualMemory(NtCurrentProcess(),
&InToken,
0L,
&AllocateSize,
MEM_COMMIT,
PAGE_READWRITE);
if (!NT_SUCCESS(Status))
{
AFPLOG_ERROR(AFPSRVMSG_PAGED_POOL, Status, &AllocateSize,sizeof(AllocateSize), NULL);
#if DBG
DbgBreakPoint();
#endif
return(AFP_ERR_MISC);
}
NtlmInToken = (PNTLM_AUTHENTICATE_MESSAGE) ((PUCHAR) InToken + InTokenSize);
OutToken = (PNTLM_ACCEPT_RESPONSE) ((PUCHAR)NtlmInToken + ((NtlmInTokenSize + 7) & 0xfffffff8));
RtlZeroMemory(InToken, InTokenSize + NtlmInTokenSize);
//
// set up the NtlmInToken first
//
if (pSda->sda_Challenge)
{
RtlCopyMemory(NtlmInToken->ChallengeToClient,
pSda->sda_Challenge,
MSV1_0_CHALLENGE_LENGTH );
}
if ((pSda->sda_ClientType == SDA_CLIENT_RANDNUM) ||
(pSda->sda_ClientType == SDA_CLIENT_TWOWAY))
{
NtlmInToken->ParameterControl = (MSV1_0_SUBAUTHENTICATION_DLL_RAS << 24);
}
else
{
NtlmInToken->ParameterControl = 0;
}
//
// Okay, now for the tought part - marshalling the AUTHENTICATE_MESSAGE
//
RtlCopyMemory(InToken->Signature,
NTLMSSP_SIGNATURE,
sizeof(NTLMSSP_SIGNATURE) );
InToken->MessageType = NtLmAuthenticate;
BufferOffset = sizeof(AUTHENTICATE_MESSAGE);
//
// LM password - case insensitive
//
pTmp = (PBYTE)InToken + BufferOffset;
*(LPWSTR)pTmp = L'\0';
InToken->LmChallengeResponse.Buffer = BufferOffset;
InToken->LmChallengeResponse.Length = 1;
InToken->LmChallengeResponse.MaximumLength = sizeof(WCHAR);
InToken->NtChallengeResponse.Buffer = BufferOffset;
InToken->NtChallengeResponse.Length = 0;
InToken->NtChallengeResponse.MaximumLength = sizeof(WCHAR);
InToken->DomainName.Buffer = BufferOffset;
InToken->DomainName.Length = 0;
InToken->DomainName.MaximumLength = sizeof(WCHAR);
InToken->Workstation.Buffer = BufferOffset;
InToken->Workstation.Length = 0;
InToken->Workstation.MaximumLength = sizeof(WCHAR);
InToken->UserName.Buffer = BufferOffset;
InToken->UserName.Length = 0;
InToken->UserName.MaximumLength = sizeof(WCHAR);
if (pSda->sda_UserName.Length != 0)
{
if (pSda->sda_DomainName.Length != 0)
{
InToken->DomainName.Length = pSda->sda_DomainName.Length;
InToken->DomainName.MaximumLength = pSda->sda_DomainName.MaximumLength;
InToken->DomainName.Buffer = BufferOffset;
RtlCopyMemory((PBYTE)InToken + BufferOffset,
(PBYTE)pSda->sda_DomainName.Buffer,
pSda->sda_DomainName.Length);
BufferOffset += pSda->sda_DomainName.Length;
BufferOffset = (BufferOffset + 3) & 0xfffffffc; // dword align it
}
InToken->LmChallengeResponse.Buffer = BufferOffset;
//
// is he using native Apple UAM? setup buffers differently!
//
if ((pSda->sda_ClientType == SDA_CLIENT_RANDNUM) ||
(pSda->sda_ClientType == SDA_CLIENT_TWOWAY))
{
pRasSubAuthInfo =
(PRAS_SUBAUTH_INFO)((PBYTE)InToken + BufferOffset);
pRasSubAuthInfo->ProtocolType = RAS_SUBAUTH_PROTO_ARAP;
pRasSubAuthInfo->DataSize = sizeof(ARAP_SUBAUTH_REQ);
pSfmSubAuthInfo = (PARAP_SUBAUTH_REQ)&pRasSubAuthInfo->Data[0];
if (pSda->sda_ClientType == SDA_CLIENT_RANDNUM)
{
pSfmSubAuthInfo->PacketType = SFM_SUBAUTH_LOGON_PKT;
}
else
{
pSfmSubAuthInfo->PacketType = SFM_2WAY_SUBAUTH_LOGON_PKT;
}
pSfmSubAuthInfo->Logon.fGuestLogon = FALSE;
ASSERT(pSda->sda_Challenge != NULL);
// put the 2 dwords of challenge that we gave the Mac
pTmp = pSda->sda_Challenge;
GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.NTChallenge1,pTmp);
pTmp += sizeof(DWORD);
GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.NTChallenge2,pTmp);
// put the 2 dwords of response that the Mac gave us
pTmp = UserPasswd->Buffer;
GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacResponse1,pTmp);
pTmp += sizeof(DWORD);
GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacResponse2,pTmp);
// 2-way guy sends his own challenge: doesn't trust us!
if (pSda->sda_ClientType == SDA_CLIENT_TWOWAY)
{
pTmp += sizeof(DWORD);
GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacChallenge1,pTmp);
pTmp += sizeof(DWORD);
GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacChallenge2,pTmp);
}
dwTmpLen = (sizeof(RAS_SUBAUTH_INFO) + sizeof(ARAP_SUBAUTH_REQ));
InToken->LmChallengeResponse.Length = (USHORT)dwTmpLen;
InToken->LmChallengeResponse.MaximumLength = (USHORT)dwTmpLen;
BufferOffset += dwTmpLen;
}
//
// this client is using MS-UAM or Apple's cleartext
//
else
{
InToken->LmChallengeResponse.Length = UserPasswd->Length;
InToken->LmChallengeResponse.MaximumLength = UserPasswd->MaximumLength;
RtlCopyMemory( (PBYTE)InToken + BufferOffset,
UserPasswd->Buffer,
UserPasswd->Length );
BufferOffset += UserPasswd->Length;
}
BufferOffset = (BufferOffset + 3) & 0xfffffffc; // dword align it
//
// Workstation Name
//
InToken->Workstation.Buffer = BufferOffset;
InToken->Workstation.Length = WSName->Length;
InToken->Workstation.MaximumLength = WSName->MaximumLength;
RtlCopyMemory((PBYTE)InToken + BufferOffset,
WSName->Buffer,
WSName->Length);
BufferOffset += WSName->Length;
BufferOffset = (BufferOffset + 3) & 0xfffffffc; // dword align it
//
// User Name
//
InToken->UserName.Buffer = BufferOffset;
InToken->UserName.Length = pSda->sda_UserName.Length;
InToken->UserName.MaximumLength = pSda->sda_UserName.MaximumLength;
RtlCopyMemory((PBYTE)InToken + BufferOffset,
pSda->sda_UserName.Buffer,
pSda->sda_UserName.Length);
BufferOffset += pSda->sda_UserName.Length;
}
InputToken.pBuffers = InputBuffers;
InputToken.cBuffers = 2;
InputToken.ulVersion = 0;
InputBuffers[0].pvBuffer = InToken;
InputBuffers[0].cbBuffer = InTokenSize;
InputBuffers[0].BufferType = SECBUFFER_TOKEN;
InputBuffers[1].pvBuffer = NtlmInToken;
InputBuffers[1].cbBuffer = NtlmInTokenSize;
InputBuffers[1].BufferType = SECBUFFER_TOKEN;
OutputToken.pBuffers = &OutputBuffer;
OutputToken.cBuffers = 1;
OutputToken.ulVersion = 0;
OutputBuffer.pvBuffer = OutToken;
OutputBuffer.cbBuffer = OutTokenSize;
OutputBuffer.BufferType = SECBUFFER_TOKEN;
Status = AcceptSecurityContext(&AfpSecHandle,
NULL,
&InputToken,
ASC_REQ_LICENSING,
SECURITY_NATIVE_DREP,
&hNewContext,
&OutputToken,
&ulUnused,
&Expiry );
if (NT_SUCCESS(Status))
{
AFPTIME CurrentTime;
NTSTATUS SecStatus;
if (pSda->sda_ClientType != SDA_CLIENT_GUEST)
{
SecPkgContext_PasswordExpiry PasswordExpires;
// Get the kickoff time from the profile buffer. Round this to
// even # of SESSION_CHECK_TIME units
SecStatus = QueryContextAttributes(
&hNewContext,
SECPKG_ATTR_PASSWORD_EXPIRY,
&PasswordExpires
);
if( SecStatus == NO_ERROR )
{
AfpGetCurrentTimeInMacFormat(&CurrentTime);
pSda->sda_tTillKickOff = (DWORD)
( AfpConvertTimeToMacFormat(&PasswordExpires.tsPasswordExpires) -
CurrentTime );
pSda->sda_tTillKickOff -= pSda->sda_tTillKickOff % SESSION_CHECK_TIME;
}
else
{
DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR,
("AfpLogonUser: QueryContextAttributes failed %lx\n",SecStatus));
}
}
// return stuff from subauth
pSfmResp = (PARAP_SUBAUTH_RESP)&OutToken->UserSessionKey[0];
ResponseHigh = pSfmResp->Response.high;
ResponseLow = pSfmResp->Response.low;
SubStatus = NtFreeVirtualMemory(NtCurrentProcess( ),
(PVOID *)&InToken,
&AllocateSize,
MEM_RELEASE);
ASSERT(NT_SUCCESS(SubStatus));
//
// 2-Way authentication? client expects us to send a response to
// the challenge that it sent
//
if (pSda->sda_ClientType == SDA_CLIENT_TWOWAY)
{
pSda->sda_ReplySize = RANDNUM_RESP_LEN;
if (AfpAllocReplyBuf(pSda) != AFP_ERR_NONE)
{
return(AFP_ERR_USER_NOT_AUTH);
}
pTmp = pSda->sda_ReplyBuf;
PUTBYTE42BYTE4(pTmp, (PBYTE)&ResponseHigh);
pTmp += sizeof(DWORD);
PUTBYTE42BYTE4(pTmp, (PBYTE)&ResponseLow);
}
else if ((pSda->sda_ClientType == SDA_CLIENT_MSUAM_V2) ||
(pSda->sda_ClientType == SDA_CLIENT_MSUAM_V3))
{
pSda->sda_ReplySize = sizeof(DWORD);
if (AfpAllocReplyBuf(pSda) != AFP_ERR_NONE)
{
return(AFP_ERR_USER_NOT_AUTH);
}
pTmp = pSda->sda_ReplyBuf;
PUTBYTE42BYTE4(pTmp, (PBYTE)&pSda->sda_tTillKickOff);
}
}
else // if (NT_SUCCESS(Status) != NO_ERROR)
{
NTSTATUS ExtErrCode = Status;
DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR,
("AfpLogonUser: AcceptSecurityContext() failed with %X\n", Status));
SubStatus = NtFreeVirtualMemory(NtCurrentProcess(),
(PVOID *)&InToken,
&AllocateSize,
MEM_RELEASE );
ASSERT(NT_SUCCESS(SubStatus));
// Set extended error codes here if using custom UAM or AFP 2.1
Status = AFP_ERR_USER_NOT_AUTH; // default
// The mac will map this to a session error dialog for each UAM.
// The dialog may be a little different for different versions of
// the mac OS and each UAM, but will always have something to do
// with getting the message across about no more sessions available.
if (ExtErrCode == STATUS_LICENSE_QUOTA_EXCEEDED)
{
DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR,
("AfpLogonUser: License Quota Exceeded: returning ASP_SERVER_BUSY\n"));
return (ASP_SERVER_BUSY);
}
if ((pSda->sda_ClientVersion >= AFP_VER_21) &&
(pSda->sda_ClientType != SDA_CLIENT_MSUAM_V2) &&
(pSda->sda_ClientType != SDA_CLIENT_MSUAM_V3))
{
if ((ExtErrCode == STATUS_PASSWORD_EXPIRED) ||
(ExtErrCode == STATUS_PASSWORD_MUST_CHANGE))
Status = AFP_ERR_PWD_EXPIRED;
}
else if ((pSda->sda_ClientType == SDA_CLIENT_MSUAM_V1) ||
(pSda->sda_ClientType == SDA_CLIENT_MSUAM_V2) ||
(pSda->sda_ClientType == SDA_CLIENT_MSUAM_V3))
{
if ((ExtErrCode == STATUS_PASSWORD_EXPIRED) ||
(ExtErrCode == STATUS_PASSWORD_MUST_CHANGE))
Status = AFP_ERR_PASSWORD_EXPIRED;
else if ((ExtErrCode == STATUS_ACCOUNT_DISABLED) ||
(ExtErrCode == STATUS_ACCOUNT_LOCKED_OUT))
Status = AFP_ERR_ACCOUNT_DISABLED;
else if (ExtErrCode == STATUS_INVALID_LOGON_HOURS)
Status = AFP_ERR_INVALID_LOGON_HOURS;
else if (ExtErrCode == STATUS_INVALID_WORKSTATION)
Status = AFP_ERR_INVALID_WORKSTATION;
}
return( Status );
}
//
// get the token out using the context
//
Status = QuerySecurityContextToken( &hNewContext, &pSda->sda_UserToken );
if (!NT_SUCCESS(Status))
{
DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR,
("AfpLogonUser: QuerySecurityContextToken() failed with %X\n", Status));
pSda->sda_UserToken = NULL; // just paranoia
return(Status);
}
Status = DeleteSecurityContext( &hNewContext );
if (!NT_SUCCESS(Status))
{
DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR,
("AfpLogonUser: DeleteSecurityContext() failed with %X\n", Status));
}
Status = AfpGetUserAndPrimaryGroupSids(pSda);
if (!NT_SUCCESS(Status))
{
DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR,
("AfpLogonUser: AfpGetUserAndPrimaryGroupSids() failed with %X\n", Status));
AFPLOG_ERROR(AFPSRVMSG_LOGON, Status, NULL, 0, NULL);
return( Status );
}
#ifdef INHERIT_DIRECTORY_PERMS
// Convert the user and group sids to IDs
AfpSidToMacId(pSda->sda_UserSid, &pSda->sda_UID);
AfpSidToMacId(pSda->sda_GroupSid, &pSda->sda_GID);
#else
// Make a security descriptor for user
Status = AfpMakeSecurityDescriptorForUser(pSda->sda_UserSid,
pSda->sda_GroupSid,
&pSda->sda_pSecDesc);
#endif
#ifdef OPTIMIZE_GUEST_LOGONS
if (pSda->sda_ClientType == SDA_CLIENT_GUEST)
{
// Save the guest login token and security descriptor
AfpSwmrAcquireExclusive(&AfpEtcMapLock);
AfpGuestToken = pSda->sda_UserToken;
#ifdef INHERIT_DIRECTORY_PERMS
AfpSidToMacId(&AfpSidWorld, &AfpIdWorld);
#else
AfpGuestSecDesc = pSda->sda_pSecDesc;
#endif
AfpSwmrRelease(&AfpEtcMapLock);
}
#endif // OPTIMIZE_GUEST_LOGONS
return Status;
}