/*
 * © 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.
 */

#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>

#include "mars.h"

/*

    This file implements different authetication methods:
    ecmwf: is the method used at ECMWF. Each MARS request is
           verified once against a list of MARS restrictions
           for the user requesting the data.
    eccert: is the method used for the MARS client installed
            in Member States or Co-operating States to
            access ECMWF MARS.

    The method can be defined per request (see options.c) or
    per database (see netbase.c).

    MARS administrators wanting to add new validation methods
    should give it a name and create a validate proc (see below).
*/

typedef err (*validateproc)(request*, request*);

typedef struct validation_handler {
    const char* name;
    validateproc proc;
} validation_handler;

err eccert_validate(request* r, request* e);
err ecmwf_validate(request* r, request* e);
err ams_validate(request* r, request* e);
err null_validate(request* r, request* e);

static validation_handler handlers[] = {
    {"eccert", eccert_validate},
    {"ecmwf", ecmwf_validate},
    {"null", null_validate},
    {"ams", ams_validate},
};


err ams_validate(request* r, request* e) {
    mars.request_id = 42;
    return 0;
}

err ecmwf_validate(request* r, request* e) {
    err ret = 0;
    char buf[80];

    marslog(LOG_DBUG, "Validation using ECMWF restrictions");

    mars.request_id = -1;
    start_timer();
    ret = local_validate_request(r, e);
    if (mars.debug) {
        marslog(LOG_DBUG, "Request after authentication:");
        print_all_requests(r);
    }
    stop_timer(buf);
    if (*buf)
        marslog(LOG_DBUG, "Authentication time: %s", buf);
    return ret;
}

int eccert_validate(request* r, request* e) {
#if mars_client_HAVE_RPC
    char* buf;
    XDR x;
    netblk blk;
    char* tmp;
    FILE* f;
    err ret = 0;
    int len;
    int ok = 0;
    int buflen;

    marslog(LOG_DBUG, "Validation using ECCERT");

    memset(&blk, 0, sizeof(blk));
    blk.req = clone_one_request(r);
    blk.env = clone_one_request(e);

    buflen = 4096;
    buf    = MALLOC(buflen);

    do {
        xdrmem_create(&x, buf, buflen, XDR_ENCODE);
        if (!xdr_netblk(&x, &blk)) {
            xdr_destroy(&x);
            FREE(buf);
            buflen += buflen / 2 + 1;
            buf = MALLOC(buflen);
        }
        else
            ok = 1;
    } while (!ok);

    free_all_requests(blk.req);
    free_all_requests(blk.env);

    len = xdr_getpos(&x);
    xdr_destroy(&x);

    tmp = marstmp();
    f   = fopen(tmp, "w");

    if (!f) {
        marslog(LOG_EROR | LOG_PERR, "Cannot open %s", tmp);
        return -2;
    }

    if (fwrite(buf, 1, len, f) != len) {
        marslog(LOG_EROR | LOG_PERR, "fwrite(%s)", tmp);
        fclose(f);
        FREE(buf);
        return -2;
    }

    if (fclose(f)) {
        marslog(LOG_EROR | LOG_PERR, "fclose(%s)", tmp);
        FREE(buf);
        return -2;
    }

    sprintf(buf, "eccmd ecmars -q %s", tmp);
    marslog(LOG_DBUG, "%s", buf);

    f = popen(buf, "r");
    if (!f) {
        marslog(LOG_EROR | LOG_PERR, "Cannot execute %s", buf);
        return -2;
    }

    len = fread(buf, 1, buflen, f);
    marslog(LOG_DBUG, "token len %d", len);
    if (len <= 0) {
        marslog(LOG_EROR | LOG_PERR, "fread");
        ret = -2;
    }

    if (len == buflen) {
        marslog(LOG_EROR, "Certificate to long");
        ret = -2;
    }

    if (pclose(f) != 0) {
        marslog(LOG_EROR | LOG_PERR, "Got non zero code from eccert");
        ret = -2;
    }
    unlink(tmp);

    if (ret == 0) {
        FREE(mars.certstr);
        mars.certstr = 0;
        mars.certstr = MALLOC(len);
        memcpy(mars.certstr, buf, len);
        mars.certlen = len;
    }

    FREE(buf);

    return ret;
#else
    return 0;
#endif /* mars_client_HAVE_RPC */
}


err null_validate(request* r, request* e) {
    mars.request_id = -1;
    marslog(LOG_DBUG, "NO Validation performed");
    return NOERR;
}

err validate_request(request* r, request* e, const char* method) {
    err ret = NOERR;
    int i   = 0;

    for (i = 0; i < NUMBER(handlers); i++)
        if (method && EQ(handlers[i].name, method)) {
            ret |= handlers[i].proc(r, e);
        }

    return ret;
}
