#include "pamc_private.h"

/*
 * Copyright (c) 1998 Andrew G. Morgan <morgan@linux.kernel.org>
 * All rights reserved.
 *
 * The license for this source code should accompany this file.  Its
 * md5 checksum is: cac41ac118b52c96cf248baec7237381
 */

extern char **environ;

/*
 * This code implements a loadable interface for plug-in authentication
 * agents.  The agents are executed by this program and communication
 * with the agents is via 2 pipes.
 */

/*
 * Agent initialization
 */

int pamc_set_agent(pamc_handle_t pch, const char *agent_id)
{
    int i, pipe1[2], pipe2[2];
    pid_t agent_pid = 0;
    char *path;

    D(("called"));
    /* If the handle was dead don't proceed */
    if (pch == NULL) {
	D(("bad handle"));
	return PAMC_CONTROL_FAIL;
    }

    /* Verify we have been given an agent id */
    if (agent_id == NULL) {                             /* default agent */
	D(("using default agent [%p]", pch->current));
	return ( pch->current ? PAMC_CONTROL_OK:PAMC_CONTROL_FAIL );
    }

    /*
     * The agent identifier is a series of (ASCII) alphanumeric
     * characters.  In addition the '.', '_' and '-' characters are
     * permitted.  Identifiers are not explicitly limited in length
     * but the application may impose some limits on its length.
     */

    for (i=0; agent_id[i]; ++i) {
	if (!(isalnum(agent_id[i]) || agent_id[i] == '.' || agent_id[i] == '_'
	      || agent_id[i] == '-')) {
	    D(("bad agent name (%c)", agent_id[i]));
	    return PAMC_CONTROL_FAIL;                   /* agent unknown */
	}
    }

    /*
     * Spin through small list of agents (if this is likely to get
     * long we should use a more advanced method for storing the
     * agents)
     */

    for (pch->current=pch->stack; pch->current;
	 pch->current=pch->current->next) {
	if (!strcmp(pch->current->id, agent_id)) {
	    pch->current->status = PAMC_CONTROL_BUSY;
	    D(("located agent"));
	    return PAMC_CONTROL_OK;
	}
    }

    /*
     * This is a new agent - attempt to load it
     */

    /* Construct path */
    i = strlen(PAMC_DEFAULT_PATH)+strlen(pch->path)+strlen(agent_id);
    path = (char *) malloc(i);
    if (path == NULL) {
	D(("no path obtained"));
	return PAMC_CONTROL_FAIL;
    }
    sprintf(path, PAMC_DEFAULT_PATH_FORMAT, pch->path, agent_id);
    D(("requested agent is [%s]", path));

    /* Open two pipes */
    pipe1[0] = pipe1[1] = pipe2[0] = pipe2[1] = -1;
    if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
	D(("openning pipe(s) failed"));
	goto drop_pipes;
    }

    /* Start the agent */
    agent_pid = fork();
    if (agent_pid == 0) {
	/* we are CHILD */
	D(("child running"));

	/* drop privileges */
	setuid(getuid());
	setgid(getgid());
	D(("privileges dropped"));

	/* make pipe fd's override stdin/stdout */
	if (dup2(pipe1[0], STDIN_FILENO) == -1
	    || dup2(pipe2[1], STDOUT_FILENO) == -1) {
	    goto drop_pipes;
	}

	/* mark client side of pipes */
	fcntl(STDIN_FILENO, F_SETFD, 0);
	fcntl(STDOUT_FILENO, F_SETFD, 0);
	fcntl(pipe1[0], F_SETFD, 1);
	fcntl(pipe1[1], F_SETFD, 1);
	fcntl(pipe2[0], F_SETFD, 1);
	fcntl(pipe2[1], F_SETFD, 1);

	/* execute the agent - with no arguments and no environment */
	environ = NULL;
	D(("doing exec as u/gids(%d %d %d %d)", getuid(), geteuid(),
	  getgid(), getegid()));
	execl(path, "pamc-agent", NULL);

	/* should not get here */
	D(("exec failed!"));
	goto drop_pipes;
    } else if (agent_pid == -1) {
	/* this is a problem - we are PARENT */
	D(("fork failed"));
	goto drop_pipes;
    } else {
	/* this is what we want - we are PARENT */

	/* allocate a new agent structure */
	pch->current = (struct pamc_agent_struct *)
	    malloc(sizeof(struct pamc_agent_struct));
	if (pch->current == NULL) {
	    goto drop_pipes;
	}
	pch->current->id = strdup(agent_id);
	if (pch->current->id == NULL) {
	    free(pch->current);
	    pch->current = NULL;
	    goto drop_pipes;
	}
	pch->current->next = pch->stack;
	pch->current->previous = NULL;
	pch->current->pid = agent_pid;
	pch->current->to_agent = pipe1[1];
	pch->current->from_agent = pipe2[0];
	pch->current->status = PAMC_CONTROL_BUSY;

	/* close agent side of pipes */
	(void) close(pipe1[0]);
	(void) close(pipe2[1]);

	/* link to beginning of stack of known agents */
	if (pch->stack != NULL) {
	    pch->stack->previous = pch->current;
	}
	pch->stack = pch->current;

	/* return new agent as current */
	D(("done"));
	return PAMC_CONTROL_OK;
    }

drop_pipes:
    if (pipe1[0] != -1) {
	(void) close(pipe1[0]);
	(void) close(pipe1[1]);
	if (pipe2[0] != -1) {
	    (void) close(pipe2[0]);
	    (void) close(pipe2[1]);
	}
    }

    if (path) {
	pamc_scrub((void **)&path, strlen(path));
    }

    /* failed */
    if (agent_pid) {
	D(("parent failing"));
	return PAMC_CONTROL_FAIL;
    }

    /* child failed */
    D(("child failing"));
    exit(1);
}


