/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
/*
 * Copyright (c) 2014-2020 Intel, Inc.  All rights reserved.
 * Copyright (c) 2016      Mellanox Technologies, Inc.
 *                         All rights reserved.
 * Copyright (c) 2016      IBM Corporation.  All rights reserved.
 * Copyright (c) 2019      Research Organization for Information Science
 *                         and Technology (RIST).  All rights reserved.
 * Copyright (c) 2021-2022 Nanook Consulting.  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */
#include "src/include/pmix_config.h"

#include "src/include/pmix_socket_errno.h"
#include "src/include/pmix_stdint.h"

#include "include/pmix.h"
#include "pmix_common.h"
#include "include/pmix_server.h"

#include "src/mca/bfrops/bfrops.h"
#include "src/mca/psec/psec.h"
#include "src/mca/ptl/ptl.h"
#include "src/threads/pmix_threads.h"
#include "src/util/pmix_argv.h"
#include "src/util/pmix_error.h"
#include "src/util/pmix_output.h"

#include "src/client/pmix_client_ops.h"
#include "src/include/pmix_globals.h"
#include "src/server/pmix_server_ops.h"

static void getcbfunc(struct pmix_peer_t *peer, pmix_ptl_hdr_t *hdr,
                      pmix_buffer_t *buf, void *cbdata)
{
    pmix_query_caddy_t *cd = (pmix_query_caddy_t *) cbdata;
    pmix_status_t rc, status = PMIX_ERR_UNPACK_FAILURE;
    int cnt;
    pmix_byte_object_t cred;
    pmix_info_t *info = NULL;
    size_t ninfo = 0;
    PMIX_HIDE_UNUSED_PARAMS(hdr);

    pmix_output_verbose(2, pmix_globals.debug_output,
                        "pmix:security cback from server with %d bytes", (int) buf->bytes_used);

    /* a zero-byte buffer indicates that this recv is being
     * completed due to a lost connection */
    if (PMIX_BUFFER_IS_EMPTY(buf)) {
        /* release the caller */
        if (NULL != cd->credcbfunc) {
            cd->credcbfunc(PMIX_ERR_COMM_FAILURE, NULL, NULL, 0, cd->cbdata);
        }
        PMIX_RELEASE(cd);
        return;
    }
    PMIX_BYTE_OBJECT_CONSTRUCT(&cred);

    /* unpack the status */
    cnt = 1;
    PMIX_BFROPS_UNPACK(rc, peer, buf, &status, &cnt, PMIX_STATUS);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        goto complete;
    }
    if (PMIX_SUCCESS != status) {
        goto complete;
    }

    /* unpack the credential */
    cnt = 1;
    PMIX_BFROPS_UNPACK(rc, peer, buf, &cred, &cnt, PMIX_BYTE_OBJECT);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        goto complete;
    }

    /* unpack any returned info */
    cnt = 1;
    PMIX_BFROPS_UNPACK(rc, peer, buf, &ninfo, &cnt, PMIX_SIZE);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        goto complete;
    }
    if (0 < ninfo) {
        PMIX_INFO_CREATE(info, ninfo);
        cnt = ninfo;
        PMIX_BFROPS_UNPACK(rc, peer, buf, info, &cnt, PMIX_INFO);
        if (PMIX_SUCCESS != rc) {
            PMIX_ERROR_LOG(rc);
            goto complete;
        }
    }

complete:
    pmix_output_verbose(2, pmix_globals.debug_output, "pmix:security cback from server releasing");
    /* release the caller */
    if (NULL != cd->credcbfunc) {
        cd->credcbfunc(status, &cred, info, ninfo, cd->cbdata);
    }
    PMIX_BYTE_OBJECT_DESTRUCT(&cred);
    if (NULL != info) {
        PMIX_INFO_FREE(info, ninfo);
    }
    PMIX_RELEASE(cd);
}

static void mycdcb(pmix_status_t status, pmix_byte_object_t *credential,
                   pmix_info_t info[], size_t ninfo, void *cbdata)
{
    pmix_query_caddy_t *cb = (pmix_query_caddy_t *) cbdata;
    PMIX_HIDE_UNUSED_PARAMS(info, ninfo);

    PMIX_ACQUIRE_OBJECT(cb);
    cb->status = status;
    if (PMIX_SUCCESS == status && NULL != credential) {
        cb->bo.bytes = malloc(credential->size);
        memcpy(cb->bo.bytes, credential->bytes, credential->size);
        cb->bo.size = credential->size;
    }
    PMIX_WAKEUP_THREAD(&cb->lock);
}

