/* Copyright (C) 2011 the GSS-PROXY contributors, see COPYING for license */

#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <errno.h>
#include <netinet/in.h>
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_krb5.h>
#include "src/gp_debug.h"
#include "src/gp_log.h"
#include "popt.h"
#include <libintl.h>
#include "t_utils.h"

#define _(STRING) gettext(STRING)

static const char *actor = "<not set>";
const char **argv;

static int gptest_inq_context(gss_ctx_id_t ctx)
{
    gss_name_t targ_name = GSS_C_NO_NAME;
    gss_name_t src_name = GSS_C_NO_NAME;
    OM_uint32 lifetime_rec = -1;
    OM_uint32 ctx_flags = -1;
    gss_OID mech_type = GSS_C_NO_OID;
    int locally_initiated = -1;
    int open = -1;
    gss_buffer_desc sname = {0};
    gss_buffer_desc tname = {0};
    gss_buffer_desc mechstr = {0};
    OM_uint32 time_rec = -1;
    OM_uint32 maj, min;

    maj = gss_inquire_context(&min, ctx, &src_name, &targ_name,
                              &lifetime_rec, &mech_type, &ctx_flags,
                              &locally_initiated, &open);
    if (maj == GSS_S_COMPLETE) {
        gss_OID type = GSS_C_NO_OID;
        maj = gss_display_name(&min, src_name, &sname, &type);
        if (maj != GSS_S_COMPLETE) {
            goto done;
        }
        maj = gss_display_name(&min, targ_name, &tname, &type);
        if (maj != GSS_S_COMPLETE) {
            goto done;
        }
        maj = gss_oid_to_str(&min, mech_type, &mechstr);
        if (maj != GSS_S_COMPLETE) {
            goto done;
        }
        DEBUG("Context properties: "
              "[ s:%s, t:%s, m:%s, l:%d, f:%d, i:%d, o:%d ]\n",
              (char *)sname.value, (char *)tname.value, (char *)mechstr.value,
              (int)lifetime_rec, (int)ctx_flags, locally_initiated, open);
    }

    maj = gss_context_time(&min, ctx, &time_rec);
    if (maj) {
        DEBUG("gss_context_time failed\n");
        gp_log_failure(GSS_C_NO_OID, maj, min);
        goto done;
    }
    DEBUG("Context validity: %d sec.\n", time_rec);

done:
    (void)gss_release_buffer(&min, &sname);
    (void)gss_release_buffer(&min, &tname);
    (void)gss_release_buffer(&min, &mechstr);
    return maj;
}

#define PROXY_LOCAL_ONLY 0
#define PROXY_LOCAL_FIRST 1
#define PROXY_REMOTE_FIRST 2
#define PROXY_REMOTE_ONLY 3

#define GSSPROXY_BEHAVIOR_ENV "GSSPROXY_BEHAVIOR"

static const char *lookup_gssproxy_behavior(int proxy_mode)
{
    switch(proxy_mode) {
    case PROXY_LOCAL_ONLY:
        return "LOCAL_ONLY";
    case PROXY_LOCAL_FIRST:
        return "LOCAL_FIRST";
    case PROXY_REMOTE_FIRST:
        return "REMOTE_FIRST";
    case PROXY_REMOTE_ONLY:
        return "REMOTE_ONLY";
    default:
        break;
    }

    return NULL;
}

static int setup_gssproxy_behavior(int proxy_mode)
{
    const char *env;

    if (getenv(GSSPROXY_BEHAVIOR_ENV)) {
        return 0;
    }

    env = lookup_gssproxy_behavior(proxy_mode);
    if (env == NULL) {
        return -1;
    }

    return setenv(GSSPROXY_BEHAVIOR_ENV, env, 0);
}

struct aproc {
    int proxy_type;
    int *cli_pipe;
    int *srv_pipe;
    char *target;
};

