/*
 * ctrlchannel.c -- control channel implementation
 *
 * (c) Copyright IBM Corporation 2015.
 *
 * Author: Stefan Berger <stefanb@us.ibm.com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the names of the IBM Corporation nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <stdint.h>
#include <stddef.h>
#include <time.h>
#include <poll.h>

#include <libtpms/tpm_library.h>
#include <libtpms/tpm_error.h>
#include <libtpms/tpm_tis.h>
#include <libtpms/tpm_memory.h>

#include "sys_dependencies.h"
#include "ctrlchannel.h"
#include "logging.h"
#include "tpm_ioctl.h"
#include "tpmlib.h"
#include "swtpm_nvstore.h"
#include "locality.h"
#include "mainloop.h"
#include "utils.h"
#include "swtpm_debug.h"
#include "swtpm_utils.h"

/* local variables */

struct ctrlchannel {
    int fd;
    int clientfd;
    char *sockpath;
};

struct ctrlchannel *ctrlchannel_new(int fd, bool is_client,
                                    const char *sockpath)
{
    struct ctrlchannel *cc = calloc(1, sizeof(struct ctrlchannel));

    if (!cc) {
        logprintf(STDERR_FILENO, "Out of memory");
        return NULL;
    }

    if (sockpath) {
        cc->sockpath = strdup(sockpath);
        if (!cc->sockpath) {
            logprintf(STDERR_FILENO, "Out of memory");
            free(cc);
            return NULL;
        }
    }

    cc->fd = cc->clientfd = -1;
    if (is_client)
        cc->clientfd = fd;
    else
        cc->fd = fd;

    return cc;
}

int ctrlchannel_get_fd(struct ctrlchannel *cc)
{
    if (!cc)
        return -1;

    return cc->fd;
}

int ctrlchannel_get_client_fd(struct ctrlchannel *cc)
{
    if (!cc)
        return -1;

    return cc->clientfd;
}

int ctrlchannel_set_client_fd(struct ctrlchannel *cc, int fd)
{
    int clientfd;

    if (!cc)
        return -1;

    clientfd = cc->clientfd;
    cc->clientfd = fd;

    return clientfd;
}

static int ctrlchannel_return_state(ptm_getstate *pgs, int fd,
                                    struct mainLoopParams *mlp)
{
    uint32_t blobtype = be32toh(pgs->u.req.type);
    const char *blobname;
    uint32_t tpm_number = 0;
    unsigned char *blob = NULL;
    uint32_t blob_length = 0, return_length;
    TPM_BOOL is_encrypted = 0;
    TPM_BOOL decrypt =
        (be32toh(pgs->u.req.state_flags) & PTM_STATE_FLAG_DECRYPTED) != 0;
    TPM_RESULT res = 0;
    uint32_t offset = be32toh(pgs->u.req.offset);
    ptm_getstate pgs_res;
    uint32_t state_flags;
    struct iovec iov[2];
    int iovcnt, n;

    blobname = tpmlib_get_blobname(blobtype);
    if (!blobname)
        res = TPM_FAIL;

    if (res == 0 && blobtype == PTM_BLOB_TYPE_VOLATILE)
        res = SWTPM_NVRAM_Store_Volatile();

    if (res == 0)
        res = SWTPM_NVRAM_GetStateBlob(&blob, &blob_length,
                                       tpm_number, blobname, decrypt,
                                       &is_encrypted);

    /* make sure the volatile state file is gone */
    if (blobtype == PTM_BLOB_TYPE_VOLATILE)
        SWTPM_NVRAM_DeleteName(tpm_number, blobname, FALSE);

    if (offset < blob_length) {
        return_length = blob_length - offset;
    } else {
        return_length = 0;
    }

    state_flags = (is_encrypted) ? PTM_STATE_FLAG_ENCRYPTED : 0;
    pgs_res.u.resp.tpm_result = htobe32(res);
    pgs_res.u.resp.state_flags = htobe32(state_flags);
    pgs_res.u.resp.totlength = htobe32(return_length);
    pgs_res.u.resp.length = htobe32(return_length);