PMIX_EXPORT pmix_status_t PMIx_Get_credential(const pmix_info_t info[], size_t ninfo,
                                              pmix_byte_object_t *credential)
{
    pmix_query_caddy_t cb;
    pmix_status_t rc;

    PMIX_CONSTRUCT(&cb, pmix_query_caddy_t);
    rc = PMIx_Get_credential_nb(info, ninfo, mycdcb, &cb);
    if (PMIX_SUCCESS == rc) {
        PMIX_WAIT_THREAD(&cb.lock);
        rc = cb.status;
        if (NULL != cb.bo.bytes) {
            credential->bytes = malloc(cb.bo.size);
            memcpy(credential->bytes, cb.bo.bytes, cb.bo.size);
            credential->size = cb.bo.size;
        }
    }
    PMIX_DESTRUCT(&cb);
    return rc;
}

PMIX_EXPORT pmix_status_t PMIx_Get_credential_nb(const pmix_info_t info[], size_t ninfo,
                                                 pmix_credential_cbfunc_t cbfunc, void *cbdata)
{
    pmix_buffer_t *msg;
    pmix_cmd_t cmd = PMIX_GET_CREDENTIAL_CMD;
    pmix_status_t rc;
    pmix_query_caddy_t *cb;
    pmix_byte_object_t cred;
    pmix_info_t *results = NULL;
    size_t nresults = 0;

    PMIX_ACQUIRE_THREAD(&pmix_global_lock);

    pmix_output_verbose(2, pmix_globals.debug_output, "pmix: Get_credential called with %d info",
                        (int) ninfo);

    if (pmix_globals.init_cntr <= 0) {
        PMIX_RELEASE_THREAD(&pmix_global_lock);
        return PMIX_ERR_INIT;
    }

    /* if we are the server */
    if (PMIX_PEER_IS_SERVER(pmix_globals.mypeer) && !PMIX_PEER_IS_LAUNCHER(pmix_globals.mypeer)) {
        PMIX_RELEASE_THREAD(&pmix_global_lock);
        /* if the host doesn't support this operation,
         * see if we can generate it ourselves */
        if (NULL == pmix_host_server.get_credential) {
            PMIX_BYTE_OBJECT_CONSTRUCT(&cred);
            PMIX_PSEC_CREATE_CRED(rc, pmix_globals.mypeer, info, ninfo, &results, &nresults, &cred);
            if (PMIX_SUCCESS == rc) {
                /* pass it back in the callback function */
                if (NULL != cbfunc) {
                    cbfunc(PMIX_SUCCESS, &cred, results, nresults, cbdata);
                    if (NULL != results) {
                        PMIX_INFO_FREE(results, nresults);
                    }
                    PMIX_BYTE_OBJECT_DESTRUCT(&cred);
                }
            }
            return rc;
        }
        /* the host is available, so let them try to create it */
        pmix_output_verbose(2, pmix_globals.debug_output, "pmix:get_credential handed to RM");
        rc = pmix_host_server.get_credential(&pmix_globals.myid, info, ninfo, cbfunc, cbdata);
        return rc;
    }

    /* if we are a client or tool and we aren't connected, see
     * if one of our internal plugins is capable of meeting the request */
    if (!pmix_globals.connected) {
        PMIX_RELEASE_THREAD(&pmix_global_lock);
        PMIX_BYTE_OBJECT_CONSTRUCT(&cred);
        PMIX_PSEC_CREATE_CRED(rc, pmix_globals.mypeer, info, ninfo, &results, &nresults, &cred);
        if (PMIX_SUCCESS == rc) {
            /* pass it back in the callback function */
            if (NULL != cbfunc) {
                cbfunc(PMIX_SUCCESS, &cred, results, nresults, cbdata);
                if (NULL != results) {
                    PMIX_INFO_FREE(results, nresults);
                }
                PMIX_BYTE_OBJECT_DESTRUCT(&cred);
            }
        }
        return rc;
    }
    PMIX_RELEASE_THREAD(&pmix_global_lock);

    /* if we are a client, then relay this request to the server */
    msg = PMIX_NEW(pmix_buffer_t);
    /* pack the cmd */
    PMIX_BFROPS_PACK(rc, pmix_client_globals.myserver, msg, &cmd, 1, PMIX_COMMAND);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        PMIX_RELEASE(msg);
        return rc;
    }

    /* pack the directives */
    PMIX_BFROPS_PACK(rc, pmix_client_globals.myserver, msg, &ninfo, 1, PMIX_SIZE);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        PMIX_RELEASE(msg);
        return rc;
    }
    if (0 < ninfo) {
        PMIX_BFROPS_PACK(rc, pmix_client_globals.myserver, msg, info, ninfo, PMIX_INFO);
        if (PMIX_SUCCESS != rc) {
            PMIX_ERROR_LOG(rc);
            PMIX_RELEASE(msg);
            return rc;
        }
    }

    /* create a callback object as we need to pass it to the
     * recv routine so we know which callback to use when
     * the return message is recvd */
    cb = PMIX_NEW(pmix_query_caddy_t);
    cb->credcbfunc = cbfunc;
    cb->cbdata = cbdata;

    /* push the message into our event base to send to the server */
    PMIX_PTL_SEND_RECV(rc, pmix_client_globals.myserver, msg, getcbfunc, (void *) cb);
    if (PMIX_SUCCESS != rc) {
        PMIX_RELEASE(msg);
        PMIX_RELEASE(cb);
    }

    return rc;
}