void run_client(struct aproc *data)
{
    uint32_t ret_maj;
    uint32_t ret_min;
    char buffer[MAX_RPC_SIZE];
    uint32_t buflen;
    gss_buffer_desc target_buf;
    gss_buffer_desc in_token = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc out_token = GSS_C_EMPTY_BUFFER;
    gss_name_t name = GSS_C_NO_NAME;
    gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
    gss_cred_id_t cred_handle = GSS_C_NO_CREDENTIAL;
    gss_buffer_desc msg_buf = GSS_C_EMPTY_BUFFER;
    const char *message = "SECRET";
    int ret = -1;
    gss_iov_buffer_desc iov[2] = { { 0, { 0, NULL } }, { 0, { 0, NULL } } };
    int sealed;
    uint32_t max_size = 0;

    ret = setup_gssproxy_behavior(data->proxy_type);
    if (ret) {
        goto done;
    }

    DEBUG("%s behavior: %s\n", actor, getenv(GSSPROXY_BEHAVIOR_ENV));

    target_buf.value = (void *)data->target;
    target_buf.length = strlen(data->target) + 1;

    ret_maj = gss_import_name(&ret_min, &target_buf,
                              GSS_C_NT_HOSTBASED_SERVICE, &name);
    if (ret_maj) {
        DEBUG("gss_import_name failed\n");
        goto done;
    }

    do {
        ret_maj = gss_init_sec_context(&ret_min,
                                       cred_handle,
                                       &ctx,
                                       name,
                                       GSS_C_NO_OID,
                                       GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
                                       0,
                                       GSS_C_NO_CHANNEL_BINDINGS,
                                       &in_token,
                                       NULL,
                                       &out_token,
                                       NULL,
                                       NULL);
        if (ret_maj != GSS_S_COMPLETE &&
            ret_maj != GSS_S_CONTINUE_NEEDED) {
            DEBUG("gss_init_sec_context() failed with: %d\n", ret_maj);
            gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
            goto done;
        }
        if (out_token.length != 0) {
            /* send to server */
            ret = t_send_buffer(data->srv_pipe[1],
                                 out_token.value, out_token.length);
            if (ret) {
                DEBUG("Failed to send data to server!\n");
                goto done;
            }

            gss_release_buffer(&ret_min, &out_token);
        }

        if (!ctx) {
            DEBUG("No context!\n");
            goto done;
        }

        if (ret_maj == GSS_S_CONTINUE_NEEDED) {
            /* and wait for reply */
            ret = t_recv_buffer(data->cli_pipe[0], buffer, &buflen);
            if (ret) {
                DEBUG("Failed to receive data from server!\n");
                goto done;
            }

            in_token.value = buffer;
            in_token.length = buflen;
        }

    } while (ret_maj == GSS_S_CONTINUE_NEEDED);

    ret = gptest_inq_context(ctx);
    if (ret) {
        DEBUG("Failed to inquire context!\n");
        goto done;
    }

    /* test gss_wrap_size_limit */

    ret_maj = gss_wrap_size_limit(&ret_min,
                                  ctx,
                                  1, /* conf_req */
                                  GSS_C_QOP_DEFAULT, /* qop_req */
                                  4096, /* size_req */
                                  &max_size);
    if (ret_maj) {
        DEBUG("gss_wrap_size_limit failed.\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }


    /* test encryption */
    msg_buf.length = strlen(message) + 1;
    msg_buf.value = discard_const(message);
    ret_maj = gss_wrap(&ret_min, ctx, 1, 0, &msg_buf, NULL, &out_token);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("Failed to wrap message.\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret = t_send_buffer(data->srv_pipe[1], out_token.value, out_token.length);
    if (ret) {
        DEBUG("Failed to send data to server!\n");
        goto done;
    }

    gss_release_buffer(&ret_min, &out_token);

    ret = t_recv_buffer(data->cli_pipe[0], buffer, &buflen);
    if (ret) {
        DEBUG("Failed to receive data from server!\n");
        goto done;
    }
    msg_buf.value = (void *)buffer;
    msg_buf.length = buflen;
    buffer[buflen] = '\0';

    in_token.value = (void *)&buffer[buflen + 1];
    ret = t_recv_buffer(data->cli_pipe[0], in_token.value, &buflen);
    if (ret) {
        DEBUG("Failed to receive data from server!\n");
        goto done;
    }
    in_token.length = buflen;

    ret_maj = gss_verify_mic(&ret_min, ctx, &msg_buf, &in_token, NULL);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("Failed to verify message (%s).\n", buffer);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
    fprintf(stdout, "Client, RECV: [%s]\n", buffer);

    /* test gss_wrap_iov_length */

    iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER;
    iov[0].buffer.value = NULL;
    iov[0].buffer.length = 0;

    iov[1].type = GSS_IOV_BUFFER_TYPE_DATA;
    iov[1].buffer.value = NULL;
    iov[1].buffer.length = msg_buf.length;

    ret_maj = gss_wrap_iov_length(&ret_min,
                                  ctx,
                                  1, /* seal */
                                  GSS_C_QOP_DEFAULT,
                                  &sealed,
                                  iov,
                                  2);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("gss_wrap_iov_length failed\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    buflen = iov[0].buffer.length;

    /* test gss_wrap_iov */

    iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER;
    iov[0].buffer.length = buflen;
    iov[0].buffer.value = malloc(iov[0].buffer.length);
    if (!iov[0].buffer.value) {
        DEBUG("Out of memory\n");
        goto done;
    }

    iov[1].type = GSS_IOV_BUFFER_TYPE_DATA;
    iov[1].buffer.value = msg_buf.value;
    iov[1].buffer.length = msg_buf.length;

    ret_maj = gss_wrap_iov(&ret_min,
                           ctx,
                           1, /* seal */
                           GSS_C_QOP_DEFAULT,
                           &sealed,
                           iov,
                           2);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("gss_wrap_iov failed.\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret = t_send_buffer(data->srv_pipe[1], iov[0].buffer.value, iov[0].buffer.length);
    if (ret) {
        DEBUG("Failed to send data to server!\n");
        goto done;
    }

    ret = t_send_buffer(data->srv_pipe[1], iov[1].buffer.value, iov[1].buffer.length);
    if (ret) {
        DEBUG("Failed to send data to server!\n");
        goto done;
    }

    ret_maj = gss_delete_sec_context(&ret_min, &ctx, &out_token);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("Failed to delete context!\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret = t_send_buffer(data->srv_pipe[1], out_token.value, out_token.length);
    if (ret) {
        DEBUG("Failed to send data to server!\n");
        goto done;
    }

    gss_release_buffer(&ret_min, &out_token);

    DEBUG("Success!\n");

done:
    free(iov[0].buffer.value);
    gss_release_name(&ret_min, &name);
    gss_release_buffer(&ret_min, &out_token);
    close(data->cli_pipe[0]);
    close(data->srv_pipe[1]);
    exit(ret);
}

void run_server(struct aproc *data)
{
    char buffer[MAX_RPC_SIZE];
    uint32_t buflen;
    gss_buffer_desc in_token = GSS_C_EMPTY_BUFFER;
    uint32_t ret_maj;
    uint32_t ret_min;
    gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
    gss_cred_id_t cred_handle = GSS_C_NO_CREDENTIAL;
    gss_name_t src_name;
    gss_buffer_desc out_token = GSS_C_EMPTY_BUFFER;
    gss_cred_id_t deleg_cred = GSS_C_NO_CREDENTIAL;
    gss_OID_set mech_set = GSS_C_NO_OID_SET;
    gss_OID_set mech_names = GSS_C_NO_OID_SET;
    gss_OID_set mech_types = GSS_C_NO_OID_SET;
    gss_OID_set mech_attrs = GSS_C_NO_OID_SET;
    gss_OID_set known_mech_attrs = GSS_C_NO_OID_SET;
    gss_buffer_desc sasl_mech_name = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc mech_name = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc mech_description = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc short_desc = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc long_desc = GSS_C_EMPTY_BUFFER;
    gss_OID_set mechs = GSS_C_NO_OID_SET;
    gss_buffer_desc const_buf;
    gss_name_t target_name = GSS_C_NO_NAME;
    gss_name_t canon_name = GSS_C_NO_NAME;
    gss_buffer_desc out_name_buf = GSS_C_EMPTY_BUFFER;
    gss_OID out_name_type = GSS_C_NO_OID;
    gss_buffer_desc exported_name = GSS_C_EMPTY_BUFFER;
    const char *message = "This message is authentic!";
    int ret = -1;
    gss_iov_buffer_desc iov[2];
    int sealed;

    ret = setup_gssproxy_behavior(data->proxy_type);
    if (ret) {
        goto done;
    }

    DEBUG("%s behavior: %s\n", actor, getenv(GSSPROXY_BEHAVIOR_ENV));

    const_buf.value = (void *)data->target;
    const_buf.length = strlen(data->target) + 1;

    /* import name family functions tests */
    ret_maj = gss_import_name(&ret_min, &const_buf,
                              GSS_C_NT_HOSTBASED_SERVICE, &target_name);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
    ret_maj = gss_canonicalize_name(&ret_min, target_name,
                                    discard_const(gss_mech_krb5),
                                    &canon_name);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret_maj = gss_export_name(&ret_min, canon_name,
                              &exported_name);
    if (ret_maj) {
        DEBUG("gss_export_name() failed with: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
#if 0
    /* disabled until gss_export_name_composite server-side is fixed */
    gss_release_buffer(&ret_min, &exported_name);

    ret_maj = gss_export_name_composite(&ret_min, canon_name,
                                        &exported_name);
    if (ret_maj) {
        DEBUG("gss_export_name_composite() failed with: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
#endif
    ret_maj = gss_display_name(&ret_min, canon_name,
                               &out_name_buf, &out_name_type);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
    fprintf(stdout, "Acquiring for: %s\n", (char *)out_name_buf.value);

    /* indicate mechs family functions tests */
    ret_maj = gss_indicate_mechs(&ret_min, &mech_set);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret_maj = gss_inquire_names_for_mech(&ret_min,
                                         &mech_set->elements[0],
                                         &mech_names);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(&mech_set->elements[0], ret_maj, ret_min);
        goto done;
    }
    ret_maj = gss_inquire_attrs_for_mech(&ret_min,
                                         &mech_set->elements[0],
                                         &mech_attrs,
                                         &known_mech_attrs);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(&mech_set->elements[0], ret_maj, ret_min);
        goto done;
    }
    ret_maj = gss_inquire_saslname_for_mech(&ret_min,
                                            &mech_set->elements[0],
                                            &sasl_mech_name,
                                            &mech_name,
                                            &mech_description);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(&mech_set->elements[0], ret_maj, ret_min);
        goto done;
    }
    ret_maj = gss_display_mech_attr(&ret_min,
                                    &mech_attrs->elements[0],
                                    &name, &short_desc, &long_desc);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
    ret_maj = gss_indicate_mechs_by_attrs(&ret_min,
                                          GSS_C_NO_OID_SET,
                                          GSS_C_NO_OID_SET,
                                          GSS_C_NO_OID_SET,
                                          &mechs);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
    ret_maj = gss_inquire_mechs_for_name(&ret_min, target_name, &mech_types);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret_maj = gss_acquire_cred(&ret_min,
                               GSS_C_NO_NAME,
                               GSS_C_INDEFINITE,
                               mech_set,
                               GSS_C_ACCEPT,
                               &cred_handle,
                               NULL,
                               NULL);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret = t_recv_buffer(data->srv_pipe[0], buffer, &buflen);
    if (ret) {
        DEBUG("Failed to get data from client!\n");
        goto done;
    }

    in_token.value = buffer;
    in_token.length = buflen;

    ret_maj = gss_accept_sec_context(&ret_min,
                                     &context_handle,
                                     cred_handle,
                                     &in_token,
                                     GSS_C_NO_CHANNEL_BINDINGS,
                                     &src_name,
                                     NULL,
                                     &out_token,
                                     NULL,
                                     NULL,
                                     &deleg_cred);
    if (ret_maj) {
        DEBUG("gssproxy returned an error: %d\n", ret_maj);
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    if (out_token.length) {
        ret = t_send_buffer(data->cli_pipe[1],
                             out_token.value, out_token.length);
        if (ret) {
            DEBUG("Failed to send data to client!\n");
            goto done;
        }
    }

    gss_release_buffer(&ret_min, &out_token);

    ret = gptest_inq_context(context_handle);
    if (ret) {
        DEBUG("Failed to inquire context!\n");
        goto done;
    }

    ret = t_recv_buffer(data->srv_pipe[0], buffer, &buflen);
    if (ret) {
        DEBUG("Failed to get data from client!\n");
        goto done;
    }
    in_token.value = buffer;
    in_token.length = buflen;

    ret_maj = gss_unwrap(&ret_min, context_handle,
                         &in_token, &out_token, NULL, NULL);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("Failed to unwrap message.\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    fprintf(stdout, "Server, RECV: %s\n", (char *)out_token.value);

    gss_release_buffer(&ret_min, &out_token);

    in_token.value = discard_const(message);
    in_token.length = strlen(message);

    ret_maj = gss_get_mic(&ret_min, context_handle, 0, &in_token, &out_token);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("Failed to protect message.\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret = t_send_buffer(data->cli_pipe[1], in_token.value, in_token.length);
    if (ret) {
        DEBUG("Failed to send data to client!\n");
        goto done;
    }
    ret = t_send_buffer(data->cli_pipe[1], out_token.value, out_token.length);
    if (ret) {
        DEBUG("Failed to send data to client!\n");
        goto done;
    }

    gss_release_buffer(&ret_min, &out_token);

    /* test gss_unwrap_iov */

    ret = t_recv_buffer(data->srv_pipe[0], buffer, &buflen);
    if (ret) {
        DEBUG("Failed to get data from client!\n");
        goto done;
    }

    iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER;
    iov[0].buffer.value = buffer;
    iov[0].buffer.length = buflen;

    ret = t_recv_buffer(data->srv_pipe[0], buffer+buflen, &buflen);
    if (ret) {
        DEBUG("Failed to get data from client!\n");
        goto done;
    }

    iov[1].type = GSS_IOV_BUFFER_TYPE_DATA;
    iov[1].buffer.value = buffer+iov[0].buffer.length;
    iov[1].buffer.length = buflen;

    ret_maj = gss_unwrap_iov(&ret_min,
                             context_handle,
                             &sealed,
                             NULL, /* qop_state */
                             iov,
                             2);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("gss_unwrap_iov failed\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }

    ret = t_recv_buffer(data->srv_pipe[0], buffer, &buflen);
    if (ret) {
        DEBUG("Failed to get data from client!\n");
        goto done;
    }
    const_buf.value = buffer;
    const_buf.length = buflen;

    ret_maj = gss_process_context_token(&ret_min, context_handle, &const_buf);
    if (ret_maj != GSS_S_COMPLETE) {
        DEBUG("Failed to process context token.\n");
        gp_log_failure(GSS_C_NO_OID, ret_maj, ret_min);
        goto done;
    }
    /* The follwing will cause the context to leak, but it is unavoidable until
     * gss_process_context_token() is fixed to at least NULL the internal
     * context in the union context. */
    context_handle = GSS_C_NO_CONTEXT;

    DEBUG("Success!\n");

done:
    gss_release_name(&ret_min, &src_name);
    gss_release_buffer(&ret_min, &out_token);
    gss_release_cred(&ret_min, &deleg_cred);
    gss_delete_sec_context(&ret_min, &context_handle, GSS_C_NO_BUFFER);
    gss_release_oid_set(&ret_min, &mech_set);
    gss_release_oid_set(&ret_min, &mech_names);
    gss_release_oid_set(&ret_min, &mech_types);
    gss_release_oid_set(&ret_min, &mech_attrs);
    gss_release_oid_set(&ret_min, &known_mech_attrs);
    gss_release_buffer(&ret_min, &sasl_mech_name);
    gss_release_buffer(&ret_min, &mech_name);
    gss_release_buffer(&ret_min, &mech_description);
    gss_release_buffer(&ret_min, &name);
    gss_release_buffer(&ret_min, &short_desc);
    gss_release_buffer(&ret_min, &long_desc);
    gss_release_oid_set(&ret_min, &mechs);
    gss_release_name(&ret_min, &target_name);
    gss_release_name(&ret_min, &canon_name);
    gss_release_buffer(&ret_min, &out_name_buf);
    gss_release_oid(&ret_min, &out_name_type);
    gss_release_buffer(&ret_min, &exported_name);
    close(data->srv_pipe[0]);
    close(data->cli_pipe[1]);
    exit(ret);
}

static int run_cli_srv_test(int server_proxy_type,
                            int client_proxy_type,
                            char *target)
{
    int srv_pipe[2];
    int cli_pipe[2];
    struct aproc client, server;
    pid_t cli, srv, w;
    int closewait;
    int options;
    int status;
    int ret;

    ret = pipe(srv_pipe);
    if (ret) {
        return -1;
    }
    ret = pipe(cli_pipe);
    if (ret) {
        return -1;
    }

    srv = -1;
    cli = -1;

    server.proxy_type = server_proxy_type;
    server.srv_pipe = srv_pipe;
    server.cli_pipe = cli_pipe;
    server.target = target;

    srv = fork();
    switch (srv) {
    case -1:
        ret = -1;
        goto done;
    case 0:
        actor = "SERVER";
        run_server(&server);
        exit(0);
    default:
        close(srv_pipe[0]);
        close(cli_pipe[1]);
        break;
    }

    client.proxy_type = client_proxy_type;
    client.srv_pipe = srv_pipe;
    client.cli_pipe = cli_pipe;
    client.target = target;

    cli = fork();
    switch (cli) {
    case -1:
        ret = -1;
        goto done;
    case 0:
        actor = "CLIENT";
        run_client(&client);
        exit(0);
    default:
        close(srv_pipe[1]);
        close(cli_pipe[0]);
        break;
    }

    closewait = -1;
    while (cli != -1 || srv != -1) {
        if (closewait < 0) {
            options = 0;
        } else {
            options = WNOHANG;
        }
        w = waitpid(-1, &status, options);
        if (w == cli) {
            cli = -1;
        } else if (w == srv) {
            srv = -1;
        } else {
            /* ? */
            ret = -1;
            goto done;
        }
        /* wait 0.1s for max ten times for the other process to terminate,
         * then just exit */
        if (closewait > 10) {
            ret = -1;
            goto done;
        }

        usleep(100000);
        closewait++;
    }
    ret = 0;

done:
    if (cli > 0) {
        kill(cli, SIGKILL);
    }
    if (srv > 0) {
        kill(srv, SIGKILL);
    }
    return ret;
}

int main(int argc, const char *main_argv[])
{
    int opt;
    poptContext pc;
    int opt_version = 0;
    int opt_all = 0;
    char *opt_target = NULL;
    int ret, i, k;

    struct poptOption long_options[] = {
        POPT_AUTOHELP
        {"target", 't', POPT_ARG_STRING, &opt_target, 0, \
         _("Specify the target name used for the tests"), NULL}, \
        {"version", '\0', POPT_ARG_NONE, &opt_version, 0, \
         _("Print version number and exit"), NULL }, \
        {"all", '\0', POPT_ARG_NONE, &opt_all, 0, \
         _("Test all gssproxy modes"), NULL }, \
        POPT_TABLEEND
    };

    argv = main_argv;

    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
    while((opt = poptGetNextOpt(pc)) != -1) {
        switch(opt) {
        default:
            fprintf(stderr, "\nInvalid option %s: %s\n\n",
                    poptBadOption(pc, 0), poptStrerror(opt));
            poptPrintUsage(pc, stderr, 0);
            return 1;
        }
    }

    if (opt_version) {
        puts(VERSION""DISTRO_VERSION""PRERELEASE_VERSION);
        return 0;
    }

    if (opt_target == NULL) {
        fprintf(stderr, "Missing target!\n");
        poptPrintUsage(pc, stderr, 0);
        return 1;
    }

    if (!opt_all) {
            return run_cli_srv_test(PROXY_LOCAL_ONLY,
                                    PROXY_LOCAL_ONLY,
                                    opt_target);
    }

    for (i=0; i<4; i++) {
        for (k=0; k<4; k++) {
            ret = run_cli_srv_test(i, k, opt_target);
            fprintf(stderr, "Running test with server proxy mode %s "
                    "and client proxy mode %s %s\n",
                    lookup_gssproxy_behavior(i),
                    lookup_gssproxy_behavior(k),
                    ret ? "failed" : "succeeded");
            if (ret) {
                return ret;
            }
        }
    }

    return ret;
}