    iov[0].iov_base = &pgs_res;
    iov[0].iov_len = offsetof(ptm_getstate, u.resp.data);
    iovcnt = 1;

    SWTPM_PrintAll(" Ctrl Rsp:", " ", iov[0].iov_base, iov[0].iov_len);

    if (res == 0 && return_length) {
        iov[1].iov_base = &blob[offset];
        iov[1].iov_len = return_length;
        iovcnt = 2;

        SWTPM_PrintAll(" Ctrl Rsp Continued:", " ",
                       iov[1].iov_base, min(iov[1].iov_len, 1024));
    }

    n = writev_full(fd, iov, iovcnt);
    if (n < 0) {
        logprintf(STDERR_FILENO,
                  "Error: Could not send response: %s\n", strerror(errno));
        close(fd);
        fd = -1;
    }

    free(blob);

    if (fd >= 0 && blobtype == PTM_BLOB_TYPE_SAVESTATE)
        mainloop_unlock_nvram(mlp, DEFAULT_LOCKING_RETRIES);

    return fd;
}

static int ctrlchannel_receive_state(ptm_setstate *pss, ssize_t n, int fd)
{
    uint32_t blobtype = be32toh(pss->u.req.type);
    uint32_t tpm_number = 0;
    unsigned char *blob = NULL;
    uint32_t blob_length = be32toh(pss->u.req.length);
    uint32_t remain = blob_length, offset = 0;
    TPM_RESULT res;
    uint32_t flags = be32toh(pss->u.req.state_flags);
    TPM_BOOL is_encrypted = (flags & PTM_STATE_FLAG_ENCRYPTED) != 0;

    blob = malloc(blob_length);
    if (!blob) {
        logprintf(STDERR_FILENO,
                  "Could not allocated %u bytes.\n", blob_length);
        res = TPM_FAIL;
        goto err_send_resp;
    }

    n -= offsetof(ptm_setstate, u.req.data);
    /* n holds the number of available data bytes */

    while (true) {
        if (n < 0 || (uint32_t)n > remain) {
            res = TPM_BAD_PARAMETER;
            goto err_send_resp;
        }
        memcpy(&blob[offset], pss->u.req.data, n);
        offset += n;
        remain -= n;
        if (remain) {
            n = read_eintr(fd, pss->u.req.data, sizeof(pss->u.req.data));
            if (n < 0) {
                close(fd);
                fd = -1;
                goto err_fd_broken;
            } else if (n == 0) {
                res = TPM_BAD_PARAMETER;
                goto err_send_resp;
            }
        } else {
            break;
        }
    }

    res = SWTPM_NVRAM_SetStateBlob(blob, blob_length, is_encrypted,
                                   tpm_number, blobtype);

err_send_resp:
    pss->u.resp.tpm_result = htobe32(res);
    n = write_full(fd, pss, sizeof(pss->u.resp.tpm_result));
    if (n < 0) {
        logprintf(STDERR_FILENO,
                  "Error: Could not send response: %s\n", strerror(errno));
        close(fd);
        fd = -1;
    }

err_fd_broken:

    free(blob);

    return fd;
}

/* timespec_diff: calculate difference between two timespecs
 *
 * @end: end time
 * @start: start time; must be earlier than @end
 * @diff: result
 *
 * This function will return a negative tv_sec in result, if
 * @end is earlier than @start, the time difference otherwise.
 */
static void timespec_diff(struct timespec *end,
                          struct timespec *start,
                          struct timespec *diff)
{
    diff->tv_nsec = end->tv_nsec - start->tv_nsec;
    diff->tv_sec = end->tv_sec - start->tv_sec;
    if (diff->tv_nsec < 0) {
        diff->tv_nsec += 1E9;
        diff->tv_sec -= 1;
    }
}

struct input {
    uint32_t cmd;
    /* ptm_hdata is the largest buffer to receive */
    uint8_t body[sizeof(ptm_hdata)];
};

/*
 * ctrlchannel_recv_cmd: Receive a command on the control channel
 *
 * @fd: file descriptor for control channel
 * @msg: prepared msghdr struct for receiveing data with single
 *       msg_iov.
 *
 * This function returns 0 or a negative number if an error receiving
 * the command occurred, including a timeout. In case of success,
 * the nunber of bytes received is returned.
 */
