/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

extern "C" {
#include <afs/param.h>
#include <afs/stds.h>
}

#include "TaAfsAdmSvrClientInternal.h"


/*
 * VARIABLES __________________________________________________________________
 *
 */

static struct
   {
   BOOL fInitializedSockets;
   size_t cReqForAdminServer;
   } l;


/*
 * ROUTINES ___________________________________________________________________
 *
 */

extern "C" void __RPC_FAR * __RPC_USER MIDL_user_allocate (size_t cbAllocate)
{
   return (void __RPC_FAR *)Allocate (cbAllocate);
}

extern "C" void __RPC_USER MIDL_user_free (void __RPC_FAR *pData)
{
   Free (pData);
}


/*
 * DATA STRUCTURES ____________________________________________________________
 *
 */

BOOL ADMINAPI asc_AsidListCreate (LPASIDLIST *ppList)
{
   return ((*ppList = AfsAdmSvr_CreateAsidList()) != NULL);
}

BOOL ADMINAPI asc_AsidListCopy (LPASIDLIST *ppListTarget, LPASIDLIST *ppListSource)
{
   return ((*ppListTarget = AfsAdmSvr_CopyAsidList (*ppListSource)) != NULL);
}

BOOL ADMINAPI asc_AsidListAddEntry (LPASIDLIST *ppList, ASID idObject, LPARAM lp)
{
   return AfsAdmSvr_AddToAsidList (ppList, idObject, lp);
}

BOOL ADMINAPI asc_AsidListRemoveEntry (LPASIDLIST *ppList, ASID idObject)
{
   return AfsAdmSvr_RemoveFromAsidList (ppList, idObject);
}

BOOL ADMINAPI asc_AsidListRemoveEntryByIndex (LPASIDLIST *ppList, size_t iIndex)
{
   return AfsAdmSvr_RemoveFromAsidListByIndex (ppList, iIndex);
}

BOOL ADMINAPI asc_AsidListSetEntryParam (LPASIDLIST *ppList, ASID idObject, LPARAM lp)
{
   return AfsAdmSvr_SetAsidListParam (ppList, idObject, lp);
}

BOOL ADMINAPI asc_AsidListSetEntryParamByIndex (LPASIDLIST *ppList, size_t iIndex, LPARAM lp)
{
   return AfsAdmSvr_SetAsidListParamByIndex (ppList, iIndex, lp);
}

BOOL ADMINAPI asc_AsidListTest (LPASIDLIST *ppList, ASID idObject, LPARAM *pParam)
{
   return AfsAdmSvr_IsInAsidList (ppList, idObject, pParam);
}

BOOL ADMINAPI asc_AsidListFree (LPASIDLIST *ppList)
{
   AfsAdmSvr_FreeAsidList (ppList);
   return TRUE;
}


BOOL ADMINAPI asc_ObjPropListCreate (LPASOBJPROPLIST *ppList)
{
   return ((*ppList = AfsAdmSvr_CreateObjPropList()) != NULL);
}

BOOL ADMINAPI asc_ObjPropListCopy (LPASOBJPROPLIST *ppListTarget, LPASOBJPROPLIST *ppListSource)
{
   return ((*ppListTarget = AfsAdmSvr_CopyObjPropList (*ppListSource)) != NULL);
}

BOOL ADMINAPI asc_ObjPropListAddEntry (LPASOBJPROPLIST *ppList, LPASOBJPROP pProperties, LPARAM lp)
{
   return AfsAdmSvr_AddToObjPropList (ppList, pProperties, lp);
}

BOOL ADMINAPI asc_ObjPropListRemoveEntry (LPASOBJPROPLIST *ppList, ASID idObject)
{
   return AfsAdmSvr_RemoveFromObjPropList (ppList, idObject);
}

BOOL ADMINAPI asc_ObjPropListTest (LPASOBJPROPLIST *ppList, ASID idObject, LPASOBJPROP pProperties, LPARAM *pParam)
{
   return AfsAdmSvr_IsInObjPropList (ppList, idObject, pProperties, pParam);
}

