/*
 * This is a small test program for testing libpamc against the
 * secret@here agent. It does the same as the test.secret@here perl
 * script in this directory, but via the libpamc API.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <security/pam_client.h>
#include <ctype.h>

struct internal_packet {
    int length;
    int at;
    char *buffer;
};


void append_data(struct internal_packet *packet, int extra, const char *data)
{
    if ((extra + packet->at) >= packet->length) {
	if (packet->length == 0) {
	    packet->length = 1000;
	}
	/* make sure we have at least a char extra space available */
	while (packet->length <= (extra + packet->at)) {
	    packet->length <<= 1;
	}
	packet->buffer = realloc(packet->buffer, packet->length);
	if (packet->buffer == NULL) {
	    fprintf(stderr, "out of memory\n");
	    exit(1);
	}
    }

    if (data != NULL) {
	memcpy(packet->at + packet->buffer, data, extra);
    }
    packet->at += extra;

    /* assisting string manipulation */
    packet->buffer[packet->at] = '\0';
}

void append_string(struct internal_packet *packet, const char *string,
		   int with_nul)
{
    append_data(packet, strlen(string) + (with_nul ? 1:0), string);
}

char *identify_secret(char *identity)
{
    struct internal_packet temp_packet;
    FILE *secrets;
    int length_id;

    temp_packet.length = temp_packet.at = 0;
    temp_packet.buffer = NULL;

    append_string(&temp_packet, "/home/", 0);
    append_string(&temp_packet, getlogin(), 0);
    append_string(&temp_packet, "/.secret@here", 1);

    secrets = fopen(temp_packet.buffer, "r");
    if (secrets == NULL) {
	fprintf(stderr, "server: failed to open\n  [%s]\n",
		temp_packet.buffer);
	exit(1);
    }

    length_id = strlen(identity);
    for (;;) {
	char *secret = NULL;
	temp_packet.at = 0;

	if (fgets(temp_packet.buffer, temp_packet.length, secrets) == NULL) {
	    fclose(secrets);
	    return NULL;
	}

	if (memcmp(temp_packet.buffer, identity, length_id)) {
	    continue;
	}

	fclose(secrets);
	for (secret=temp_packet.buffer; *secret; ++secret) {
	    if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
		break;
	    }
	}
	for (; *secret; ++secret) {
	    if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
		break;
	    }
	}

	for (temp_packet.buffer=secret; *temp_packet.buffer;
	     ++temp_packet.buffer) {
	    if (*temp_packet.buffer == ' ' || *temp_packet.buffer == '\n'
		|| *temp_packet.buffer == '\t') {
		break;
	    }
	}
	if (*temp_packet.buffer) {
	    *temp_packet.buffer = '\0';
	}

	return secret;
    }

    /* NOT REACHED */
}

/*
 * This is a hack, and is fundamentally insecure. All our secrets will be
 * displayed on the command line for someone doing 'ps' to see. This
 * is just for programming convenience in this instance, since this
 * program is simply a regression test. The pam_secret module should
 * not do this, but make use of md5 routines directly.
 */

char *create_digest(int length, const char *raw)
{
    struct internal_packet temp_packet;
    FILE *pipe;

    temp_packet.length = temp_packet.at = 0;
    temp_packet.buffer = NULL;

    append_string(&temp_packet, "echo -n '", 0);
    append_string(&temp_packet, raw, 0);
    append_string(&temp_packet, "'|/usr/bin/md5sum -", 1);

    fprintf(stderr, "am attempting to run [%s]\n", temp_packet.buffer);

    pipe = popen(temp_packet.buffer, "r");
    if (pipe == NULL) {
	fprintf(stderr, "server: failed to run\n  [%s]\n", temp_packet.buffer);
	exit(1);
    }

    temp_packet.at = 0;
    append_data(&temp_packet, 32, NULL);

    if (fgets(temp_packet.buffer, 33, pipe) == NULL) {
	fprintf(stderr, "server: failed to read digest\n");
	exit(1);
    }
    if (strlen(temp_packet.buffer) != 32) {
	fprintf(stderr, "server: digest was not 32 chars?? [%s]\n",
		temp_packet.buffer);
	exit(1);
    }

    pclose(pipe);

    return temp_packet.buffer;
}

void packet_to_prompt(pamc_bp_t *prompt_p, uint8_t control,
		      struct internal_packet *packet)
{
    PAM_BP_RENEW(prompt_p, control, packet->at);
    PAM_BP_FILL(*prompt_p, 0, packet->at, packet->buffer);
    packet->at = 0;
}

void prompt_to_packet(pamc_bp_t prompt, struct internal_packet *packet)
{
    int data_length;

    data_length = PAM_BP_LENGTH(prompt);
    packet->at = 0;
    append_data(packet, data_length, NULL);

    PAM_BP_EXTRACT(prompt, 0, data_length, packet->buffer);

    fprintf(stderr, "server received[%d]: {%d|0x%.2x|%s}\n",
	    data_length,
	    PAM_BP_SIZE(prompt), PAM_BP_RCONTROL(prompt),
	    PAM_BP_RDATA(prompt));
}

