#include "settings.h" extern int errno; #include /* private interface methods start*/ static gboolean copy_file(const gchar* source, const gchar* destination) ; static gboolean jail_new_from_file(const gchar* dir, gchar* canonical_name, struct jail** out) ; static gboolean jail_is_assigned(GKeyFile* settings, int uid); static int settings_get_free_jail(GKeyFile* settings); static gboolean settings_set_jail_uid(GKeyFile* settings, const gchar* jail_name, int uid); /* private interface methods end */ // convert from host:ip:port to ip:port static gchar* get_host(gchar* group_host) { return group_host+strlen("host:"); } // convert from enum to char enum hostaction char2enum(gchar* action) { if (strcasecmp(action, "deny") == 0) return DENY; else if(strcasecmp(action, "allow") == 0) return ALLOW; else if(strcasecmp(action, "ask") == 0) return ASK; else if(strcasecmp(action, "allowentry") == 0) return ALLOWENTRY; else return ERROR; } // convert from char to enum gchar* enum2char(enum hostaction action) { if (action == DENY) return "deny"; else if(action == ALLOW) return "allow"; else if(action == ASK) return "ask"; else if(action == ALLOWENTRY) return "allowentry"; else return "error"; } /* taken from gnucash and slightly modified */ GKeyFile * settings_load_from_file (const gchar * filename, gboolean ignore_error, gboolean return_empty_struct) { GKeyFile *key_file; GError *error = NULL; g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "settings_load_from_file ENTER\n"); if (!filename) return FALSE; if (!g_file_test (filename, G_FILE_TEST_EXISTS)) return NULL; key_file = g_key_file_new (); if (!key_file) return NULL; if (g_key_file_load_from_file(key_file, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "settings_load_from_file LEAVE SUCCESS\n"); return key_file; } /* An error occurred */ if (!return_empty_struct) { g_key_file_free (key_file); key_file = NULL; } if (!ignore_error) g_warning ("Unable to read file %s: %s\n", filename, error->message); g_error_free (error); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "settings_load_from_file LEAVE FAILURE\n"); return key_file; } /* taken from gnucash and slightly modified */ gboolean settings_save_to_file (const gchar * filename, GKeyFile * key_file, GError ** error) { gchar *contents; gint fd; gint length; ssize_t written; gboolean success = TRUE; if (filename == NULL || key_file == NULL) return FALSE; contents = g_key_file_to_data (key_file, NULL, NULL); length = strlen (contents); fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd == -1) { if (error) { *error = g_error_new (G_FILE_ERROR, g_file_error_from_errno (errno), "Cannot open file %s: %s", filename, strerror (errno)); } else { g_critical ("Cannot open file %s: %s\n", filename, strerror (errno)); } g_free (contents); return FALSE; } written = write (fd, contents, length); if (written == -1) { success = FALSE; if (error) { *error = g_error_new (G_FILE_ERROR, g_file_error_from_errno (errno), "Cannot write to file %s: %s", filename, strerror (errno)); } else { g_critical ("Cannot write to file %s: %s\n", filename, strerror (errno)); } close (fd); } else if (written != length) { success = FALSE; if (error) { *error = g_error_new (G_FILE_ERROR, g_file_error_from_errno (errno), "File %s truncated (provided %d, written %d)", filename, length, (int) written); } else { g_critical ("File %s truncated (provided %d, written %d)", filename, length, (int) written); } /* Ignore any error */ close (fd); } else if (close (fd) == -1) { if (error) { *error = g_error_new (G_FILE_ERROR, g_file_error_from_errno (errno), "Close failed for file %s: %s", filename, strerror (errno)); } else { g_warning ("Close failed for file %s: %s", filename, strerror (errno)); } } g_free (contents); return success; } // print the host structure in human readable form void host_print(struct host* h) { g_assert(h != NULL); gchar** execve = h->execve; g_print("host_addr:\t%s\n", h->host_addr); g_print("action:\t%s\n", enum2char(h->action)); g_print("cmd:\t%s\n", h->cmd); g_print("execve:{"); while (execve && *execve) { g_print("%s ",*execve); execve++; } g_print("}\n"); g_print("keys:{"); FOREACH_LIST(h->keys, key) { g_print("%s ", (char*)key->data); } g_print("}\n"); } struct host* host_dup(struct host* old) { struct host* ret = malloc(sizeof (struct host)); if (ret == NULL) g_error("no memory left\n"); memset(ret, 0, sizeof(struct host)); // make the new rule COPY OVER ret->action = old->action; SAFE_STRNCPY(ret->host_addr, old->host_addr, MAX_HOST); SAFE_STRNCPY(ret->cmd, old->cmd, MAX_PATH); if (ret->execve) ret->execve = g_strdupv(old->execve); FOREACH_LIST(ret->keys, key) { ret->keys = g_list_append(ret->keys, key->data); } return ret; } // load policies void policy_new(int main_uid, struct policies* out) { DIR* proc; struct dirent* dirp; struct jail* new_jail; char default_path[MAX_PATH+1]; int uid = -1; GError *err = NULL; out->uid2jailstruct = g_hash_table_new(NULL, NULL); proc = opendir(out->policy_path); if (proc == NULL) g_error("load_policy:could not open policy dir %s:%s\n", out->policy_path, g_strerror(errno)); if (!SAFE_SNPRINTF(default_path, MAX_PATH, "%s/%s", out->policy_path, "default")) { g_error("policy_new:failure to concatenate paths\n"); } if (access(default_path, R_OK)) { g_error("policy_new:could not find the default policy in %s\n", default_path); } // try to load the main policy // if (jail_new_from_file(out->policy_path, "main", &new_jail)==FALSE) // g_error("load_policy:could not main policy dir:%s\n", g_strerror(errno)); // out->main = new_jail; // g_hash_table_insert(out->uid2jailstruct, GINT_TO_POINTER(main_uid), new_jail); while ( (dirp = readdir(proc)) ) { // load the policy but ignore main and default policies if (strcmp(dirp->d_name, "..")==0 || strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "default") == 0) continue; if (jail_new_from_file(out->policy_path, dirp->d_name, &new_jail)) { g_print("loaded jail with canonical namd %s\n", dirp->d_name); // find which uid this jail is associated with uid = settings_get_jail_uid(out->settings, dirp->d_name); if (uid == -1) { uid = settings_get_free_jail(out->settings); if (uid == -1) { g_print("policy_new:there are no free slots for this new jail\n"); continue; } g_print("no associate for jail %s will associate with uid %d\n", new_jail->name, uid); settings_set_jail_uid(out->settings, new_jail->name, uid); // update the settings file settings_save_to_file(out->settings_file, out->settings, &err); if (err) g_error("policy_new: failed to update settings file\n"); } g_hash_table_insert(out->uid2jailstruct, GINT_TO_POINTER(uid), new_jail); } } if (proc) closedir(proc); return; } // NOT FINISHED AT ALL void policy_free(struct policies* in) { if (in) { free(in); } // clear up the hash table and all the jails } // match the host wildcard with a given uid. if we find it, we return the pointer to the host structure // that describes the policy for that host gboolean jail_match_policy(const gchar* hostregex, struct jail* jail_in, struct host** host_out) { pcre* re = NULL; const char *error = NULL; gint erroffset; int rc; int ovector[30]; struct host* host; g_assert(jail_in != NULL); g_assert(hostregex != NULL); // iterate over all sequentially, and try to match the host via regex FOREACH_LIST(jail_in->hosts, host_key) { host = (struct host*)host_key->data; // compile regex re = pcre_compile(host->host_addr, 0, &error, &erroffset, NULL); if (re == NULL) { g_warning("jail_match_policy:%s\n", error?error:""); return FALSE; } rc = pcre_exec(re, NULL, hostregex, strlen(hostregex), 0, 0, ovector, 30); if (rc >= 0) { pcre_free(re); *host_out = host; return TRUE; } else if (re) pcre_free(re); } return FALSE; } // free the host void host_free(struct host* h) { g_assert(h != NULL); if (h->execve) g_strfreev(h->execve); while (h->keys) { if (h->keys->data) { free(h->keys->data); } h->keys = h->keys->next; } free(h); } // clean up the linked list of host structures void jail_free(struct jail* in){ g_assert(in != NULL); while (in->hosts) { if (in->hosts->data) { host_free((struct host*)in->hosts->data); } in->hosts = in->hosts->next; } free(in); } struct policy_search_result {struct jail* jail_result; struct host* host_result; gchar* host_addr;} srch; // callback for g_hash_table_foreach. pretty ugly void has_primary_host(gpointer key, gpointer value, gpointer user_data) { struct jail* haystack = (struct jail*)value; // current jail that we are lokoing at struct host* host_result; // resultant which will point to the host structure found // we already found it if (srch.jail_result != NULL) // this will be called many times return; // if search->ret is already filled in, somebody found it before us if (jail_match_policy(srch.host_addr, haystack, &host_result)) { srch.jail_result = (host_result->action==ALLOWENTRY)?(haystack):(NULL); // this will pass the result back to the caller srch.host_result = host_result; return; } } // given a certain policy, find in which jail resides the primary host addr host_addr // and return the pointer to it gboolean policy_search_entry_point( struct policies* policy, gchar* host_regex, struct jail** jail_result, struct host** host_result) { srch.jail_result = NULL; srch.host_result = NULL; srch.host_addr = host_regex; g_hash_table_foreach(policy->uid2jailstruct, has_primary_host, &srch); *host_result = srch.host_result; *jail_result = srch.jail_result; return *jail_result != NULL && *host_result != NULL; } struct jail* policy_get_jail_by_uid(struct policies* policy, int uid) { return (struct jail*) g_hash_table_lookup(policy->uid2jailstruct, GINT_TO_POINTER(uid)); } // send the host structure in text format to the data stream pointed to by out gboolean host_write_struct(FILE* fout, struct host* target_host) { GList* key = NULL; // dump the host params fprintf(fout, "[host#%s]\n", target_host->host_addr); fprintf(fout, "action=%s\n", enum2char(target_host->action)); if (target_host->cmd) fprintf(fout, "cmd=%s\n", target_host->cmd); if (target_host->execve) fprintf(fout, "execve=%s\n", g_strjoinv(";", target_host->execve)); key = target_host->keys; while (key && key->data) { fprintf(fout, "%s\n", (char*)key->data); key = key->next; } return TRUE; } // take the command field in target_host and substitute away any string of type {key with} // the actual value of the key if it is defined. if not defined, null will be placed gboolean host_cmd_substition(struct host* target_host, gchar** resultant_cmd, ...) { GList *key_node = target_host->keys; gchar* s; gchar* key = NULL; gchar* value = NULL; gchar* ret = g_strdup(target_host->cmd); va_list ap; // FIRST WE DO SUBSTITUTION ON THE VARGS va_start(ap, resultant_cmd); while (s) { gchar *temp; s = va_arg(ap, gchar *); if (!s) break; key = str_unpack_key(s); value = str_unpack_value(s); if (key == NULL || value == NULL) { if (key) free(key); if (value) free(value); g_warning("host_cmd_substition:malformed substition\n"); continue; } // case sensitive string replace temp = str_replace(ret, key, value); g_assert(temp != NULL); // dump the old return string and replace it with the replaced string free(ret); free(key); free(value); ret = temp; } va_end(ap); // SECOND WE DO SUBSTITUTION ON THE POLICY FILE PARAMETERS while (key_node && key_node->data) { gchar *temp; key = str_unpack_key(key_node->data); value = str_unpack_value(key_node->data); if (key == NULL || value == NULL) { if (key) free(key); if (value) free(value); g_warning("host_cmd_substition:malformed substition\n"); continue; } // case sensitive string replace temp = str_replace(ret, key, value); g_assert(temp != NULL); // dump the old return string and replace it with the replaced string free(ret); free(key); free(value); ret = temp; key_node = key_node->next; } *resultant_cmd = ret; return TRUE; } struct host* host_new() { struct host* ret = malloc(sizeof(struct host)); memset(ret, 0, sizeof(struct host)); return ret; } gboolean host_new_from_settings(GKeyFile* jail_settings, gchar* host_string, struct host** host_out) { gchar** keys = NULL, **keys_iter = NULL; gsize size; GError* err = NULL; gchar* string_key = NULL; gchar** string_list = NULL; gboolean error_free = TRUE; g_assert(host_out != NULL); // we will iterate over all the keys to get and save the mapping keys = keys_iter = g_key_file_get_keys(jail_settings, host_string, &size, &err); if (keys == NULL) { g_warning("get_jail_struct:error during g_key_file_get_keys for %s\n", host_string); return FALSE; } // allocate a host structure to hold the resultant *host_out = (struct host*)malloc(sizeof(struct host)); if (*host_out == NULL) return FALSE; memset(*host_out, 0, sizeof(struct host)); // null it // get the host address if (!SAFE_STRNCPY((*host_out)->host_addr, get_host(host_string), MAX_HOST)) error_free = FALSE; while (*keys_iter && error_free == TRUE) { error_free = FALSE; // we are not error free until we extract the key string_key = NULL; string_key = NULL; if (strcmp(*keys_iter, "cmd") == 0) { string_key = g_key_file_get_string(jail_settings, host_string, "cmd", &err); if (err) break; if (!SAFE_STRNCPY((*host_out)->cmd, string_key?string_key:"", MAX_HOST)) { g_free(string_key); break; } g_free(string_key); } else if (strcmp(*keys_iter, "execve") == 0) { string_list = g_key_file_get_string_list(jail_settings, host_string, "execve", &size, &err); if (err) break; if (string_list) // duplicate the list (*host_out)->execve = g_strdupv(string_list); g_strfreev(string_list); } else if (strcmp(*keys_iter, "action") == 0) { string_key = g_key_file_get_string(jail_settings, host_string, "action", &err); if (err) break; (*host_out)->action = char2enum(string_key); g_free(string_key); if ((*host_out)->action == ERROR) break; } else { gchar* string_key = g_key_file_get_string(jail_settings, host_string, *keys_iter, &err); if (err) break; // we will collect all the variables that are not a part of the policy defined keys (*host_out)->keys = g_list_append((*host_out)->keys, g_strdup_printf("%s=%s", *keys_iter, string_key)); g_free(string_key); } error_free = TRUE; keys_iter++; } if (error_free == FALSE) { if (keys) g_strfreev(keys); if (*host_out) host_free(*host_out); *host_out = NULL; g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "host_new_from_settings LEAVE FAILURE\n"); return FALSE; } return TRUE; } // extract the jail structure from the file gboolean jail_new_from_file(const gchar* dir, gchar* canonical_name, struct jail** jail_out) { char jail_path[MAX_PATH+1]; gsize size; gchar** groups = NULL, **groups_iter = NULL; GKeyFile* jail_settings = NULL; struct host* new_host; gboolean error_free = TRUE; g_assert(jail_out != NULL); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "jail_new_from_file ENTER\n"); *jail_out = (struct jail*)malloc(sizeof(struct jail)); if (*jail_out == NULL) { g_warning("jail_new_from_file:%s\n", g_strerror(errno)); return FALSE; } memset(*jail_out, 0, sizeof(struct jail)); // store the jail canonical name in the jail structure if (!SAFE_STRNCPY((*jail_out)->name, canonical_name, MAX_PATH)) { g_warning("jail_new_from_file:could not set canonical_name\n"); free(*jail_out); *jail_out = NULL; return FALSE; } // get where this policy is located on disk if (!SAFE_SNPRINTF(jail_path, MAX_PATH, "%s/%s", dir, canonical_name)) { g_warning("jail_new_from_file:could not concatenate paths\n"); free(*jail_out); *jail_out = NULL; return FALSE; } // open the policy file jail_settings = settings_load_from_file(jail_path, FALSE, FALSE); if (jail_settings == NULL) { free(*jail_out); *jail_out = NULL; g_warning("jail_new_from_file:error during open settings %s\n%s\n", jail_path, strerror(errno)); return FALSE; } // get all the hosts for the jail groups_iter = groups = g_key_file_get_groups(jail_settings, &size); if (groups == NULL) { free(*jail_out); *jail_out = NULL; g_key_file_free(jail_settings); g_warning("jail_new_from_file:error during g_key_file_get_groups for %s\n", jail_path); return FALSE; } // iterate over all the hosts while (*groups_iter && error_free == TRUE) { error_free = FALSE; if (strstr(*groups_iter, "host#") == *groups_iter) { if (!host_new_from_settings(jail_settings, *groups_iter, &new_host)) break; (*jail_out)->hosts = g_list_append((*jail_out)->hosts, new_host); // add it to the jail list error_free = TRUE; } groups_iter++; } if (error_free == FALSE) { g_warning("jail_new_from_file:error during g_key_get_xxxxx:command for %s\n", jail_path); jail_free(*jail_out); *jail_out = NULL; } g_strfreev(groups); g_key_file_free(jail_settings); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "jail_new_from_file LEAVE SUCCESS\n"); return error_free; } // make a new jail with either an existing name // or make a jail name which will be of the form