diff options
Diffstat (limited to '')
-rw-r--r-- | libpamc/pamc_load.c | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/libpamc/pamc_load.c b/libpamc/pamc_load.c new file mode 100644 index 0000000..24a65df --- /dev/null +++ b/libpamc/pamc_load.c @@ -0,0 +1,477 @@ +/* + * $Id$ + * + * Copyright (c) 1999 Andrew G. Morgan <morgan@ftp.kernel.org> + * + * pamc_load + */ + +#include "libpamc.h" + +static int __pamc_exec_agent(pamc_handle_t pch, pamc_agent_t *agent) +{ + char *full_path; + int found_agent, length, reset_length, to_agent[2], from_agent[2]; + int return_code = PAM_BPC_FAIL; + + if (agent->id[agent->id_length] != '\0') { + PAM_BP_ASSERT("libpamc: internal error agent_id not terminated"); + } + + for (length=0; (length < agent->id_length); ++length) { + switch (agent->id[length]) { + case '/': + D(("ill formed agent id")); + return PAM_BPC_FAIL; + } + } + + /* enough memory for any path + this agent */ + reset_length = 3 + pch->max_path + agent->id_length; + D(("reset_length = %d (3+%d+%d)", + reset_length, pch->max_path, agent->id_length)); + full_path = malloc(reset_length); + if (full_path == NULL) { + D(("no memory for agent path")); + return PAM_BPC_FAIL; + } + + found_agent = 0; + for (length=0; pch->agent_paths[length]; ++length) { + struct stat buf; + + D(("path: [%s]", pch->agent_paths[length])); + D(("agent id: [%s]", agent->id)); + + sprintf(full_path, "%s/%s", pch->agent_paths[length], agent->id); + + D(("looking for agent here: [%s]\n", full_path)); + if (stat(full_path, &buf) == 0) { + D(("file existis")); + found_agent = 1; + break; + } + } + + if (! found_agent) { + D(("no agent was found")); + goto free_and_return; + } + + if (pipe(to_agent)) { + D(("failed to open pipe to agent")); + goto free_and_return; + } + + if (pipe(from_agent)) { + D(("failed to open pipe from agent")); + goto close_the_agent; + } + + agent->pid = fork(); + if (agent->pid == -1) { + + D(("failed to fork for agent")); + goto close_both_pipes; + + } else if (agent->pid == 0) { + + int i; + + dup2(from_agent[1], STDOUT_FILENO); + dup2(to_agent[0], STDIN_FILENO); + + /* we close all of the files that have filedescriptors lower + and equal to twice the highest we have seen, The idea is + that we don't want to leak filedescriptors to agents from a + privileged client application. + + XXX - this is a heuristic at this point. There is a growing + need for an extra 'set param' libpamc function, that could + be used to supply info like the highest fd to close etc.. + */ + + if (from_agent[1] > pch->highest_fd_to_close) { + pch->highest_fd_to_close = 2*from_agent[1]; + } + + for (i=0; i <= pch->highest_fd_to_close; ++i) { + switch (i) { + case STDOUT_FILENO: + case STDERR_FILENO: + case STDIN_FILENO: + /* only these three remain open */ + break; + default: + (void) close(i); /* don't care if its not open */ + } + } + + /* we make no attempt to drop other privileges - this library + has no idea how that would be done in the general case. It + is up to the client application (when calling + pamc_converse) to make sure no privilege will leak into an + (untrusted) agent. */ + + /* we propagate no environment - future versions of this + library may have the ability to audit all agent + transactions. */ + + D(("exec'ing agent %s", full_path)); + execle(full_path, "pam-agent", NULL, NULL); + + D(("exec failed")); + _exit(1); + + } + + close(to_agent[0]); + close(from_agent[1]); + + agent->writer = to_agent[1]; + agent->reader = from_agent[0]; + + return_code = PAM_BPC_TRUE; + goto free_and_return; + +close_both_pipes: + close(from_agent[0]); + close(from_agent[1]); + +close_the_agent: + close(to_agent[0]); + close(to_agent[1]); + +free_and_return: + memset(full_path, 0, reset_length); + free(full_path); + + D(("returning %d", return_code)); + + return return_code; +} + +/* + * has the named agent been loaded? + */ + +static int __pamc_agent_is_enabled(pamc_handle_t pch, const char *agent_id) +{ + pamc_agent_t *agent; + + for (agent = pch->chain; agent; agent = agent->next) { + if (!strcmp(agent->id, agent_id)) { + D(("agent already loaded")); + return PAM_BPC_TRUE; + } + } + + D(("agent is not loaded")); + return PAM_BPC_FALSE; +} + +/* + * has the named agent been disabled? + */ + +static int __pamc_agent_is_disabled(pamc_handle_t pch, const char *agent_id) +{ + pamc_blocked_t *blocked; + + for (blocked=pch->blocked_agents; blocked; blocked = blocked->next) { + if (!strcmp(agent_id, blocked->id)) { + D(("agent is disabled")); + return PAM_BPC_TRUE; + } + } + + D(("agent is not disabled")); + return PAM_BPC_FALSE; +} + +/* + * disable an agent + */ + +int pamc_disable(pamc_handle_t pch, const char *agent_id) +{ + pamc_blocked_t *block; + + if (pch == NULL) { + D(("pch is NULL")); + return PAM_BPC_FALSE; + } + + if (agent_id == NULL) { + D(("agent_id is NULL")); + return PAM_BPC_FALSE; + } + + if (__pamc_agent_is_enabled(pch, agent_id) != PAM_BPC_FALSE) { + D(("agent is already loaded")); + return PAM_BPC_FALSE; + } + + if (__pamc_agent_is_disabled(pch, agent_id) != PAM_BPC_FALSE) { + D(("agent is already disabled")); + return PAM_BPC_TRUE; + } + + block = calloc(1, sizeof(pamc_blocked_t)); + if (block == NULL) { + D(("no memory for new blocking structure")); + return PAM_BPC_FALSE; + } + + block->id = malloc(1 + strlen(agent_id)); + if (block->id == NULL) { + D(("no memory for agent id")); + free(block); + return PAM_BPC_FALSE; + } + + strcpy(block->id, agent_id); + block->next = pch->blocked_agents; + pch->blocked_agents = block; + + return PAM_BPC_TRUE; +} + +/* + * force the loading of a particular agent + */ + +int pamc_load(pamc_handle_t pch, const char *agent_id) +{ + pamc_agent_t *agent; + int length; + + /* santity checking */ + + if (pch == NULL) { + D(("pch is NULL")); + return PAM_BPC_FALSE; + } + + if (agent_id == NULL) { + D(("agent_id is NULL")); + return PAM_BPC_FALSE; + } + + if (__pamc_agent_is_disabled(pch, agent_id) != PAM_BPC_FALSE) { + D(("sorry agent is disabled")); + return PAM_BPC_FALSE; + } + + length = strlen(agent_id); + + /* scan list to see if agent is loaded */ + + if (__pamc_agent_is_enabled(pch, agent_id) == PAM_BPC_TRUE) { + D(("no need to load an already loaded agent (%s)", agent_id)); + return PAM_BPC_TRUE; + } + + /* not in the list, so we need to load it and add it to the head + of the chain */ + + agent = calloc(1, sizeof(pamc_agent_t)); + if (agent == NULL) { + D(("no memory for new agent")); + return PAM_BPC_FALSE; + } + agent->id = calloc(1, 1+length); + if (agent->id == NULL) { + D(("no memory for new agent's id")); + goto fail_free_agent; + } + memcpy(agent->id, agent_id, length); + agent->id[length] = '\0'; + agent->id_length = length; + + if (__pamc_exec_agent(pch, agent) != PAM_BPC_TRUE) { + D(("unable to exec agent")); + goto fail_free_agent_id; + } + + agent->next = pch->chain; + pch->chain = agent; + + return PAM_BPC_TRUE; + +fail_free_agent_id: + + memset(agent->id, 0, agent->id_length); + free(agent->id); + + memset(agent, 0, sizeof(*agent)); + +fail_free_agent: + + free(agent); + return PAM_BPC_FALSE; +} + +/* + * what's a valid agent name? + */ + +int __pamc_valid_agent_id(int id_length, const char *id) +{ + int post, i; + + for (i=post=0 ; i < id_length; ++i) { + int ch = id[i++]; + + if (isalpha(ch) || isdigit(ch) || (ch == '_')) { + continue; + } else if (post && (ch == '.')) { + continue; + } else if ((i > 1) && (!post) && (ch == '@')) { + post = 1; + } else { + D(("id=%s contains '%c' which is illegal", id, ch)); + return 0; + } + } + + if (!i) { + D(("length of id is 0")); + return 0; + } else { + return 1; /* id is valid */ + } +} + +/* + * building a tree of available agent names + */ + +static pamc_id_node_t *__pamc_add_node(pamc_id_node_t *root, const char *id, + int *counter) +{ + if (root) { + + int cmp; + + if ((cmp = strcmp(id, root->agent_id))) { + if (cmp > 0) { + root->right = __pamc_add_node(root->right, id, + &(root->child_count)); + } else { + root->left = __pamc_add_node(root->left, id, + &(root->child_count)); + } + } + + return root; + + } else { + + pamc_id_node_t *node = calloc(1, sizeof(pamc_id_node_t)); + + if (node) { + node->agent_id = malloc(1+strlen(id)); + if (node->agent_id) { + strcpy(node->agent_id, id); + } else { + free(node); + node = NULL; + } + } + + (*counter)++; + return node; + } +} + +/* + * drop all of the tree and any remaining ids + */ + +static pamc_id_node_t *__pamc_liberate_nodes(pamc_id_node_t *tree) +{ + if (tree) { + if (tree->agent_id) { + free(tree->agent_id); + tree->agent_id = NULL; + } + + tree->left = __pamc_liberate_nodes(tree->left); + tree->right = __pamc_liberate_nodes(tree->right); + + tree->child_count = 0; + free(tree); + } + + return NULL; +} + +/* + * fill a list with the contents of the tree (in ascii order) + */ + +static void __pamc_fill_list_from_tree(pamc_id_node_t *tree, char **agent_list, + int *counter) +{ + if (tree) { + __pamc_fill_list_from_tree(tree->left, agent_list, counter); + agent_list[(*counter)++] = tree->agent_id; + tree->agent_id = NULL; + __pamc_fill_list_from_tree(tree->right, agent_list, counter); + } +} + +/* + * get a list of the available agents + */ + +char **pamc_list_agents(pamc_handle_t pch) +{ + int i, total_agent_count=0; + pamc_id_node_t *tree = NULL; + char **agent_list; + + /* loop over agent paths */ + + for (i=0; pch->agent_paths[i]; ++i) { + DIR *dir; + + dir = opendir(pch->agent_paths[i]); + if (dir) { + struct dirent *item; + + while ((item = readdir(dir))) { + + /* this is a cheat on recognizing agent_ids */ + if (!__pamc_valid_agent_id(strlen(item->d_name), + item->d_name)) { + continue; + } + + tree = __pamc_add_node(tree, item->d_name, &total_agent_count); + } + + closedir(dir); + } + } + + /* now, we build a list of ids */ + D(("total of %d available agents\n", total_agent_count)); + + agent_list = calloc(total_agent_count+1, sizeof(char *)); + if (agent_list) { + int counter=0; + + __pamc_fill_list_from_tree(tree, agent_list, &counter); + if (counter != total_agent_count) { + PAM_BP_ASSERT("libpamc: internal error transcribing tree"); + } + } else { + D(("no memory for agent list")); + } + + __pamc_liberate_nodes(tree); + + return agent_list; +} |