/*
 * © Copyright 1996-2012 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/*
** File: ecmarscert.c
**
** Purpose: Shared definitions and code for member state MARS client and
**          ECMWF MARS server used by routines handling the creation and
**          validation of ECMarsCertificates.
**
** $Date: 1996/03/26 17:06:05 $ $Revision: 1.6 $
*/
#include "eccert.h"

#include <string.h>
#include <unistd.h>

#define ECDIGESTLEN (2 * sizeof(digestType) + 1)
#define ECMAXMARSCERTLEN 512

char* OKECMARSCERT         = NULL;
char EECCMDCLIENTFAILURE[] = "ECCMD client has failed!";
char EECDIGESTMISMATCH[]   = "The message digest does not match data!";
char EECMARSCERTREJECT[]   = "MARS certificate rejected!";
char EECMARSLIMREJECT[]    = "MARS certificate no longer valid!";
char EECMARSUNVERIFIED[]   = "Unverified passcode!";
char EECNOBIND[]           = "Can't bind to socket!";
char EECNOCMDCLIENT[]      = "ECCMD client is not running!";
char EECNOCMDSERVER[]      = "ECCMD server is not running!";
char EECNORESOLVE[]        = "Can't resolve hostname!";
char EECNORESPONSE[]       = "ECCMD client doesn't respond!";
char EECNOSEND[]           = "Can't send to server!";
char EECNOSOCKET[]         = "Can't create socket!";
char EECNOSPACE[]          = "Insufficient memory for MARS certificate!";
char EECPASSCODEINVALID[]  = "Passcode no longer valid!";
char EECPASSCODEREJECT[]   = "Passcode rejected!";
char EECSERVERINTERNAL[]   = "Internal server error!";

/* MARS server (running at ECMWF): */
/* =============================== */
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/uio.h>

char* verify_ecmars_certificate(
    char* data,            /* Data to be certified */
    int len,               /* its length in bytes */
    char* marsCert,        /* Certificate Area */
    int marsCertLen,       /* its length in bytes */
    eCMarsCertReply* reply /* Structure to receive reply */
) {
    int sd;

    /* Connect and send mars certificate to ECMWF certification server */
    {
        struct sockaddr_in client;
        struct hostent* hent;
        struct sockaddr_in server;

        if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
            return EECNOSOCKET;

        client.sin_family      = AF_INET;
        client.sin_port        = htons(0);
        client.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(sd, (struct sockaddr*)&client, sizeof(client)) < 0)
            return EECNOBIND;

        if (!(hent = gethostbyname(ECQ_SERV_HOST)))
            return EECNORESOLVE;

        server.sin_family = AF_INET;
        server.sin_port   = htons(ECQ_SERV_PORT);
        server.sin_addr   = *(struct in_addr*)*hent->h_addr_list;

        if (connect(sd, (struct sockaddr*)&server, sizeof(server)) < 0)
            return EECNOCMDSERVER;

        /* Send the mars certificate and append the data
        ** NOTE: THIS INFORMATION IS EXPECTED TO ARRIVE IN
        **       A SINGLE PACKET ON THE ECCMD SERVER AND IS THEREFORE SENT
        **       IN A SINGLE REQUEST.
        **/
        {
            struct iovec iov[2];

            iov[0].iov_base = marsCert;
            iov[0].iov_len  = marsCertLen;
            iov[1].iov_base = data;
            iov[1].iov_len  = len;
            if (writev(sd, iov, 2) < 0) {
                close(sd);
                return EECNOSEND;
            };
        };
    };

    /* Receive reply and check if mars certificate is valid. */
    {
        short n = read(sd, reply, sizeof(*reply));

        close(sd);

        if (n < sizeof(*reply) || reply->protocolID != ECMARSCERTPROTOCOLID)
            reply->returnCode = RCSINTERNAL;

        switch (reply->returnCode) {
            case RCSPASSCODEREJECT:
                return EECPASSCODEINVALID;
            case RCSPASSCODERETRY:
                return EECPASSCODEREJECT;
            case RCSFAIL:
                return EECMARSCERTREJECT;
            case RCSLIMREJECT:
                return EECMARSLIMREJECT;
            case RCSMARSOK:
                return OKECMARSCERT;
        };
    };

    return EECSERVERINTERNAL;
}
