Ejemplo n.º 1
0
int
VmDirExternalOperationCreate(
    BerElement*       ber,
    ber_int_t         msgId,
    ber_tag_t         reqCode,
    PVDIR_CONNECTION  pConn,
    PVDIR_OPERATION*  ppOperation
    )
{
   int retVal = 0;
   PVDIR_OPERATION pOperation = NULL;

   VmDirLog( LDAP_DEBUG_TRACE, "NewOperation: Begin" );

   retVal = VmDirAllocateMemory( sizeof(*pOperation), (PVOID *)&pOperation );
   BAIL_ON_VMDIR_ERROR( retVal );

   pOperation->protocol = 0;
   pOperation->reqCode = reqCode;
   pOperation->ber = ber;
   pOperation->msgId = msgId;
   pOperation->conn = pConn;
   pOperation->opType = VDIR_OPERATION_TYPE_EXTERNAL;

   retVal = VmDirAllocateMemory( sizeof(*pOperation->pBECtx), (PVOID)&(pOperation->pBECtx));
   BAIL_ON_VMDIR_ERROR( retVal );

   retVal = VmDirSchemaCtxAcquire(&pOperation->pSchemaCtx);
   BAIL_ON_VMDIR_ERROR( retVal );

   pOperation->lowestPendingUncommittedUsn = 0;

   if ( pOperation->reqCode == LDAP_REQ_ADD )
   {
       retVal = VmDirAllocateMemory( sizeof(*(pOperation->request.addReq.pEntry)),
                                     (PVOID)&(pOperation->request.addReq.pEntry) );
       BAIL_ON_VMDIR_ERROR( retVal );
   }

   *ppOperation = pOperation;

cleanup:
   VmDirLog( LDAP_DEBUG_TRACE, "%s: End", __FUNCTION__ );

   return retVal;

error:
   VmDirLog( LDAP_DEBUG_TRACE, "%s: acquire schema context failed",
           __FUNCTION__ );

   VmDirFreeOperation(pOperation);
   *ppOperation = NULL;

   goto cleanup;
}
Ejemplo n.º 2
0
static
int
BindListenOnPort(
#ifndef _WIN32
   sa_family_t   addr_type,
   size_t        addr_size,
#else
   short         addr_type,
   int           addr_size,
#endif
    void         *pServ_addr,
    ber_socket_t *pSockfd
)
{
#define LDAP_PORT_LISTEN_BACKLOG 128
   int  optname = 0;
   int  retVal = LDAP_SUCCESS;
   int  retValBind = 0;
   PSTR pszLocalErrMsg = NULL;
   int  on = 1;
#ifdef _WIN32
   DWORD sTimeout = 0;
   int  reTries = 0;
#else
   struct timeval sTimeout = {0};
#endif

   *pSockfd = -1;
   *pSockfd = socket(addr_type, SOCK_STREAM, 0);
   if (*pSockfd < 0)
   {
#ifdef _WIN32
      errno = WSAGetLastError();
#endif
      retVal = LDAP_OPERATIONS_ERROR;
      BAIL_ON_VMDIR_ERROR_WITH_MSG( retVal, pszLocalErrMsg,
                      "%s: socket() call failed with errno: %d", __func__, errno );
   }

#ifdef _WIN32
    optname = SO_EXCLUSIVEADDRUSE;
#else
    optname = SO_REUSEADDR;
#endif

   if (setsockopt(*pSockfd, SOL_SOCKET, optname, (const char *)(&on), sizeof(on)) < 0)
   {
#ifdef _WIN32
      errno = WSAGetLastError();
#endif
      retVal = LDAP_OPERATIONS_ERROR;
      BAIL_ON_VMDIR_ERROR_WITH_MSG( retVal, pszLocalErrMsg,
                      "%s: setsockopt() call failed with errno: %d", __func__, errno );
   }

   on = 1;  // turn on TCP_NODELAY below

   if (setsockopt(*pSockfd,  IPPROTO_TCP, TCP_NODELAY, (const char *)(&on), sizeof(on) ) < 0)
   {
#ifdef _WIN32
      errno = WSAGetLastError();
#endif
      retVal = LDAP_OPERATIONS_ERROR;
      BAIL_ON_VMDIR_ERROR_WITH_MSG( retVal, pszLocalErrMsg,
                      "%s: setsockopt() TCP_NODELAY call failed with errno: %d", __func__, errno );
   }

   if (addr_type == AF_INET6)
   {
#ifdef _WIN32
       if (setsockopt(*pSockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)(&on), sizeof(on) ) < 0)
       {
           errno = WSAGetLastError();
#else
       if (setsockopt(*pSockfd, SOL_IPV6, IPV6_V6ONLY, (const char *)(&on), sizeof(on) ) < 0)
       {
#endif
           retVal = LDAP_OPERATIONS_ERROR;
           BAIL_ON_VMDIR_ERROR_WITH_MSG( retVal, pszLocalErrMsg,
                      "%s: setsockopt() IPV6_V6ONLY call failed with errno: %d", __func__, errno );
       }
   }

   if (gVmdirGlobals.dwLdapRecvTimeoutSec > 0)
   {
#ifdef _WIN32
       sTimeout = gVmdirGlobals.dwLdapRecvTimeoutSec*1000;
#else
       sTimeout.tv_sec = gVmdirGlobals.dwLdapRecvTimeoutSec;
       sTimeout.tv_usec = 0;
#endif
       if (setsockopt(*pSockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &sTimeout, sizeof(sTimeout)) < 0)
       {
#ifdef _WIN32
           errno = WSAGetLastError();
#endif
           retVal = LDAP_OPERATIONS_ERROR;
           BAIL_ON_VMDIR_ERROR_WITH_MSG( retVal, pszLocalErrMsg,
                      "%s: setsockopt() SO_RCVTIMEO failed, errno: %d", __func__, errno );
       }
   }

   retValBind = bind(*pSockfd, (struct sockaddr *) pServ_addr, addr_size);

#ifdef _WIN32
   // Add retry logic per PR 1347783
   reTries = 0;
   while (retValBind != 0 && reTries < MAX_NUM_OF_BIND_PORT_RETRIES)
   {
      errno = WSAGetLastError();
      if (errno != WSAEADDRINUSE)
      {
         break;
      }
      reTries++;
      VMDIR_LOG_WARNING( VMDIR_LOG_MASK_ALL, "%s: bind() call failed with errno: %d, re-trying (%d)",
                                   __func__, errno, reTries);
      VmDirSleep(1000);
      retValBind = bind(*pSockfd, (struct sockaddr *) pServ_addr, addr_size);
   }
#endif

   if (retValBind != 0)
   {
      retVal = LDAP_OPERATIONS_ERROR;
      //need to free socket ...
      BAIL_ON_VMDIR_ERROR_WITH_MSG( retVal, pszLocalErrMsg,
                      "%s: bind() call failed with errno: %d", __func__, errno );
   }

   if (listen(*pSockfd, LDAP_PORT_LISTEN_BACKLOG) != 0)
   {
#ifdef _WIN32
      errno = WSAGetLastError();
#endif
      retVal = LDAP_OPERATIONS_ERROR;
      BAIL_ON_VMDIR_ERROR_WITH_MSG( retVal, pszLocalErrMsg,
                      "%s: listen() call failed with errno: %d", __func__, errno );
   }

cleanup:

    VMDIR_SAFE_FREE_MEMORY(pszLocalErrMsg);
    return retVal;

error:

    if (*pSockfd >= 0)
    {
        tcp_close(*pSockfd);
        *pSockfd = -1;
    }
    VMDIR_LOG_ERROR(VMDIR_LOG_MASK_ALL, VDIR_SAFE_STRING(pszLocalErrMsg));
    goto cleanup;
}