int main(int argc, char **argv)
{
    pamc_handle_t pch;
    pamc_bp_t prompt = NULL;
    struct internal_packet packet_data, *packet;
    char *temp_string, *secret, *user, *a_cookie, *seqid, *digest;
    const char *cookie = "123451234512345";
    int retval;

    packet = &packet_data;
    packet->length = 0;
    packet->at = 0;
    packet->buffer = NULL;

    pch = pamc_start();
    if (pch == NULL) {
	fprintf(stderr, "server: unable to get a handle from libpamc\n");
	exit(1);
    }

    temp_string = getlogin();
    if (temp_string == NULL) {
	fprintf(stderr, "server: who are you?\n");
	exit(1);
    }
#define DOMAIN "@local.host"
    user = malloc(1+strlen(temp_string)+strlen(DOMAIN));
    if (user == NULL) {
	fprintf(stderr, "server: out of memory for user id\n");
	exit(1);
    }
    sprintf(user, "%s%s", temp_string, DOMAIN);

    append_string(packet, "secret@here/", 0);
    append_string(packet, user, 0);
    append_string(packet, "|", 0);
    append_string(packet, cookie, 0);
    packet_to_prompt(&prompt, PAM_BPC_SELECT, packet);

    /* get the library to accept the first packet (which should load
       the secret@here agent) */

    retval = pamc_converse(pch, &prompt);
    fprintf(stderr, "server: after conversation\n");
    if (PAM_BP_RCONTROL(prompt) != PAM_BPC_OK) {
	fprintf(stderr, "server: prompt had unexpected control type: %u\n",
		PAM_BP_RCONTROL(prompt));
	exit(1);
    }

    fprintf(stderr, "server: got a prompt back\n");

    prompt_to_packet(prompt, packet);

    temp_string = strtok(packet->buffer, "|");
    if (temp_string == NULL) {
	fprintf(stderr, "server: prompt does not contain anything");
	exit(1);
    }
    seqid = strdup(temp_string);
    if (seqid == NULL) {
	fprintf(stderr, "server: unable to store sequence id\n");
    }

    temp_string = strtok(NULL, "|");
    if (temp_string == NULL) {
	fprintf(stderr, "server: no cookie from agent\n");
	exit(1);
    }
    a_cookie = strdup(temp_string);
    if (a_cookie == NULL) {
	fprintf(stderr, "server: no memory to store agent cookie\n");
	exit(1);
    }

    fprintf(stderr, "server: agent responded with {%s|%s}\n", seqid, a_cookie);
    secret = identify_secret(user);
    fprintf(stderr, "server: secret=%s\n", secret);

    /* now, we construct the response */
    packet->at = 0;
    append_string(packet, a_cookie, 0);
    append_string(packet, "|", 0);
    append_string(packet, cookie, 0);
    append_string(packet, "|", 0);
    append_string(packet, secret, 0);

    fprintf(stderr, "server: get digest of %s\n", packet->buffer);

    digest = create_digest(packet->at, packet->buffer);

    fprintf(stderr, "server: secret=%s, digest=%s\n", secret, digest);

    packet->at = 0;
    append_string(packet, seqid, 0);
    append_string(packet, "|", 0);
    append_string(packet, digest, 0);
    packet_to_prompt(&prompt, PAM_BPC_OK, packet);

    retval = pamc_converse(pch, &prompt);
    fprintf(stderr, "server: after 2nd conversation\n");
    if (PAM_BP_RCONTROL(prompt) != PAM_BPC_DONE) {
	fprintf(stderr, "server: 2nd prompt had unexpected control type: %u\n",
		PAM_BP_RCONTROL(prompt));
	exit(1);
    }

    prompt_to_packet(prompt, packet);
    PAM_BP_RENEW(&prompt, 0, 0);

    temp_string = strtok(packet->buffer, "|");
    if (temp_string == NULL) {
	fprintf(stderr, "no digest from agent\n");
	exit(1);
    }
    temp_string = strdup(temp_string);

    packet->at = 0;
    append_string(packet, secret, 0);
    append_string(packet, "|", 0);
    append_string(packet, cookie, 0);
    append_string(packet, "|", 0);
    append_string(packet, a_cookie, 0);

    fprintf(stderr, "server: get digest of %s\n", packet->buffer);

    digest = create_digest(packet->at, packet->buffer);

    fprintf(stderr, "server: digest=%s\n", digest);

    if (strcmp(digest, temp_string)) {
	fprintf(stderr, "server: agent doesn't know the secret\n");
	fprintf(stderr, "server: agent says:  [%s]\n"
	                "server: server says: [%s]\n", temp_string, digest);
	exit(1);
    } else {
	fprintf(stderr, "server: agent seems to know the secret\n");

	packet->at = 0;
	append_string(packet, cookie, 0);
	append_string(packet, "|", 0);
	append_string(packet, secret, 0);
	append_string(packet, "|", 0);
	append_string(packet, a_cookie, 0);

	digest = create_digest(packet->at, packet->buffer);

	fprintf(stderr, "server: putenv(\"AUTH_SESSION_TICKET=%s\")\n",
		digest);
    }


    retval = pamc_end(&pch);

    fprintf(stderr, "server: agent(s) were %shappy to terminate\n",
	    retval == PAM_BPC_TRUE ? "":"un");

    exit(!retval);
}