static ssize_t ctrlchannel_recv_cmd(int fd,
                                    struct msghdr *msg)
{
    ssize_t n;
    size_t recvd = 0;
    size_t needed = offsetof(struct input, body);
    struct input *input = (struct input *)msg->msg_iov[0].iov_base;
    struct pollfd pollfd =  {
        .fd = fd,
        .events = POLLIN,
    };
    struct timespec deadline, now, timeout;
    int to;
    size_t buffer_len = msg->msg_iov[0].iov_len;
    /* Read-write */
    ptm_init *init_p;
    ptm_reset_est *pre;
    ptm_hdata *phd;
    ptm_getstate *pgs;
    ptm_setstate *pss;
    ptm_loc *pl;
    const void *msg_iov = msg->msg_iov;

    clock_gettime(CLOCK_REALTIME, &deadline);

    /* maximum allowed time is 500ms to receive everything */
    deadline.tv_nsec += 500 * 1E6;
    if (deadline.tv_nsec >= 1E9) {
        deadline.tv_nsec -= 1E9;
        deadline.tv_sec += 1;
    }

    while (recvd < buffer_len) {
        if (!recvd) {
            n = recvmsg(fd, msg, 0);
            /* address a coverity issue by validating msg */
            if (msg_iov != msg->msg_iov ||
                msg->msg_iov[0].iov_base != input ||
                msg->msg_iov[0].iov_len > buffer_len)
                return -1;
        } else
            n = read_eintr(fd, msg->msg_iov[0].iov_base + recvd, buffer_len - recvd);
        if (n <= 0)
            return n;
        recvd += n;
        /* we need to at least see the cmd */
        if (recvd < offsetof(struct input, body))
            goto wait_chunk;

        switch (be32toh(input->cmd)) {
        case CMD_GET_CAPABILITY:
            break;
        case CMD_INIT:
            needed = offsetof(struct input, body) +
                     sizeof(init_p->u.req);
            break;
        case CMD_SHUTDOWN:
            break;
        case CMD_GET_TPMESTABLISHED:
            break;
        case CMD_SET_LOCALITY:
            needed = offsetof(struct input, body) +
                     sizeof(pl->u.req);
            break;
        case CMD_HASH_START:
            break;
        case CMD_HASH_DATA:
            needed = offsetof(struct input, body) +
                     offsetof(struct ptm_hdata, u.req.data);
            if (recvd >= needed) {
                phd = (struct ptm_hdata *)&input->body;
                needed += be32toh(phd->u.req.length);
            }
            break;
        case CMD_HASH_END:
            break;
        case CMD_CANCEL_TPM_CMD:
            break;
        case CMD_STORE_VOLATILE:
            break;
        case CMD_RESET_TPMESTABLISHED:
            needed = offsetof(struct input, body) +
                     sizeof(pre->u.req);
            break;
        case CMD_GET_STATEBLOB:
            needed = offsetof(struct input, body) +
                     sizeof(pgs->u.req);
            break;
        case CMD_SET_STATEBLOB:
            needed = offsetof(struct input, body) +
                     offsetof(struct ptm_setstate, u.req.data);
            if (recvd >= needed) {
                pss = (struct ptm_setstate *)&input->body;
                needed += be32toh(pss->u.req.length);
            }
            break;
        case CMD_STOP:
            break;
        case CMD_GET_CONFIG:
            break;
        case CMD_SET_BUFFERSIZE:
            break;
        }

        if (recvd >= needed)
            break;

wait_chunk:
        clock_gettime(CLOCK_REALTIME, &now);
        timespec_diff(&deadline, &now, &timeout);

        if (timeout.tv_sec < 0)
            break;
        to = timeout.tv_sec * 1000 + timeout.tv_nsec / 1E6;

        /* wait for the next chunk */
        while (true) {
            n = poll(&pollfd, 1, to);
            if (n < 0 && errno == EINTR)
                continue;
            if (n <= 0)
                return n;
            break;
        }
        /* we should have data now */
    }
    return recvd;
}