/*
 *  We own pConnection and delete it when done.
 */
static
DWORD
ProcessAConnection(
   PVOID pArg
   )
{
   VDIR_CONNECTION *pConn = NULL;
   int            retVal = LDAP_SUCCESS;
   ber_tag_t      tag = LBER_ERROR;
   ber_len_t      len = 0;
   BerElement *   ber = NULL;
   ber_int_t      msgid = -1;
   PVDIR_OPERATION pOperation = NULL;
   int            reTries = 0;
   BOOLEAN                      bDownOpThrCount = FALSE;
   PVDIR_CONNECTION_CTX pConnCtx = NULL;

   // increment operation thread counter
   retVal = VmDirSyncCounterIncrement(gVmdirGlobals.pOperationThrSyncCounter);
   BAIL_ON_VMDIR_ERROR(retVal);
   bDownOpThrCount = TRUE;

   pConnCtx = (PVDIR_CONNECTION_CTX)pArg;
   assert(pConnCtx);

   retVal = NewConnection(pConnCtx->sockFd, &pConn, pConnCtx->pSockbuf_IO);
   if (retVal != LDAP_SUCCESS)
   {
       VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "%s: NewConnection [%d] failed with error: %d",
                        __func__, pConnCtx->sockFd, retVal);
       goto error;
   }

   while (TRUE)
   {
       if (VmDirdState() == VMDIRD_STATE_SHUTDOWN)
       {
           goto cleanup;
       }

      ber = ber_alloc();
      assert( ber != NULL);

      /* An LDAP request message looks like:
       * LDAPMessage ::= SEQUENCE {
       *                    messageID       MessageID,
       *                    protocolOp      CHOICE {
       *                       bindRequest     BindRequest,
       *                       unbindRequest   UnbindRequest,
       *                       searchRequest   SearchRequest,
       *                       ... },
       *                       controls       [0] Controls OPTIONAL }
       */


      // reset retry count
      reTries = 0;
      // Read complete LDAP request message (tag, length, and real message).
      while( reTries < MAX_NUM_OF_SOCK_READ_RETRIES )
      {
         if ((tag = ber_get_next( pConn->sb, &len, ber )) == LDAP_TAG_MESSAGE )
         {
            break;
         }

#ifdef _WIN32
         // in ber_get_next (liblber) call, sock_errset() call WSASetLastError()
         errno = WSAGetLastError();
         if ( errno == EWOULDBLOCK || errno == EAGAIN || errno == WSAETIMEDOUT)
#else
         if ( errno == EWOULDBLOCK || errno == EAGAIN)
#endif
         {
            if (gVmdirGlobals.dwLdapRecvTimeoutSec > 0 && ber->ber_len == 0)
            {
                VMDIR_LOG_INFO( LDAP_DEBUG_CONNS,
                    "%s: disconnecting peer (%s), idle > %d seconds",
                    __func__, pConn->szClientIP, gVmdirGlobals.dwLdapRecvTimeoutSec);
                retVal = LDAP_NOTICE_OF_DISCONNECT;
                BAIL_ON_VMDIR_ERROR( retVal );
            }

            //This may occur when not all data have recieved - set to EAGAIN/EWOULDBLOCK by ber_get_next,
            // and in such case ber->ber_len > 0;
            if (reTries > 0 && reTries % 5 == 0)
            {
                VMDIR_LOG_WARNING( VMDIR_LOG_MASK_ALL, "%s: ber_get_next() failed with errno = %d, peer (%s), re-trying (%d)",
                                   __func__, errno , pConn->szClientIP, reTries);
            }
            VmDirSleep(200);
            reTries++;
            continue;
         }
         // Unexpected error case.
         if (errno == 0)
         {
             VMDIR_LOG_INFO( LDAP_DEBUG_CONNS, "%s: ber_get_next() peer (%s) disconnected",
                 __func__, pConn->szClientIP);
         } else
         {
             VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "%s: ber_get_next() call failed with errno = %d peer (%s)",
                  __func__, errno, pConn->szClientIP);
         }
         retVal = LDAP_NOTICE_OF_DISCONNECT;
         BAIL_ON_VMDIR_ERROR( retVal );
      }

      // Read LDAP request messageID (tag, length (not returned since it is implicit/integer), and messageID value)
      if ( (tag = ber_get_int( ber, &msgid )) != LDAP_TAG_MSGID )
      {
         VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "ProcessAConnection: ber_get_int() call failed." );
         retVal = LDAP_NOTICE_OF_DISCONNECT;
         BAIL_ON_VMDIR_ERROR( retVal );
      }

      // Read protocolOp (tag) and length of the LDAP operation message, and leave the pointer at the beginning
      // of the LDAP operation message (to be parsed by PerformXYZ methods).
      if ( (tag = ber_peek_tag( ber, &len )) == LBER_ERROR )
      {
         VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "ProcessAConnection: ber_peek_tag() call failed." );
         retVal = LDAP_NOTICE_OF_DISCONNECT;
         BAIL_ON_VMDIR_ERROR( retVal );
      }

      retVal = VmDirNewOperation(ber, msgid, tag, pConn, &pOperation);
      if (retVal)
      {
          VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "ProcessAConnection: NewOperation() call failed." );
          retVal = LDAP_OPERATIONS_ERROR;
      }
      BAIL_ON_VMDIR_ERROR( retVal );

      //
      // If this is a multi-stage operation don't overwrite the start time if it's already set.
      //
      pConn->SuperLogRec.iStartTime = pConn->SuperLogRec.iStartTime ? pConn->SuperLogRec.iStartTime : VmDirGetTimeInMilliSec();

      switch (tag)
      {
         case LDAP_REQ_BIND:
            retVal = VmDirPerformBind(pOperation);
            if (retVal != LDAP_SASL_BIND_IN_PROGRESS)
            {
                _VmDirCollectBindSuperLog(pConn, pOperation); // ignore error
            }

            break;

         case LDAP_REQ_ADD:
            retVal = VmDirPerformAdd(pOperation);
            break;

         case LDAP_REQ_SEARCH:
            retVal = VmDirPerformSearch(pOperation);
            break;

         case LDAP_REQ_UNBIND:
            retVal = VmDirPerformUnbind(pOperation);
            break;

         case LDAP_REQ_MODIFY:
             retVal = VmDirPerformModify(pOperation);
             break;

         case LDAP_REQ_DELETE:
             retVal = VmDirPerformDelete(pOperation);
             break;

         case LDAP_REQ_MODDN:
         case LDAP_REQ_COMPARE:
         case LDAP_REQ_ABANDON:
         case LDAP_REQ_EXTENDED:
            VMDIR_LOG_INFO( VMDIR_LOG_MASK_ALL, "ProcessAConnection: Operation is not yet implemented.." );
            pOperation->ldapResult.errCode = retVal = LDAP_UNWILLING_TO_PERFORM;
            // ignore following VmDirAllocateStringA error.
            VmDirAllocateStringA( "Operation is not yet implemented.", &pOperation->ldapResult.pszErrMsg);
            VmDirSendLdapResult( pOperation );
            break;

         default:
            pOperation->ldapResult.errCode = LDAP_PROTOCOL_ERROR;
            retVal = LDAP_NOTICE_OF_DISCONNECT;
            break;
      }

      pConn->SuperLogRec.iEndTime = VmDirGetTimeInMilliSec();
      VmDirOPStatisticUpdate(tag, pConn->SuperLogRec.iEndTime - pConn->SuperLogRec.iStartTime);

      if (tag != LDAP_REQ_BIND)
      {
         VmDirLogOperation(gVmdirGlobals.pLogger, tag, pConn, pOperation->ldapResult.errCode);

         _VmDirScrubSuperLogContent(tag, &pConn->SuperLogRec);
      }

      VmDirFreeOperation(pOperation);
      pOperation = NULL;

      ber_free( ber, 1);
      ber = NULL;

      if (retVal == LDAP_NOTICE_OF_DISCONNECT) // returned as a result of protocol parsing error.
      {
         // RFC 4511, section 4.1.1: If the server receives an LDAPMessage from the client in which the LDAPMessage
         // SEQUENCE tag cannot be recognized, the messageID cannot be parsed, the tag of the protocolOp is not
         // recognized as a request, or the encoding structures or lengths of data fields are found to be incorrect,
         // then the server **SHOULD** return the Notice of Disconnection, with the resultCode
         // set to protocolError, and **MUST** immediately terminate the LDAP session as described in Section 5.3.

         goto cleanup;
      }
   }

