/* Editor Settings: expandtabs and use 4 spaces for indentation
* ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
* -*- mode: c, c-basic-offset: 4 -*- */
/*
* Copyright Likewise Software 2004-2008
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details. You should have received a copy of the GNU General
* Public License along with this program. If not, see
* .
*
* LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
* TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
* WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
* TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
* GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU
* HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
* TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
* license@likewisesoftware.com
*/
/*
* Copyright (C) Likewise Software. All rights reserved.
*
* Module Name:
*
* main.c
*
* Abstract:
*
* Likewise Security and Authentication Subsystem (LSASS)
*
* Tool to get status from LSA Server
*
* Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
* Sriram Nambakam (snambakam@likewisesoftware.com)
*/
#define _POSIX_PTHREAD_SEMANTICS 1
#include "config.h"
#include "lsasystem.h"
#include "lsadef.h"
#include "lsa/lsa.h"
#include "lsaclient.h"
#include "lsaipc.h"
#define LSA_MODE_STRING_UNKNOWN "Unknown"
#define LSA_MODE_STRING_UNPROVISIONED "Un-provisioned"
#define LSA_MODE_STRING_DEFAULT_CELL "Default Cell"
#define LSA_MODE_STRING_NON_DEFAULT_CELL "Non-default Cell"
#define LSA_MODE_STRING_LOCAL "Local system"
#define LSA_SUBMODE_STRING_UNKNOWN "Unknown"
#define LSA_SUBMODE_STRING_SCHEMA "Schema"
#define LSA_SUBMODE_STRING_NON_SCHEMA "Non-schema"
#define LSA_STATUS_STRING_UNKNOWN "Unknown"
#define LSA_STATUS_STRING_ONLINE "Online"
#define LSA_STATUS_STRING_OFFLINE "Offline"
#define LSA_TRUST_TYPE_STRING_UNKNOWN "Unknown"
#define LSA_TRUST_TYPE_STRING_DOWNLEVEL "Down Level"
#define LSA_TRUST_TYPE_STRING_UPLEVEL "Up Level"
#define LSA_TRUST_TYPE_STRING_MIT "MIT"
#define LSA_TRUST_TYPE_STRING_DCE "DCE"
#define LW_PRINTF_STRING(x) ((x) ? (x) : "")
static
VOID
ParseArgs(
int argc,
char* argv[]
);
static
VOID
ShowUsage();
static
VOID
PrintStatus(
PLSASTATUS pStatus
);
static
PCSTR
GetStatusString(
LsaAuthProviderStatus status
);
static
PCSTR
GetModeString(
LsaAuthProviderMode mode
);
static
PCSTR
GetSubmodeString(
LsaAuthProviderSubMode subMode
);
static
PCSTR
GetTrustTypeString(
DWORD dwTrustType
);
static
DWORD
MapErrorCode(
DWORD dwError
);
int
get_status_main(
int argc,
char* argv[]
)
{
DWORD dwError = 0;
PLSASTATUS pLsaStatus = NULL;
HANDLE hLsaConnection = (HANDLE)NULL;
size_t dwErrorBufferSize = 0;
BOOLEAN bPrintOrigError = TRUE;
ParseArgs(argc, argv);
dwError = LsaOpenServer(&hLsaConnection);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaGetStatus(
hLsaConnection,
&pLsaStatus);
BAIL_ON_LSA_ERROR(dwError);
PrintStatus(pLsaStatus);
cleanup:
if (pLsaStatus) {
LsaFreeStatus(pLsaStatus);
}
if (hLsaConnection != (HANDLE)NULL) {
LsaCloseServer(hLsaConnection);
}
return (dwError);
error:
dwError = MapErrorCode(dwError);
dwErrorBufferSize = LwGetErrorString(dwError, NULL, 0);
if (dwErrorBufferSize > 0)
{
DWORD dwError2 = 0;
PSTR pszErrorBuffer = NULL;
dwError2 = LwAllocateMemory(
dwErrorBufferSize,
(PVOID*)&pszErrorBuffer);
if (!dwError2)
{
DWORD dwLen = LwGetErrorString(dwError, pszErrorBuffer, dwErrorBufferSize);
if ((dwLen == dwErrorBufferSize) && !LW_IS_NULL_OR_EMPTY_STR(pszErrorBuffer))
{
fprintf(
stderr,
"Failed to query status from LSA service. Error code %u (%s).\n%s\n",
dwError,
LW_PRINTF_STRING(LwWin32ExtErrorToName(dwError)),
pszErrorBuffer);
bPrintOrigError = FALSE;
}
}
LW_SAFE_FREE_STRING(pszErrorBuffer);
}
if (bPrintOrigError)
{
fprintf(
stderr,
"Failed to query status from LSA service. Error code %u (%s).\n",
dwError,
LW_PRINTF_STRING(LwWin32ExtErrorToName(dwError)));
}
goto cleanup;
}
VOID
ParseArgs(
int argc,
char* argv[]
)
{
typedef enum {
PARSE_MODE_OPEN = 0
} ParseMode;
int iArg = 1;
PSTR pszArg = NULL;
ParseMode parseMode = PARSE_MODE_OPEN;
do {
pszArg = argv[iArg++];
if (pszArg == NULL || *pszArg == '\0')
{
break;
}
switch (parseMode)
{
case PARSE_MODE_OPEN:
if ((strcmp(pszArg, "--help") == 0) ||
(strcmp(pszArg, "-h") == 0))
{
ShowUsage();
exit(0);
}
else
{
ShowUsage();
exit(1);
}
break;
}
} while (iArg < argc);
}
void
ShowUsage()
{
printf("Usage: lw-get-status\n");
}
VOID
PrintStatus(
PLSASTATUS pStatus
)
{
DWORD iCount = 0;
DWORD dwDays = pStatus->dwUptime/LSA_SECONDS_IN_DAY;
DWORD dwHours = (pStatus->dwUptime - (dwDays * LSA_SECONDS_IN_DAY))/LSA_SECONDS_IN_HOUR;
DWORD dwMins = (pStatus->dwUptime -
(dwDays * LSA_SECONDS_IN_DAY) -
(dwHours * LSA_SECONDS_IN_HOUR))/LSA_SECONDS_IN_MINUTE;
DWORD dwSecs = (pStatus->dwUptime -
(dwDays * LSA_SECONDS_IN_DAY) -
(dwHours * LSA_SECONDS_IN_HOUR) -
(dwMins * LSA_SECONDS_IN_MINUTE));
printf("LSA Server Status:\n\n");
printf("Compiled daemon version: %u.%u.%u.%u\n",
pStatus->lsassVersion.dwMajor,
pStatus->lsassVersion.dwMinor,
pStatus->lsassVersion.dwBuild,
pStatus->lsassVersion.dwRevision);
printf("Packaged product version: %u.%u.%u.%u\n",
pStatus->productVersion.dwMajor,
pStatus->productVersion.dwMinor,
pStatus->productVersion.dwBuild,
pStatus->productVersion.dwRevision);
printf("Uptime: %u days %u hours %u minutes %u seconds\n", dwDays, dwHours, dwMins, dwSecs);
for (iCount = 0; iCount < pStatus->dwCount; iCount++)
{
PLSA_AUTH_PROVIDER_STATUS pProviderStatus =
&pStatus->pAuthProviderStatusList[iCount];
printf("\n[Authentication provider: %s]\n\n",
LW_IS_NULL_OR_EMPTY_STR(pProviderStatus->pszId) ? "" : pProviderStatus->pszId);
printf("\tStatus: %s\n", GetStatusString(pProviderStatus->status));
printf("\tMode: %s\n", GetModeString(pProviderStatus->mode));
switch (pProviderStatus->mode)
{
case LSA_PROVIDER_MODE_LOCAL_SYSTEM:
break;
case LSA_PROVIDER_MODE_UNPROVISIONED:
case LSA_PROVIDER_MODE_DEFAULT_CELL:
case LSA_PROVIDER_MODE_NON_DEFAULT_CELL:
printf("\tDomain: %s\n", LW_IS_NULL_OR_EMPTY_STR(pProviderStatus->pszDomain) ? "" : pProviderStatus->pszDomain);
printf("\tForest: %s\n", LW_IS_NULL_OR_EMPTY_STR(pProviderStatus->pszForest) ? "" : pProviderStatus->pszForest);
printf("\tSite: %s\n", LW_IS_NULL_OR_EMPTY_STR(pProviderStatus->pszSite) ? "" : pProviderStatus->pszSite);
printf("\tOnline check interval: %d seconds\n", pProviderStatus->dwNetworkCheckInterval);
break;
default:
break;
}
switch (pProviderStatus->mode)
{
case LSA_PROVIDER_MODE_DEFAULT_CELL:
printf("\tSub mode: %s\n", GetSubmodeString(pProviderStatus->subMode));
break;
case LSA_PROVIDER_MODE_NON_DEFAULT_CELL:
printf("\tSub mode: %s\n", GetSubmodeString(pProviderStatus->subMode));
printf("\tCell: %s\n", LW_IS_NULL_OR_EMPTY_STR(pProviderStatus->pszCell) ? "" : pProviderStatus->pszCell);
break;
default:
break;
}
if (pProviderStatus->pTrustedDomainInfoArray)
{
DWORD iDomain = 0;
printf("\t[Trusted Domains: %d]\n\n", pProviderStatus->dwNumTrustedDomains);
for (; iDomain < pProviderStatus->dwNumTrustedDomains; iDomain++)
{
PLSA_TRUSTED_DOMAIN_INFO pDomainInfo =
&pProviderStatus->pTrustedDomainInfoArray[iDomain];
printf("\n\t[Domain: %s]\n\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszNetbiosDomain) ? "" : pDomainInfo->pszNetbiosDomain);
printf("\t\tDNS Domain: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszDnsDomain) ? "" : pDomainInfo->pszDnsDomain);
printf("\t\tNetbios name: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszNetbiosDomain) ? "" : pDomainInfo->pszNetbiosDomain);
printf("\t\tForest name: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszForestName) ? "" : pDomainInfo->pszForestName);
printf("\t\tTrustee DNS name: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszTrusteeDnsDomain) ? "" : pDomainInfo->pszTrusteeDnsDomain);
printf("\t\tClient site name: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszClientSiteName) ? "" : pDomainInfo->pszClientSiteName);
printf("\t\tDomain SID: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszDomainSID) ? "" : pDomainInfo->pszDomainSID);
printf("\t\tDomain GUID: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pszDomainGUID) ? "" : pDomainInfo->pszDomainGUID);
printf("\t\tTrust Flags: [0x%.04x]\n", pDomainInfo->dwTrustFlags);
if (pDomainInfo->dwTrustFlags & LSA_TRUST_FLAG_IN_FOREST)
printf("\t\t [0x%.04x - In forest]\n", LSA_TRUST_FLAG_IN_FOREST);
if (pDomainInfo->dwTrustFlags & LSA_TRUST_FLAG_OUTBOUND)
printf("\t\t [0x%.04x - Outbound]\n", LSA_TRUST_FLAG_OUTBOUND);
if (pDomainInfo->dwTrustFlags & LSA_TRUST_FLAG_TREEROOT)
printf("\t\t [0x%.04x - Tree root]\n", LSA_TRUST_FLAG_TREEROOT);
if (pDomainInfo->dwTrustFlags & LSA_TRUST_FLAG_PRIMARY)
printf("\t\t [0x%.04x - Primary]\n", LSA_TRUST_FLAG_PRIMARY);
if (pDomainInfo->dwTrustFlags & LSA_TRUST_FLAG_NATIVE)
printf("\t\t [0x%.04x - Native]\n", LSA_TRUST_FLAG_NATIVE);
if (pDomainInfo->dwTrustFlags & LSA_TRUST_FLAG_INBOUND)
printf("\t\t [0x%.04x - Inbound]\n", LSA_TRUST_FLAG_INBOUND);
printf("\t\tTrust type: %s\n", GetTrustTypeString(pDomainInfo->dwTrustType));
printf("\t\tTrust Attributes: [0x%.04x]\n", pDomainInfo->dwTrustAttributes);
if (pDomainInfo->dwTrustAttributes & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE)
printf("\t\t [0x%.04x - Non-transitive]\n", LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE);
if (pDomainInfo->dwTrustAttributes & LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY)
printf("\t\t [0x%.04x - Uplevel only]\n", LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY);
if (pDomainInfo->dwTrustAttributes & LSA_TRUST_ATTRIBUTE_FILTER_SIDS)
printf("\t\t [0x%.04x - Filter SIDs]\n", LSA_TRUST_ATTRIBUTE_FILTER_SIDS);
if (pDomainInfo->dwTrustAttributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)
printf("\t\t [0x%.04x - Forest transitive]\n", LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE);
if (pDomainInfo->dwTrustAttributes & LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION)
printf("\t\t [0x%.04x - Cross organization]\n", LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION);
if (pDomainInfo->dwTrustAttributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)
printf("\t\t [0x%.04x - Within forest]\n", LSA_TRUST_ATTRIBUTE_WITHIN_FOREST);
printf("\t\tTrust Direction: ");
switch (pDomainInfo->dwTrustDirection)
{
case LSA_TRUST_DIRECTION_ZERO_WAY:
printf("Zeroway Trust\n");
break;
case LSA_TRUST_DIRECTION_ONE_WAY:
printf("Oneway Trust\n");
break;
case LSA_TRUST_DIRECTION_TWO_WAY:
printf("Twoway Trust\n");
break;
case LSA_TRUST_DIRECTION_SELF:
printf("Primary Domain\n");
break;
default:
printf("Unknown trust direction\n");
}
printf("\t\tTrust Mode: ");
switch (pDomainInfo->dwTrustMode)
{
case LSA_TRUST_MODE_EXTERNAL:
printf("External Trust (ET)\n");
break;
case LSA_TRUST_MODE_MY_FOREST:
printf("In my forest Trust (MFT)\n");
break;
case LSA_TRUST_MODE_OTHER_FOREST:
printf("In other forest Trust (OFT)\n");
break;
default:
printf("Unknown trust mode\n");
}
printf("\t\tDomain flags: [0x%.04x]\n", pDomainInfo->dwDomainFlags);
if (pDomainInfo->dwDomainFlags & LSA_DM_DOMAIN_FLAG_PRIMARY)
printf("\t\t [0x%.04x - Primary]\n", LSA_DM_DOMAIN_FLAG_PRIMARY);
if (pDomainInfo->dwDomainFlags & LSA_DM_DOMAIN_FLAG_OFFLINE)
printf("\t\t [0x%.04x - Offline]\n", LSA_DM_DOMAIN_FLAG_OFFLINE);
if (pDomainInfo->dwDomainFlags & LSA_DM_DOMAIN_FLAG_FORCE_OFFLINE)
printf("\t\t [0x%.04x - Force offline]\n", LSA_DM_DOMAIN_FLAG_FORCE_OFFLINE);
if (pDomainInfo->dwDomainFlags & LSA_DM_DOMAIN_FLAG_TRANSITIVE_1WAY_CHILD)
printf("\t\t [0x%.04x - Transitive 1 way child]\n", LSA_DM_DOMAIN_FLAG_TRANSITIVE_1WAY_CHILD);
if (pDomainInfo->dwDomainFlags & LSA_DM_DOMAIN_FLAG_FOREST_ROOT)
printf("\t\t [0x%.04x - Forest root]\n", LSA_DM_DOMAIN_FLAG_FOREST_ROOT);
if (pDomainInfo->dwDomainFlags & LSA_DM_DOMAIN_FLAG_GC_OFFLINE)
printf("\t\t [0x%.04x - GC offline]\n", LSA_DM_DOMAIN_FLAG_GC_OFFLINE);
if (pDomainInfo->pDCInfo)
{
printf("\n\t\t[Domain Controller (DC) Information]\n\n");
printf("\t\t\tDC Name: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pDCInfo->pszName) ? "" : pDomainInfo->pDCInfo->pszName);
printf("\t\t\tDC Address: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pDCInfo->pszAddress) ? "" : pDomainInfo->pDCInfo->pszAddress);
printf("\t\t\tDC Site: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pDCInfo->pszSiteName) ? "" : pDomainInfo->pDCInfo->pszSiteName);
printf("\t\t\tDC Flags: [0x%.08x]\n", pDomainInfo->pDCInfo->dwFlags);
printf("\t\t\tDC Is PDC: %s\n",
(pDomainInfo->pDCInfo->dwFlags & LSA_DS_PDC_FLAG) ? "yes" : "no");
printf("\t\t\tDC is time server: %s\n",
(pDomainInfo->pDCInfo->dwFlags & LSA_DS_TIMESERV_FLAG) ? "yes" : "no");
printf("\t\t\tDC has writeable DS: %s\n",
(pDomainInfo->pDCInfo->dwFlags & LSA_DS_WRITABLE_FLAG) ? "yes" : "no");
printf("\t\t\tDC is Global Catalog: %s\n",
(pDomainInfo->pDCInfo->dwFlags & LSA_DS_GC_FLAG) ? "yes" : "no");
printf("\t\t\tDC is running KDC: %s\n",
(pDomainInfo->pDCInfo->dwFlags & LSA_DS_KDC_FLAG) ? "yes" : "no");
}
if (pDomainInfo->pGCInfo)
{
printf("\n\t\t[Global Catalog (GC) Information]\n\n");
printf("\t\t\tGC Name: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pGCInfo->pszName) ? "" : pDomainInfo->pGCInfo->pszName);
printf("\t\t\tGC Address: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pGCInfo->pszAddress) ? "" : pDomainInfo->pGCInfo->pszAddress);
printf("\t\t\tGC Site: %s\n", LW_IS_NULL_OR_EMPTY_STR(pDomainInfo->pGCInfo->pszSiteName) ? "" : pDomainInfo->pGCInfo->pszSiteName);
printf("\t\t\tGC Flags: [0x%.08x]\n", pDomainInfo->pGCInfo->dwFlags);
printf("\t\t\tGC Is PDC: %s\n",
(pDomainInfo->pGCInfo->dwFlags & LSA_DS_PDC_FLAG) ? "yes" : "no");
printf("\t\t\tGC is time server: %s\n",
(pDomainInfo->pGCInfo->dwFlags & LSA_DS_TIMESERV_FLAG) ? "yes" : "no");
printf("\t\t\tGC has writeable DS: %s\n",
(pDomainInfo->pGCInfo->dwFlags & LSA_DS_WRITABLE_FLAG) ? "yes" : "no");
printf("\t\t\tGC is running KDC: %s\n",
(pDomainInfo->pGCInfo->dwFlags & LSA_DS_KDC_FLAG) ? "yes" : "no");
}
}
}
}
}
PCSTR
GetStatusString(
LsaAuthProviderStatus status
)
{
PCSTR pszStatusString = NULL;
switch (status)
{
case LSA_AUTH_PROVIDER_STATUS_ONLINE:
pszStatusString = LSA_STATUS_STRING_ONLINE;
break;
case LSA_AUTH_PROVIDER_STATUS_OFFLINE:
pszStatusString = LSA_STATUS_STRING_OFFLINE;
break;
default:
pszStatusString = LSA_STATUS_STRING_UNKNOWN;
break;
}
return pszStatusString;
}
PCSTR
GetModeString(
LsaAuthProviderMode mode
)
{
PCSTR pszModeString = NULL;
switch (mode)
{
case LSA_PROVIDER_MODE_UNPROVISIONED:
pszModeString = LSA_MODE_STRING_UNPROVISIONED;
break;
case LSA_PROVIDER_MODE_DEFAULT_CELL:
pszModeString = LSA_MODE_STRING_DEFAULT_CELL;
break;
case LSA_PROVIDER_MODE_NON_DEFAULT_CELL:
pszModeString = LSA_MODE_STRING_NON_DEFAULT_CELL;
break;
case LSA_PROVIDER_MODE_LOCAL_SYSTEM:
pszModeString = LSA_MODE_STRING_LOCAL;
break;
default:
pszModeString = LSA_MODE_STRING_UNKNOWN;
break;
}
return pszModeString;
}
PCSTR
GetSubmodeString(
LsaAuthProviderSubMode subMode
)
{
PCSTR pszSubmodeString = NULL;
switch(subMode)
{
case LSA_AUTH_PROVIDER_SUBMODE_SCHEMA:
pszSubmodeString = LSA_SUBMODE_STRING_SCHEMA;
break;
case LSA_AUTH_PROVIDER_SUBMODE_NONSCHEMA:
pszSubmodeString = LSA_SUBMODE_STRING_NON_SCHEMA;
break;
default:
pszSubmodeString = LSA_SUBMODE_STRING_UNKNOWN;
break;
}
return pszSubmodeString;
}
PCSTR
GetTrustTypeString(
DWORD dwTrustType
)
{
PCSTR pszTrustTypeString = NULL;
switch (dwTrustType)
{
case LSA_TRUST_TYPE_DOWNLEVEL:
pszTrustTypeString = LSA_TRUST_TYPE_STRING_DOWNLEVEL;
break;
case LSA_TRUST_TYPE_UPLEVEL:
pszTrustTypeString = LSA_TRUST_TYPE_STRING_UPLEVEL;
break;
case LSA_TRUST_TYPE_MIT:
pszTrustTypeString = LSA_TRUST_TYPE_STRING_MIT;
break;
case LSA_TRUST_TYPE_DCE:
pszTrustTypeString = LSA_TRUST_TYPE_STRING_DCE;
break;
default:
pszTrustTypeString = LSA_TRUST_TYPE_STRING_UNKNOWN;
break;
}
return pszTrustTypeString;
}
DWORD
MapErrorCode(
DWORD dwError
)
{
DWORD dwError2 = dwError;
switch (dwError)
{
case ECONNREFUSED:
case ENETUNREACH:
case ETIMEDOUT:
dwError2 = LW_ERROR_LSA_SERVER_UNREACHABLE;
break;
default:
break;
}
return dwError2;
}