static uint64_t get_ptm_caps_supported(TPMLIB_TPMVersion tpmversion)
{
    uint64_t caps =
              PTM_CAP_INIT
            | PTM_CAP_SHUTDOWN
            | PTM_CAP_GET_TPMESTABLISHED
            | PTM_CAP_SET_LOCALITY
            | PTM_CAP_HASHING
            | PTM_CAP_CANCEL_TPM_CMD
            | PTM_CAP_STORE_VOLATILE
            | PTM_CAP_RESET_TPMESTABLISHED
            | PTM_CAP_GET_STATEBLOB
            | PTM_CAP_SET_STATEBLOB
            | PTM_CAP_STOP
            | PTM_CAP_GET_CONFIG
#ifndef __CYGWIN__
            | PTM_CAP_SET_DATAFD
#endif
            | PTM_CAP_SET_BUFFERSIZE
            | PTM_CAP_GET_INFO
            | PTM_CAP_LOCK_STORAGE;
    if (tpmversion == TPMLIB_TPM_VERSION_2)
        caps |= PTM_CAP_SEND_COMMAND_HEADER;

    return caps;
}

/*
 * ctrlchannel_process_fd: Read command from control channel and execute it
 *
 * @fd: file descriptor for control channel
 * @terminate: pointer to a boolean that will be set to true by this
 *             function in case the process should shut down; CMD_SHUTDOWN
 *             will set this
 * @locality: pointer to locality identifier that must point to the global
 *            locality variable and that will receive the new locality
 *            number when set via CMD_SET_LOCALITY
 * @tpm_running: indicates whether the TPM is running; may be changed by
 *               this function in case TPM is stopped or started
 * @mlp: mainloop parameters used; may be altered by this function incase of
 *       CMD_SET_DATAFD
 *
 * This function returns the passed file descriptor or -1 in case the
 * file descriptor was closed.
 */
int ctrlchannel_process_fd(int fd,
                           bool *terminate,
                           TPM_MODIFIER_INDICATOR *locality,
                           bool *tpm_running,
                           struct mainLoopParams *mlp)
{
    struct input input = {0, };
    struct output {
        uint8_t body[sizeof(struct ptm_hdata)]; /* ptm_hdata is largest */
    } output;
    ssize_t n;
    struct iovec iov = {
        .iov_base = &input,
        .iov_len = sizeof(input),
    };
    char control[CMSG_SPACE(sizeof(int))];
    struct msghdr msg = {
        .msg_iov = &iov,
        .msg_iovlen = 1,
        .msg_control = control,
        .msg_controllen = sizeof(control),
    };
    struct cmsghdr *cmsg = NULL;
    int *data_fd = NULL;

    /* Write-only */
    ptm_cap *ptm_caps = (ptm_cap *)&output.body;
    ptm_res *res_p = (ptm_res *)&output.body;
    ptm_est *te = (ptm_est *)&output.body;
    ptm_getconfig *pgc = (ptm_getconfig *)&output.body;
    /* Read-write */
    ptm_init *init_p;
    ptm_reset_est *re;
    ptm_hdata *data;
    ptm_getstate *pgs;
    ptm_setstate *pss;
    ptm_loc *pl;
    ptm_setbuffersize *psbs;
    ptm_getinfo *pgi, _pgi;
    ptm_lockstorage *pls;

    size_t out_len = 0;
    TPM_RESULT res;
    uint32_t remain;
    uint32_t buffersize, maxsize, minsize;
    uint64_t info_flags;
    uint32_t offset;
    char *info_data = NULL;
    size_t length;
    TPM_MODIFIER_INDICATOR orig_locality;

    if (fd < 0)
        return -1;

    n = ctrlchannel_recv_cmd(fd, &msg);
    if (n <= 0) {
        goto err_socket;
    }

    SWTPM_PrintAll(" Ctrl Cmd:", " ", msg.msg_iov->iov_base, min(n, 1024));

    if ((size_t)n < sizeof(input.cmd)) {
        goto err_bad_input;
    }

    n -= sizeof(input.cmd);