BOOL ADMINAPI asc_ObjPropListFree (LPASOBJPROPLIST *ppList)
{
   AfsAdmSvr_FreeObjPropList (ppList);
   return TRUE;
}


BOOL ADMINAPI asc_ActionListCreate (LPASACTIONLIST *ppList)
{
   return ((*ppList = AfsAdmSvr_CreateActionList()) != NULL);
}

BOOL ADMINAPI asc_ActionListCopy (LPASACTIONLIST *ppListTarget, LPASACTIONLIST *ppListSource)
{
   return ((*ppListTarget = AfsAdmSvr_CopyActionList (*ppListSource)) != NULL);
}

BOOL ADMINAPI asc_ActionListAddEntry (LPASACTIONLIST *ppList, LPASACTION pAction)
{
   return AfsAdmSvr_AddToActionList (ppList, pAction);
}

BOOL ADMINAPI asc_ActionListRemoveEntry (LPASACTIONLIST *ppList, DWORD idAction)
{
   return AfsAdmSvr_RemoveFromActionList (ppList, idAction);
}

BOOL ADMINAPI asc_ActionListTest (LPASACTIONLIST *ppList, DWORD idAction, LPASACTION pAction)
{
   return AfsAdmSvr_IsInActionList (ppList, idAction, pAction);
}

BOOL ADMINAPI asc_ActionListFree (LPASACTIONLIST *ppList)
{
   AfsAdmSvr_FreeActionList (ppList);
   return TRUE;
}


/*
 * ROUTINES ___________________________________________________________________
 *
 */