static void valid_cbfunc(struct pmix_peer_t *peer, pmix_ptl_hdr_t *hdr,
                         pmix_buffer_t *buf, void *cbdata)
{
    pmix_query_caddy_t *cd = (pmix_query_caddy_t *) cbdata;
    pmix_status_t rc, status = PMIX_ERR_UNPACK_FAILURE;
    int cnt;
    pmix_info_t *info = NULL;
    size_t ninfo = 0;
    PMIX_HIDE_UNUSED_PARAMS(hdr);

    pmix_output_verbose(2, pmix_globals.debug_output,
                        "pmix:security cback from server with %d bytes", (int) buf->bytes_used);

    /* a zero-byte buffer indicates that this recv is being
     * completed due to a lost connection */
    if (PMIX_BUFFER_IS_EMPTY(buf)) {
        /* release the caller */
        if (NULL != cd->validcbfunc) {
            cd->validcbfunc(PMIX_ERR_COMM_FAILURE, NULL, 0, cd->cbdata);
        }
        PMIX_RELEASE(cd);
        return;
    }

    /* unpack the status */
    cnt = 1;
    PMIX_BFROPS_UNPACK(rc, peer, buf, &status, &cnt, PMIX_STATUS);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        goto complete;
    }
    if (PMIX_SUCCESS != status) {
        goto complete;
    }

    /* unpack any returned info */
    cnt = 1;
    PMIX_BFROPS_UNPACK(rc, peer, buf, &ninfo, &cnt, PMIX_SIZE);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        goto complete;
    }
    if (0 < ninfo) {
        PMIX_INFO_CREATE(info, ninfo);
        cnt = ninfo;
        PMIX_BFROPS_UNPACK(rc, peer, buf, info, &cnt, PMIX_INFO);
        if (PMIX_SUCCESS != rc) {
            PMIX_ERROR_LOG(rc);
            goto complete;
        }
    }

complete:
    pmix_output_verbose(2, pmix_globals.debug_output, "pmix:security cback from server releasing");
    /* release the caller */
    if (NULL != cd->validcbfunc) {
        cd->validcbfunc(status, info, ninfo, cd->cbdata);
    }
    if (NULL != info) {
        PMIX_INFO_FREE(info, ninfo);
    }
    PMIX_RELEASE(cd);
}

static void myvalcb(pmix_status_t status, pmix_info_t info[], size_t ninfo, void *cbdata)
{
    pmix_query_caddy_t *cb = (pmix_query_caddy_t *) cbdata;
    size_t n;

    PMIX_ACQUIRE_OBJECT(cb);
    cb->status = status;
    if (PMIX_SUCCESS == status && NULL != info) {
        cb->ninfo = ninfo;
        PMIX_INFO_CREATE(cb->info, cb->ninfo);
        for (n = 0; n < ninfo; n++) {
            PMIX_INFO_XFER(&cb->info[n], &info[n]);
        }
    }
    PMIX_WAKEUP_THREAD(&cb->lock);
}

PMIX_EXPORT pmix_status_t PMIx_Validate_credential(const pmix_byte_object_t *cred,
                                                   const pmix_info_t directives[], size_t ndirs,
                                                   pmix_info_t *results[], size_t *nresults)
{
    pmix_query_caddy_t cb;
    pmix_status_t rc;

    PMIX_CONSTRUCT(&cb, pmix_query_caddy_t);
    rc = PMIx_Validate_credential_nb(cred, directives, ndirs, myvalcb, &cb);
    if (PMIX_SUCCESS == rc) {
        PMIX_WAIT_THREAD(&cb.lock);
        rc = cb.status;
        if (NULL != cb.info) {
            *results = cb.info;
            *nresults = cb.ninfo;
            cb.info = NULL;
            cb.ninfo = 0;
        }
    }
    PMIX_DESTRUCT(&cb);
    return rc;
}

