/*
 * This program is provided under the terms of the Common Public License,
 * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
 * software constitutes recipient's acceptance of CPL-1.0 terms which can be
 * found in the file LICENSE file or at
 * https://opensource.org/licenses/cpl1.0.php
 */

/* (C) COPYRIGHT Google Inc. 2013 */

//
// Pkcs11 Api Socket client routines
//

#include <stdio.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <syslog.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <grp.h>
#include <errno.h>
#include <stdlib.h>

#include "apiproto.h"
#include "slotmgr.h"
#include "apictl.h"
#include "ock_syslog.h"

extern API_Proc_Struct_t *Anchor;
//
// Will fill out the Slot_Mgr_Socket_t structure in the Anchor global data
// structure with the values passed by the pkcsslotd via a socket RPC.
int init_socket_data()
{
    int socketfd;
    struct sockaddr_un daemon_address;
    struct stat file_info;
    struct group *grp;
    int n;
    unsigned int bytes_received = 0;
    Slot_Mgr_Socket_t *daemon_socket_data = NULL;
    int ret = FALSE;

    if (stat(SOCKET_FILE_PATH, &file_info)) {
        OCK_SYSLOG(LOG_ERR,
                   "init_socket_data: failed to find socket file, errno=%d",
                   errno);
        return FALSE;
    }

    grp = getgrnam("pkcs11");
    if (!grp) {
        OCK_SYSLOG(LOG_ERR,
                   "init_socket_data: pkcs11 group does not exist, errno=%d",
                   errno);
        return FALSE;
    }

    if (file_info.st_uid != 0 || file_info.st_gid != grp->gr_gid) {
        OCK_SYSLOG(LOG_ERR,
                   "init_socket_data: incorrect permissions on socket file");
        return FALSE;
    }

    if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        OCK_SYSLOG(LOG_ERR,
                   "init_socket_data: failed to create socket, errno=%d",
                   errno);
        return FALSE;
    }

    memset(&daemon_address, 0, sizeof(struct sockaddr_un));
    daemon_address.sun_family = AF_UNIX;
    strcpy(daemon_address.sun_path, SOCKET_FILE_PATH);

    if (connect(socketfd, (struct sockaddr *) &daemon_address,
                sizeof(struct sockaddr_un)) != 0) {
        OCK_SYSLOG(LOG_ERR,
                   "init_socket_data: failed to connect to slotmanager daemon, "
                   "errno=%d",
                   errno);
        goto exit;
    }
    // allocate data buffer
    daemon_socket_data =
        (Slot_Mgr_Socket_t *) malloc(sizeof(*daemon_socket_data));
    if (!daemon_socket_data) {
        OCK_SYSLOG(LOG_ERR, "init_socket_data: failed to \
                   allocate %lu bytes \
                   for daemon data, errno=%d",
                   sizeof(*daemon_socket_data), errno);
        goto exit;
    }

    while (bytes_received < sizeof(*daemon_socket_data)) {
        n = read(socketfd, ((char *) daemon_socket_data) + bytes_received,
                 sizeof(*daemon_socket_data) - bytes_received);
        if (n < 0) {
            // read error
            if (errno == EINTR)
                continue;
            OCK_SYSLOG(LOG_ERR, "init_socket_data: read error \
                       on daemon socket, errno=%d", errno);
            goto exit;
        } else if (n == 0) {
            // eof but we still expect some bytes
            OCK_SYSLOG(LOG_ERR, "init_socket_data: read returned \
                       with eof but we still \
                       expect %lu bytes from daemon",
                       sizeof(*daemon_socket_data) - bytes_received);
            goto exit;
        } else {
            // n > 0, we got some bytes
            bytes_received += n;
        }
    }

    ret = TRUE;

    // copy the Slot_Mgr_Socket_t struct into global
    // Anchor SocketDataPdata buffer
    memcpy(&(Anchor->SocketDataP), daemon_socket_data,
           sizeof(*daemon_socket_data));

exit:
    //free the data buffer after copy
    if (daemon_socket_data)
        free(daemon_socket_data);

    close(socketfd);

    return ret;
}