    switch (be32toh(input.cmd)) {
    case CMD_GET_CAPABILITY:
        *ptm_caps = htobe64(get_ptm_caps_supported(mlp->tpmversion));

        out_len = sizeof(*ptm_caps);
        break;

    case CMD_INIT:
        if (n != (ssize_t)sizeof(ptm_init)) /* r/w */
            goto err_bad_input;

        if (*tpm_running && !mlp->disable_auto_shutdown)
            tpmlib_maybe_send_tpm2_shutdown(mlp->tpmversion,
                                            &mlp->lastCommand);

        init_p = (ptm_init *)input.body;

        TPMLIB_Terminate();

        *tpm_running = false;

        mlp->storage_locked = !mlp->incoming_migration;

        res = tpmlib_start(be32toh(init_p->u.req.init_flags),
                           mlp->tpmversion, mlp->storage_locked);
        if (res) {
            logprintf(STDERR_FILENO,
                      "Error: Could not initialize the TPM\n");
        } else {
            *tpm_running = true;
        }

        *res_p = htobe32(res);
        out_len = sizeof(ptm_res);
        break;

    case CMD_STOP:
        if (n != 0) /* wo */
            goto err_bad_input;

        if (*tpm_running && !mlp->disable_auto_shutdown)
            tpmlib_maybe_send_tpm2_shutdown(mlp->tpmversion,
                                            &mlp->lastCommand);

        TPMLIB_Terminate();

        *tpm_running = false;

        *res_p = htobe32(TPM_SUCCESS);
        out_len = sizeof(ptm_res);
        break;

    case CMD_SHUTDOWN:
        if (n != 0) /* wo */
            goto err_bad_input;

        if (*tpm_running && !mlp->disable_auto_shutdown)
            tpmlib_maybe_send_tpm2_shutdown(mlp->tpmversion,
                                            &mlp->lastCommand);

        TPMLIB_Terminate();

        *tpm_running = false;

        *res_p = htobe32(TPM_SUCCESS);
        out_len = sizeof(ptm_res);

        *terminate = true;
        break;

    case CMD_GET_TPMESTABLISHED:
        if (!*tpm_running)
            goto err_not_running;

        if (n != 0) /* wo */
            goto err_bad_input;

        out_len = sizeof(te->u.resp);
        memset(output.body, 0, out_len);

        res = htobe32(TPM_IO_TpmEstablished_Get(&te->u.resp.bit));
        te->u.resp.tpm_result = res;

        break;

    case CMD_RESET_TPMESTABLISHED:
        if (!*tpm_running)
            goto err_not_running;

        if (n < (ssize_t)sizeof(re->u.req.loc)) /* rw */
            goto err_bad_input;

        re = (ptm_reset_est *)input.body;

        if (re->u.req.loc > 4) {
            res = htobe32(TPM_BAD_LOCALITY);
        } else {
            orig_locality = *locality;
            *locality = re->u.req.loc;

            res = htobe32(TPM_IO_TpmEstablished_Reset());

            *locality = orig_locality;
        }

        *res_p = res;
        out_len = sizeof(re->u.resp);
        break;

    case CMD_SET_LOCALITY:
        if (n < (ssize_t)sizeof(pl->u.req.loc)) /* rw */
            goto err_bad_input;

        pl = (ptm_loc *)input.body;
        if (pl->u.req.loc > 4 ||
            (pl->u.req.loc == 4 &&
             mlp->locality_flags & LOCALITY_FLAG_REJECT_LOCALITY_4)) {
            res = TPM_BAD_LOCALITY;
        } else {
            res = TPM_SUCCESS;
            *locality = pl->u.req.loc;
        }

        *res_p = htobe32(res);
        out_len = sizeof(re->u.resp);
        break;

    case CMD_HASH_START:
        if (!*tpm_running)
            goto err_not_running;

        if (n != 0) /* wo */
            goto err_bad_input;

        *res_p = htobe32(TPM_IO_Hash_Start());
        out_len = sizeof(ptm_res);

        break;

    case CMD_HASH_DATA:
        if (!*tpm_running)
             goto err_not_running;

        if (n < (ssize_t)offsetof(ptm_hdata, u.req.data)) /* rw */
             goto err_bad_input;

        data = (ptm_hdata *)&input.body;
        remain = htobe32(data->u.req.length);
        n -= sizeof(data->u.req.length);
        /* n has the available number of bytes to hash */

        while (true) {
            res = TPM_IO_Hash_Data(data->u.req.data, n);
            if (res)
                break;
            remain -= n;
            if (!remain)
                break;

            n = read_eintr(fd, &data->u.req.data, sizeof(data->u.req.data));
            if (n <= 0) {
                res = TPM_IOERROR;
                break;
            }
        }

        data = (ptm_hdata *)&output.body;

        data->u.resp.tpm_result = htobe32(res);
        out_len = sizeof(data->u.resp.tpm_result);

        break;

    case CMD_HASH_END:
        if (!*tpm_running)
            goto err_not_running;

        if (n != 0) /* wo */
            goto err_bad_input;

        *res_p = htobe32(TPM_IO_Hash_End());
        out_len = sizeof(ptm_res);

        break;

    case CMD_CANCEL_TPM_CMD:
        if (!*tpm_running)
            goto err_not_running;

        if (n != 0) /* wo */
            goto err_bad_input;

        /* for cancellation to work, the TPM would have to
         * execute in another thread that polls on a cancel
         * flag
         */
        *res_p = htobe32(TPMLIB_CancelCommand());
        out_len = sizeof(ptm_res);
        break;

    case CMD_STORE_VOLATILE:
        if (!*tpm_running)
            goto err_not_running;

        if (n != 0) /* wo */
            goto err_bad_input;

        *res_p = htobe32(SWTPM_NVRAM_Store_Volatile());
        out_len = sizeof(ptm_res);
        break;

    case CMD_GET_STATEBLOB:
        if (!*tpm_running)
            goto err_not_running;

        pgs = (ptm_getstate *)input.body;
        if (n < (ssize_t)sizeof(pgs->u.req)) /* rw */
            goto err_bad_input;

        return ctrlchannel_return_state(pgs, fd, mlp);

    case CMD_SET_STATEBLOB:
        if (*tpm_running)
            goto err_running;

        /* tpm state dir must be set */
        SWTPM_NVRAM_Init();
        if ((*terminate = !mainloop_ensure_locked_storage(mlp)))
            goto err_io;

        pss = (ptm_setstate *)input.body;
        if (n < (ssize_t)offsetof(ptm_setstate, u.req.data)) /* rw */
            goto err_bad_input;

        return ctrlchannel_receive_state(pss, n, fd);

    case CMD_GET_CONFIG:
        if (n != 0) /* wo */
            goto err_bad_input;

        pgc->u.resp.tpm_result = htobe32(0);
        pgc->u.resp.flags = htobe32(0);
        if (SWTPM_NVRAM_Has_FileKey())
            pgc->u.resp.flags |= htobe32(PTM_CONFIG_FLAG_FILE_KEY);
        if (SWTPM_NVRAM_Has_MigrationKey())
            pgc->u.resp.flags |= htobe32(PTM_CONFIG_FLAG_MIGRATION_KEY);

        out_len = sizeof(pgc->u.resp);
        break;

    case CMD_SET_DATAFD:
#ifdef __CYGWIN__
        if (1)
            goto err_running;
#endif
        if (mlp->fd != -1)
            goto err_io;

        cmsg = CMSG_FIRSTHDR(&msg);
        if (!cmsg || cmsg->cmsg_len < CMSG_LEN(sizeof(int)) ||
             cmsg->cmsg_level != SOL_SOCKET ||
             cmsg->cmsg_type != SCM_RIGHTS ||
             !(data_fd = (int *)CMSG_DATA(cmsg)) ||
             *data_fd < 0) {
            logprintf(STDERR_FILENO, "no valid data socket in message; cmsg = "
                                     "%p", cmsg);
            goto err_bad_input;
        }

        mlp->flags |= MAIN_LOOP_FLAG_USE_FD | MAIN_LOOP_FLAG_KEEP_CONNECTION;
        mlp->fd = *data_fd;

        *res_p = htobe32(TPM_SUCCESS);
        out_len = sizeof(ptm_res);
        break;

    case CMD_SET_BUFFERSIZE:
        psbs = (ptm_setbuffersize *)input.body;
        if (n < (ssize_t)sizeof(psbs->u.req)) /* rw */
            goto err_bad_input;

        buffersize = be32toh(psbs->u.req.buffersize);

        if (buffersize > 0 && *tpm_running)
            goto err_running;

        buffersize = TPMLIB_SetBufferSize(buffersize,
                                          &minsize,
                                          &maxsize);

        *res_p = htobe32(TPM_SUCCESS);

        psbs = (ptm_setbuffersize *)&output.body;
        out_len = sizeof(psbs->u.resp);

        psbs->u.resp.buffersize = htobe32(buffersize);
        psbs->u.resp.minsize = htobe32(minsize);
        psbs->u.resp.maxsize = htobe32(maxsize);

        break;

    case CMD_GET_INFO:
        if (n < (ssize_t)sizeof(pgi->u.req)) /* rw */
            goto err_bad_input;

        /* copy since flags needs to be 8-byte aligned  */
        memcpy(&_pgi, input.body, sizeof(_pgi));
        pgi = &_pgi;

        info_flags = be64toh(pgi->u.req.flags);

        info_data = TPMLIB_GetInfo(info_flags);
        if (!info_data)
            goto err_memory;

        offset = be32toh(pgi->u.req.offset);
        if (offset >= strlen(info_data)) {
            free(info_data);
            goto err_bad_input;
        }

        length = min(strlen(info_data) + 1 - offset,
                     sizeof(pgi->u.resp.buffer));

        pgi = (ptm_getinfo *)&output.body;
        pgi->u.resp.tpm_result = htobe32(0);
        pgi->u.resp.totlength = htobe32(strlen(info_data) + 1);
        pgi->u.resp.length = htobe32(length);
        /* client has to collect whole string in case buffer is too small */
        memcpy(pgi->u.resp.buffer, &info_data[offset], length);
        free(info_data);

        out_len = offsetof(ptm_getinfo, u.resp.buffer) + length;

        break;

    case CMD_LOCK_STORAGE:
        if (n < (ssize_t)sizeof(pls->u.req)) /* rw */
            goto err_bad_input;

        pls = (ptm_lockstorage *)input.body;

        mlp->locking_retries = be32toh(pls->u.req.retries);

        pls = (ptm_lockstorage *)&output.body;
        out_len = sizeof(pls->u.resp);

        if (!mainloop_ensure_locked_storage(mlp))
            pls->u.resp.tpm_result = htobe32(TPM_FAIL);
        else
            pls->u.resp.tpm_result = htobe32(TPM_SUCCESS);

        break;

    default:
        logprintf(STDERR_FILENO,
                  "Error: Unknown command: 0x%08x\n", be32toh(input.cmd));

        *res_p = htobe32(TPM_BAD_ORDINAL);
        out_len = sizeof(ptm_res);
    }

send_resp:
    SWTPM_PrintAll(" Ctrl Rsp:", " ", output.body, min(out_len, 1024));

    n = write_full(fd, output.body, out_len);
    if (n < 0) {
        logprintf(STDERR_FILENO,
                  "Error: Could not send response: %s\n", strerror(errno));
        close(fd);
        fd = -1;
    }

    return fd;

err_bad_input:
    *res_p = htobe32(TPM_BAD_PARAMETER);
    out_len = sizeof(ptm_res);

    goto send_resp;

err_running:
err_not_running:
    *res_p = htobe32(TPM_BAD_ORDINAL);
    out_len = sizeof(ptm_res);

    goto send_resp;

err_io:
    *res_p = htobe32(TPM_IOERROR);
    out_len = sizeof(ptm_res);

    goto send_resp;

err_memory:
    *res_p = htobe32(TPM_SIZE);
    out_len = sizeof(ptm_res);

    goto send_resp;

err_socket:
    close(fd);

    return -1;
}

void ctrlchannel_free(struct ctrlchannel *cc)
{
    if (!cc)
        return;

    if (cc->fd >= 0)
        close(cc->fd);
    if (cc->clientfd >= 0)
        close(cc->clientfd);
    if (cc->sockpath) {
        unlink(cc->sockpath);
        free(cc->sockpath);
    }
    free(cc);
}