cleanup:
   if (retVal == LDAP_NOTICE_OF_DISCONNECT)
   {
      // Optionally send Notice of Disconnection with rs->err.
   }
   if (ber != NULL)
   {
      ber_free( ber, 1 );
   }
    VmDirDeleteConnection(&pConn);
    VMDIR_SAFE_FREE_MEMORY(pConnCtx);
    VmDirFreeOperation(pOperation);

    if (bDownOpThrCount)
    {
        VmDirSyncCounterDecrement(gVmdirGlobals.pOperationThrSyncCounter);
    }

    _VmDirFlowCtrlThrExit();

    // TODO: should we return dwError ?
    return 0;

error:
    goto cleanup;
}
Ejemplo n.º 3
0
DWORD
VmDirRESTObjectPut(
    void*   pIn,
    void**  ppOut
    )
{
    DWORD   dwError = 0;
    PSTR    pszTenant = NULL;
    PVDIR_ENTRY pObj = NULL;
    PVDIR_REST_OPERATION    pRestOp = NULL;
    PVDIR_OPERATION         pAddOp = NULL;

    if (!pIn)
    {
        dwError = VMDIR_ERROR_INVALID_PARAMETER;
        BAIL_ON_VMDIR_ERROR(dwError);
    }

    pRestOp = (PVDIR_REST_OPERATION)pIn;

    // put request must have subpath (=objectpath)
    if (IsNullOrEmptyString(pRestOp->pszSubPath))
    {
        dwError = VMDIR_ERROR_INVALID_REQUEST;
        BAIL_ON_VMDIR_ERROR(dwError);
    }

    dwError = VmDirExternalOperationCreate(
            NULL, -1, LDAP_REQ_ADD, pRestOp->pConn, &pAddOp);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTGetObjectTenantParam(pRestOp, &pszTenant);
    BAIL_ON_VMDIR_ERROR(dwError)

    dwError = VmDirRESTDecodeObject(
            pRestOp->pjInput, pRestOp->pszSubPath, pszTenant, &pObj);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirResetAddRequestEntry(pAddOp, pObj);
    BAIL_ON_VMDIR_ERROR(dwError);
    pObj = NULL;

    dwError = VmDirMLAdd(pAddOp);
    BAIL_ON_VMDIR_ERROR(dwError);

cleanup:
    VMDIR_SET_REST_RESULT(pRestOp, pAddOp, dwError, NULL);
    VMDIR_SAFE_FREE_MEMORY(pszTenant);
    VmDirFreeOperation(pAddOp);
    VmDirFreeEntry(pObj);
    return dwError;

error:
    VMDIR_LOG_ERROR(
            VMDIR_LOG_MASK_ALL,
            "%s failed, error (%d)",
            __FUNCTION__,
            dwError);

    goto cleanup;
}
Ejemplo n.º 4
0
DWORD
VmDirRESTObjectDelete(
    void*   pIn,
    void**  ppOut
    )
{
    DWORD   dwError = 0;
    PSTR    pszTenant = NULL;
    PSTR    pszDN = NULL;
    BOOLEAN bRecursive = FALSE;
    PVDIR_REST_OPERATION    pRestOp = NULL;
    PVDIR_OPERATION         pDeleteOp = NULL;

    if (!pIn)
    {
        dwError = VMDIR_ERROR_INVALID_PARAMETER;
        BAIL_ON_VMDIR_ERROR(dwError);
    }

    pRestOp = (PVDIR_REST_OPERATION)pIn;

    dwError = VmDirRESTGetBoolParam(pRestOp, "recursive", &bRecursive, FALSE);
    BAIL_ON_VMDIR_ERROR(dwError);

    // TODO implement recursive option
    dwError = bRecursive ? VMDIR_ERROR_UNWILLING_TO_PERFORM : 0;
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirExternalOperationCreate(
            NULL, -1, LDAP_REQ_DELETE, pRestOp->pConn, &pDeleteOp);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTGetObjectTenantParam(pRestOp, &pszTenant);
    BAIL_ON_VMDIR_ERROR(dwError)

    dwError = VmDirRESTDecodeObjectPathToDN(
            pRestOp->pszSubPath, pszTenant, &pszDN);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirStringToBervalContent(pszDN, &pDeleteOp->reqDn);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirStringToBervalContent(pszDN, &pDeleteOp->request.deleteReq.dn);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirMLDelete(pDeleteOp);
    BAIL_ON_VMDIR_ERROR(dwError);

cleanup:
    VMDIR_SET_REST_RESULT(pRestOp, pDeleteOp, dwError, NULL);
    VMDIR_SAFE_FREE_MEMORY(pszTenant);
    VMDIR_SAFE_FREE_MEMORY(pszDN);
    VmDirFreeOperation(pDeleteOp);
    return dwError;

error:
    VMDIR_LOG_ERROR(
            VMDIR_LOG_MASK_ALL,
            "%s failed, error (%d)",
            __FUNCTION__,
            dwError);

    goto cleanup;
}
Ejemplo n.º 5
0
DWORD
VmDirRESTObjectPatch(
    void*   pIn,
    void**  ppOut
    )
{
    DWORD   dwError = 0;
    PSTR    pszTenant = NULL;
    PSTR    pszDN = NULL;
    PVDIR_REST_OPERATION    pRestOp = NULL;
    PVDIR_OPERATION         pModifyOp = NULL;

    if (!pIn)
    {
        dwError = VMDIR_ERROR_INVALID_PARAMETER;
        BAIL_ON_VMDIR_ERROR(dwError);
    }

    pRestOp = (PVDIR_REST_OPERATION)pIn;

    dwError = VmDirExternalOperationCreate(
            NULL, -1, LDAP_REQ_MODIFY, pRestOp->pConn, &pModifyOp);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTGetObjectTenantParam(pRestOp, &pszTenant);
    BAIL_ON_VMDIR_ERROR(dwError)

    dwError = VmDirRESTDecodeObjectPathToDN(
            pRestOp->pszSubPath, pszTenant, &pszDN);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirStringToBervalContent(pszDN, &pModifyOp->reqDn);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirStringToBervalContent(pszDN, &pModifyOp->request.modifyReq.dn);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTDecodeObjectMods(
            pRestOp->pjInput,
            pszTenant,
            &pModifyOp->request.modifyReq.mods,
            &pModifyOp->request.modifyReq.numMods);
    BAIL_ON_VMDIR_ERROR(dwError);

    if (pRestOp->pszHeaderIfMatch)
    {
        dwError = _VmDirRESTCreateCondWriteCtrl(
                    pModifyOp,
                    pszTenant,
                    pRestOp->pszHeaderIfMatch);
        BAIL_ON_VMDIR_ERROR(dwError);
    }

    dwError = VmDirMLModify(pModifyOp);
    BAIL_ON_VMDIR_ERROR(dwError);

cleanup:
    VMDIR_SET_REST_RESULT(pRestOp, pModifyOp, dwError, NULL);
    VMDIR_SAFE_FREE_MEMORY(pszTenant);
    VMDIR_SAFE_FREE_MEMORY(pszDN);
    VmDirFreeOperation(pModifyOp);
    return dwError;

error:
    VMDIR_LOG_ERROR(
            VMDIR_LOG_MASK_ALL,
            "%s failed, error (%d)",
            __FUNCTION__,
            dwError);

    goto cleanup;
}
Ejemplo n.º 6
0
DWORD
VmDirRESTObjectGet(
    void*   pIn,
    void**  ppOut
    )
{
    DWORD   dwError = 0;
    PSTR    pszTenant = NULL;
    PSTR    pszDN = NULL;
    size_t  skipped = 0;
    PVDIR_LDAP_CONTROL  pPagedResultsCtrl = NULL;
    json_t*             pjResult = NULL;
    PVDIR_REST_OPERATION    pRestOp = NULL;
    PVDIR_OPERATION         pSearchOp = NULL;

    if (!pIn)
    {
        dwError = VMDIR_ERROR_INVALID_PARAMETER;
        BAIL_ON_VMDIR_ERROR(dwError);
    }

    pRestOp = (PVDIR_REST_OPERATION)pIn;

    dwError = VmDirExternalOperationCreate(
            NULL, -1, LDAP_REQ_SEARCH, pRestOp->pConn, &pSearchOp);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTGetObjectGetParams(
            pRestOp,
            &pszTenant,
            &pSearchOp->request.searchReq.scope,
            &pSearchOp->request.searchReq.filter,
            &pSearchOp->request.searchReq.attrs,
            &pPagedResultsCtrl);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTDecodeObjectPathToDN(
            pRestOp->pszSubPath, pszTenant, &pszDN);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirStringToBervalContent(pszDN, &pSearchOp->reqDn);
    BAIL_ON_VMDIR_ERROR(dwError);

    pSearchOp->showPagedResultsCtrl = pPagedResultsCtrl;
    pSearchOp->request.searchReq.bStoreRsltInMem = TRUE;

    dwError = VmDirMLSearch(pSearchOp);
    BAIL_ON_VMDIR_ERROR(dwError);

    // set operation result
    dwError = VmDirRESTEncodeObjectArray(
            &pSearchOp->internalSearchEntryArray,
            pSearchOp->request.searchReq.attrs,
            pszTenant,
            &pjResult,
            &skipped);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTResultSetObjData(pRestOp->pResult, "result", pjResult);
    BAIL_ON_VMDIR_ERROR(dwError);

    dwError = VmDirRESTResultSetIntData(
            pRestOp->pResult,
            "result_count",
            pSearchOp->internalSearchEntryArray.iSize - skipped);
    BAIL_ON_VMDIR_ERROR(dwError);

    if (pPagedResultsCtrl)
    {
        VDIR_PAGED_RESULT_CONTROL_VALUE* pCtrl =
                &pPagedResultsCtrl->value.pagedResultCtrlVal;

        if (!IsNullOrEmptyString(pCtrl->cookie))
        {
            dwError = VmDirRESTResultSetStrData(
                    pRestOp->pResult, "paged_results_cookie", pCtrl->cookie);
            BAIL_ON_VMDIR_ERROR(dwError);
        }
    }

cleanup:
    VMDIR_SET_REST_RESULT(pRestOp, pSearchOp, dwError, NULL);
    VMDIR_SAFE_FREE_MEMORY(pPagedResultsCtrl);
    VMDIR_SAFE_FREE_MEMORY(pszTenant);
    VMDIR_SAFE_FREE_MEMORY(pszDN);
    VmDirFreeOperation(pSearchOp);
    return dwError;

error:
    VMDIR_LOG_ERROR(
            VMDIR_LOG_MASK_ALL,
            "%s failed, error (%d)",
            __FUNCTION__,
            dwError);

    if (pjResult)
    {
        json_decref(pjResult);
    }
    goto cleanup;
}