PMIX_EXPORT pmix_status_t PMIx_Validate_credential_nb(const pmix_byte_object_t *cred,
                                                      const pmix_info_t directives[], size_t ndirs,
                                                      pmix_validation_cbfunc_t cbfunc, void *cbdata)
{
    pmix_buffer_t *msg;
    pmix_cmd_t cmd = PMIX_VALIDATE_CRED_CMD;
    pmix_status_t rc;
    pmix_query_caddy_t *cb;
    pmix_info_t *results = NULL;
    size_t nresults = 0;

    PMIX_ACQUIRE_THREAD(&pmix_global_lock);

    pmix_output_verbose(2, pmix_globals.debug_output, "pmix: monitor called");

    if (pmix_globals.init_cntr <= 0) {
        PMIX_RELEASE_THREAD(&pmix_global_lock);
        return PMIX_ERR_INIT;
    }

    /* if we are the server */
    if (PMIX_PEER_IS_SERVER(pmix_globals.mypeer) && !PMIX_PEER_IS_LAUNCHER(pmix_globals.mypeer)) {
        PMIX_RELEASE_THREAD(&pmix_global_lock);
        /* if the host doesn't support this operation,
         * see if we can validate it ourselves */
        if (NULL == pmix_host_server.validate_credential) {
            PMIX_PSEC_VALIDATE_CRED(rc, pmix_globals.mypeer, directives, ndirs, &results, &nresults,
                                    cred);
            if (PMIX_SUCCESS == rc) {
                /* pass it back in the callback function */
                if (NULL != cbfunc) {
                    cbfunc(PMIX_SUCCESS, results, nresults, cbdata);
                    if (NULL != results) {
                        PMIX_INFO_FREE(results, nresults);
                    }
                }
            }
            return rc;
        }
        /* the host is available, so let them try to validate it */
        pmix_output_verbose(2, pmix_globals.debug_output, "pmix:get_credential handed to RM");
        rc = pmix_host_server.validate_credential(&pmix_globals.myid, cred, directives, ndirs,
                                                  cbfunc, cbdata);
        return rc;
    }

    /* if we are a client or tool and we aren't connected, see
     * if one of our internal plugins is capable of meeting the request */
    if (!pmix_globals.connected) {
        PMIX_RELEASE_THREAD(&pmix_global_lock);
        PMIX_PSEC_VALIDATE_CRED(rc, pmix_globals.mypeer, directives, ndirs, &results, &nresults,
                                cred);
        if (PMIX_SUCCESS == rc) {
            /* pass it back in the callback function */
            if (NULL != cbfunc) {
                cbfunc(PMIX_SUCCESS, results, nresults, cbdata);
                if (NULL != results) {
                    PMIX_INFO_FREE(results, nresults);
                }
            }
        }
        return rc;
    }
    PMIX_RELEASE_THREAD(&pmix_global_lock);

    /* if we are a client, then relay this request to the server */
    msg = PMIX_NEW(pmix_buffer_t);
    /* pack the cmd */
    PMIX_BFROPS_PACK(rc, pmix_client_globals.myserver, msg, &cmd, 1, PMIX_COMMAND);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        PMIX_RELEASE(msg);
        return rc;
    }

    /* pack the credential */
    PMIX_BFROPS_PACK(rc, pmix_client_globals.myserver, msg, cred, 1, PMIX_BYTE_OBJECT);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        PMIX_RELEASE(msg);
        return rc;
    }

    /* pack the directives */
    PMIX_BFROPS_PACK(rc, pmix_client_globals.myserver, msg, &ndirs, 1, PMIX_SIZE);
    if (PMIX_SUCCESS != rc) {
        PMIX_ERROR_LOG(rc);
        PMIX_RELEASE(msg);
        return rc;
    }
    if (0 < ndirs) {
        PMIX_BFROPS_PACK(rc, pmix_client_globals.myserver, msg, directives, ndirs, PMIX_INFO);
        if (PMIX_SUCCESS != rc) {
            PMIX_ERROR_LOG(rc);
            PMIX_RELEASE(msg);
            return rc;
        }
    }

    /* create a callback object as we need to pass it to the
     * recv routine so we know which callback to use when
     * the return message is recvd */
    cb = PMIX_NEW(pmix_query_caddy_t);
    cb->validcbfunc = cbfunc;
    cb->cbdata = cbdata;

    /* push the message into our event base to send to the server */
    PMIX_PTL_SEND_RECV(rc, pmix_client_globals.myserver, msg, valid_cbfunc, (void *) cb);
    if (PMIX_SUCCESS != rc) {
        PMIX_RELEASE(msg);
        PMIX_RELEASE(cb);
    }

    return rc;
}