BOOL ADMINAPI asc_AdminServerOpen (LPCTSTR pszAddress, DWORD *pidClient, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   if (!l.fInitializedSockets)
      {
      WSADATA Data;
      WSAStartup (0x0101, &Data);
      l.fInitializedSockets = TRUE;
      }

   if ((++l.cReqForAdminServer) == 1)
      {
      LPCTSTR pszResolvedAddress = ResolveAddress (pszAddress);
      if (!BindToAdminServer (pszResolvedAddress, FALSE, pidClient, &status))
         {
         if (status != RPC_S_CALL_FAILED_DNE) // server rejected us?
            rc = FALSE;
         else if (pszResolvedAddress || !ForkNewAdminServer (&status))
            rc = FALSE;
         else
            rc = BindToAdminServer (pszResolvedAddress, TRUE, pidClient, &status);
         }
      }

   if (rc)
      StartPingThread (*pidClient);
   if (rc)
      StartCallbackThread();
   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_AdminServerClose (DWORD idClient, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   StopCallbackThread();
   StopPingThread (idClient);

   if (l.cReqForAdminServer && ((--l.cReqForAdminServer) == 0))
      {
      UnbindFromAdminServer (idClient, &status);
      }

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}



BOOL ADMINAPI asc_CredentialsCrack (DWORD idClient, PVOID hCreds, LPTSTR pszCell, LPTSTR pszUser, SYSTEMTIME *pstExpiration, ULONG *pStatus)
{
   BOOL rc = FALSE;
   ULONG status = 0;

   RpcTryExcept
      {
      STRING szCell = TEXT("");
      STRING szUser = TEXT("");

      if ((rc = AfsAdmSvr_CrackCredentials (idClient, (DWORD)hCreds, szCell, szUser, pstExpiration, &status)) != FALSE)
         {
         lstrcpy (pszCell, szCell);
         lstrcpy (pszUser, szUser);
         }
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


PVOID ADMINAPI asc_CredentialsGet (DWORD idClient, LPCTSTR pszCell, ULONG *pStatus)
{
   PVOID rc = NULL;
   ULONG status = 0;

   RpcTryExcept
      {
      if (pszCell)
         {
         STRING szCell;
         lstrcpy (szCell, pszCell);

         rc = (PVOID)AfsAdmSvr_GetCredentials (idClient, szCell, &status);
         }
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


PVOID ADMINAPI asc_CredentialsSet (DWORD idClient, LPCTSTR pszCell, LPCTSTR pszUser, LPCTSTR pszPassword, ULONG *pStatus)
{
   PVOID rc = NULL;
   ULONG status = 0;

   RpcTryExcept
      {
      STRING szCell;
      lstrcpy (szCell, pszCell);

      STRING szUser;
      lstrcpy (szUser, pszUser);

      STRING szPassword;
      lstrcpy (szPassword, pszPassword);

      // TODO: Ensure we do some encryption here, or using an
      // encrypted socket, or something... can't just be pushing
      // the user's unencrypted password across the wire.

      rc = (PVOID)AfsAdmSvr_SetCredentials (idClient, szCell, szUser, szPassword, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_CredentialsPush (DWORD idClient, PVOID hCreds, ASID idCell, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      rc = AfsAdmSvr_PushCredentials (idClient, (DWORD)hCreds, idCell, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_LocalCellGet (DWORD idClient, LPTSTR pszCell, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      STRING szCell;
      if ((rc = AfsAdmSvr_GetLocalCell (idClient, szCell, &status)) != FALSE)
         {
         lstrcpy (pszCell, szCell);
         }
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ErrorCodeTranslate (DWORD idClient, ULONG code, LANGID idLanguage, STRING pszErrorText, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      STRING szText;
      if ((rc = AfsAdmSvr_ErrorCodeTranslate (idClient, code, idLanguage, szText, &status)) != FALSE)
         {
         lstrcpy (pszErrorText, szText);
         }
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_CellOpen (DWORD idClient, PVOID hCreds, LPCTSTR pszCell, DWORD dwScope, ASID *pidCell, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      STRING szCell;
      lstrcpy (szCell, pszCell);

      if ((rc = AfsAdmSvr_OpenCell (idClient, (DWORD)hCreds, szCell, dwScope, pidCell, &status)) != FALSE)
         {
         if (!CreateCellCache (*pidCell))
            {
            (void)AfsAdmSvr_CloseCell (idClient, *pidCell, &status);
            rc = FALSE;
            status = ERROR_NOT_ENOUGH_MEMORY;
            }
         else // get rudimentary properties about the cell
            {
            rc = RefreshCachedProperties (idClient, *pidCell, *pidCell, GET_RUDIMENTARY_DATA, &status);
            }
         }
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_CellClose (DWORD idClient, ASID idCell, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      rc = AfsAdmSvr_CloseCell (idClient, idCell, &status);
      DestroyCellCache (idCell);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectFind (DWORD idClient, ASID idSearchScope, ASOBJTYPE ObjectType, LPCTSTR pszName, ASID *pidObject, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      STRING szName = TEXT("");
      if (pszName)
         lstrcpy (szName, pszName);
      rc = AfsAdmSvr_FindObject (idClient, idSearchScope, ObjectType, SEARCH_ALL_OBJECTS, szName, pidObject, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectFindMultiple (DWORD idClient, ASID idSearchScope, ASOBJTYPE ObjectType, LPCTSTR pszPattern, LPAFSADMSVR_SEARCH_PARAMS pSearchParams, LPASIDLIST *ppList, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      *ppList = NULL;

      STRING szPattern = TEXT("");
      if (pszPattern)
         lstrcpy (szPattern, pszPattern);

      AFSADMSVR_SEARCH_PARAMS SearchParams;
      if (pSearchParams)
         memcpy (&SearchParams, pSearchParams, sizeof(AFSADMSVR_SEARCH_PARAMS));
      else
         {
         memset (&SearchParams, 0x00, sizeof(AFSADMSVR_SEARCH_PARAMS));
         SearchParams.SearchType = SEARCH_NO_LIMITATIONS;
         }

      rc = AfsAdmSvr_FindObjects (idClient, idSearchScope, ObjectType, SEARCH_ALL_OBJECTS, szPattern, &SearchParams, ppList, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectPropertiesGet (DWORD idClient, AFSADMSVR_GET_LEVEL GetLevel, ASID idCell, ASID idObject, LPASOBJPROP pProperties, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   if (!RefreshCachedProperties (idClient, idCell, idObject, GetLevel, &status))
      {
      rc = FALSE;
      }
   else
      {
      LPASOBJPROP pFound;
      if ((pFound = GetCachedProperties (idCell, idObject)) == NULL)
         {
         status = ERROR_NOT_ENOUGH_MEMORY;
         rc = FALSE;
         }
      else
         {
         memcpy (pProperties, pFound, sizeof(ASOBJPROP));
         }
      }

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectPropertiesGetMultiple (DWORD idClient, AFSADMSVR_GET_LEVEL GetLevel, ASID idCell, LPASIDLIST pAsidList, LPASOBJPROPLIST *ppPropertiesList, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   if (!RefreshCachedProperties (idClient, idCell, pAsidList, GetLevel, &status))
      {
      rc = FALSE;
      }
   else
      {
      *ppPropertiesList = NULL;
      for (size_t iAsidList = 0; iAsidList < pAsidList->cEntries; ++iAsidList)
         {
         LPASOBJPROP pFound;
         if ((pFound = GetCachedProperties (idCell, pAsidList->aEntries[ iAsidList ].idObject)) != NULL)
            {
            if (!*ppPropertiesList)
               asc_ObjPropListCreate(ppPropertiesList);
            if (*ppPropertiesList)
               AfsAdmSvr_AddToObjPropList (ppPropertiesList, pFound, pAsidList->aEntries[ iAsidList ].lParam);
            }
         }
      }

   if (!rc && *ppPropertiesList)
      AfsAdmSvr_FreeObjPropList (ppPropertiesList);
   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectListen (DWORD idClient, ASID idCell, ASID idObject, HWND hNotify, ULONG *pStatus)
{
   if (!idObject)
      {
      if (*pStatus)
         *pStatus = ERROR_INVALID_PARAMETER;
      return FALSE;
      }

   if (!AddObjectNotification (hNotify, idCell, idObject))
      {
      if (*pStatus)
         *pStatus = ERROR_NOT_ENOUGH_MEMORY;
      return FALSE;
      }

   TestForNotifications (idClient, idCell, idObject);
   return TRUE;
}


BOOL ADMINAPI asc_ObjectListenClear (DWORD idClient, HWND hNotify, ULONG *pStatus)
{
   ClearObjectNotifications (hNotify);
   return TRUE;
}


BOOL ADMINAPI asc_ObjectListenMultiple (DWORD idClient, ASID idCell, LPASIDLIST pAsidList, HWND hNotify, ULONG *pStatus)
{
   if (!pAsidList)
      {
      if (*pStatus)
         *pStatus = ERROR_INVALID_PARAMETER;
      return FALSE;
      }

   for (size_t ii = 0; ii < pAsidList->cEntriesAllocated; ++ii)
      {
      if (!pAsidList->aEntries[ ii ].idObject)
         continue;

      if (!AddObjectNotification (hNotify, idCell, pAsidList->aEntries[ ii ].idObject))
         {
         if (*pStatus)
            *pStatus = ERROR_NOT_ENOUGH_MEMORY;
         return FALSE;
         }

      TestForNotifications (idClient, idCell, pAsidList->aEntries[ ii ].idObject);
      }

   return TRUE;
}


BOOL ADMINAPI asc_ObjectRefresh (DWORD idClient, ASID idCell, ASID idObject, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   // First have the server invalidate its cache of information; regardless
   // of the name, this is actually just an Invalidate call, not a Refresh call
   //
   RpcTryExcept
      {
      rc = AfsAdmSvr_RefreshObject (idClient, idObject, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   // If that suceeded, see if there is anyone listening for changes
   // in this object or any of its children. If so, this call
   // will requery the server for the latest properties for all
   // listened-for objects, which will make us post notifications if
   // we get new data back.
   //
   if (rc)
      {
      TestForNotifications (idClient, idCell);
      }

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectRefreshMultiple (DWORD idClient, ASID idCell, LPASIDLIST pAsidList, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   // First have the server invalidate its cache of information; regardless
   // of the name, this is actually just an Invalidate call, not a Refresh call
   //
   RpcTryExcept
      {
      rc = AfsAdmSvr_RefreshObjects (idClient, pAsidList, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   // If that suceeded, see if there is anyone listening for changes
   // in any of these objects or their children. If so, this call
   // will requery the server for the latest properties for all
   // listened-for objects, which will make us post notifications if
   // we get new data back.
   //
   if (rc)
      {
      TestForNotifications (idClient, idCell);
      }

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_RandomKeyGet (DWORD idClient, ASID idCell, PBYTE key, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      memset (key, 0x00, sizeof(BYTE) * ENCRYPTIONKEYLENGTH);
      rc = AfsAdmSvr_GetRandomKey (idClient, idCell, key, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_CellNameGet_Fast (DWORD idClient, ASID idCell, LPTSTR pszCell, ULONG *pStatus)
{
   return asc_ObjectNameGet_Fast (idClient, idCell, idCell, pszCell, pStatus);
}


BOOL ADMINAPI asc_ObjectNameGet_Fast (DWORD idClient, ASID idCell, ASID idObject, LPTSTR pszObject, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   LPASOBJPROP pProperties;
   if ((pProperties = GetCachedProperties (idCell, idObject)) == NULL)
      {
      rc = FALSE;
      status = ERROR_NO_DATA;
      }
   else if (pProperties->verProperties == verPROP_NO_OBJECT)
      {
      rc = FALSE;
      status = ERROR_NO_DATA;
      }
   else
      {
      lstrcpy (pszObject, pProperties->szName);
      }

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectTypeGet_Fast (DWORD idClient, ASID idCell, ASID idObject, ASOBJTYPE *pObjectType, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   LPASOBJPROP pProperties;
   if ((pProperties = GetCachedProperties (idCell, idObject)) == NULL)
      {
      rc = FALSE;
      status = ERROR_NO_DATA;
      }
   else if (pProperties->verProperties == verPROP_NO_OBJECT)
      {
      rc = FALSE;
      status = ERROR_NO_DATA;
      }
   else
      {
      *pObjectType = pProperties->Type;
      }

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ObjectPropertiesGet_Fast (DWORD idClient, ASID idCell, ASID idObject, LPASOBJPROP pProperties, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   LPASOBJPROP pPropFound;
   if ((pPropFound = GetCachedProperties (idCell, idObject)) == NULL)
      {
      rc = FALSE;
      status = ERROR_NO_DATA;
      }
   else if (pPropFound->verProperties == verPROP_NO_OBJECT)
      {
      rc = FALSE;
      status = ERROR_NO_DATA;
      }
   else if (pProperties)
      {
      memcpy (pProperties, pPropFound, sizeof(ASOBJPROP));
      }

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ActionGet (DWORD idClient, DWORD idAction, LPASACTION pAction, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      memset (pAction, 0x00, sizeof(ASACTION));
      rc = AfsAdmSvr_GetAction (idClient, idAction, pAction, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ActionGetMultiple (DWORD idClient, DWORD idClientSearch, ASID idCellSearch, LPASACTIONLIST *ppList, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   RpcTryExcept
      {
      *ppList = NULL;
      rc = AfsAdmSvr_GetActions (idClient, idClientSearch, idCellSearch, ppList, &status);
      }
   RpcExcept(1)
      {
      rc = FALSE;
      status = RPC_S_CALL_FAILED_DNE;
      }
   RpcEndExcept

   if (!rc && pStatus)
      *pStatus = status;
   return rc;
}


BOOL ADMINAPI asc_ActionListen (DWORD idClient, HWND hNotify, ULONG *pStatus)
{
   if (!SetActionNotification (hNotify, TRUE))
      {
      if (*pStatus)
         *pStatus = ERROR_NOT_ENOUGH_MEMORY;
      return FALSE;
      }
   return TRUE;
}


BOOL ADMINAPI asc_ActionListenClear (DWORD idClient, HWND hNotify, ULONG *pStatus)
{
   if (!SetActionNotification (hNotify, FALSE))
      {
      if (*pStatus)
         *pStatus = ERROR_NOT_ENOUGH_MEMORY;
      return FALSE;
      }
   return TRUE;
}


      // AfsAdmSvrCallback_Action
      // ...called by the server in the context of the CallbackHost() routine;
      //    this routine is used to notify the client whenever an action is
      //    initiated or completed.
      //
extern "C" void AfsAdmSvrCallback_Action (LPASACTION pAction, BOOL fFinished)
{
   NotifyActionListeners (pAction, fFinished);
}

