// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*- // Copyright (c) 2001-2007 International Computer Science Institute // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software") // to deal in the Software without restriction, subject to the conditions // listed in the XORP LICENSE file. These conditions include: you must // preserve this copyright notice, and you cannot mention the copyright // holders in advertising related to the Software without their permission. // The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This // notice is a summary of the XORP LICENSE file; the license in that file is // legally binding. #ident "$XORP: xorp/rtrmgr/master_conf_tree.cc,v 1.77 2007/03/15 07:39:53 pavlin Exp $" #include "rtrmgr_module.h" #include "libxorp/xorp.h" #include "libxorp/xlog.h" #include "libxorp/debug.h" #include "libxorp/utils.hh" #ifdef HAVE_GRP_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #include "master_conf_tree.hh" #include "module_command.hh" #include "rtrmgr_error.hh" #include "template_commands.hh" #include "master_template_tree.hh" #include "master_template_tree_node.hh" #include "util.hh" #ifdef HOST_OS_WINDOWS // XXX: Use unlink emulation from MS VC runtime. #ifdef unlink #undef unlink #endif #define unlink(x) _unlink(x) // Stub out umask. #ifdef umask #undef umask #endif #define umask(x) // Stub out fchown. #ifdef fchown #undef fchown #endif #define fchown(x,y,z) (0) #endif // // The strings that are used to add and delete a load or save file, to // access the content of the loaded file, etc. // // XXX: sigh, hard-coding the strings here is bad... static string RTRMGR_CONFIG_NODE = "rtrmgr"; static string RTRMGR_CONFIG = "rtrmgr {\n}\n"; static string RTRMGR_SAVE_FILE_CONFIG = "rtrmgr {\n save %s\n}\n"; static string RTRMGR_LOAD_FILE_CONFIG = "rtrmgr {\n load %s\n}\n"; static string RTRMGR_CONFIG_FILENAME_VARNAME = "$(rtrmgr.CONFIG_FILENAME)"; /************************************************************************* * Master Config Tree class *************************************************************************/ MasterConfigTree::MasterConfigTree(const string& config_file, MasterTemplateTree* tt, ModuleManager& mmgr, XorpClient& xclient, bool global_do_exec, bool verbose) throw (InitError) : ConfigTree(tt, verbose), _root_node(verbose), _commit_in_progress(false), _config_failed(false), _rtrmgr_config_node_found(false), _xorp_gid(0), _is_xorp_gid_set(false), // // XXX: set _enable_program_exec_id to true to enable // setting the user and group ID when executing external helper // programs during (re)configuration. Note that this doesn't apply // for running the XORP processes themselves. // _enable_program_exec_id(false), _config_tree_copy(NULL) { string configuration; string error_msg; _current_node = &_root_node; _task_manager = new TaskManager(*this, mmgr, xclient, global_do_exec, verbose); #ifdef HAVE_GRP_H // Get the group ID for group "xorp" struct group* grp = getgrnam("xorp"); if (grp != NULL) { _xorp_gid = grp->gr_gid; _is_xorp_gid_set = true; } #else _xorp_gid = 0; _is_xorp_gid_set = true; #endif if (read_file(configuration, config_file, error_msg) != true) { xorp_throw(InitError, error_msg); } if (parse(configuration, config_file, error_msg) != true) { xorp_throw(InitError, error_msg); } // // Go through the config tree, and create nodes for any defaults // specified in the template tree that aren't already configured. // add_default_children(); if (root_node().check_config_tree(error_msg) != true) { xorp_throw(InitError, error_msg); } // // If we got this far, it looks like we have a good bootfile. // The bootfile is now stored in the ConfigTree. // Now do the second pass, starting processes and configuring things. // execute(); } MasterConfigTree::MasterConfigTree(TemplateTree* tt, bool verbose) : ConfigTree(tt, verbose), _root_node(verbose), _task_manager(NULL), _config_tree_copy(NULL) { _current_node = &_root_node; } MasterConfigTree::~MasterConfigTree() { remove_tmp_config_file(); delete _task_manager; if (_config_tree_copy != NULL) delete _config_tree_copy; } MasterConfigTree& MasterConfigTree::operator=(const MasterConfigTree& orig_tree) { master_root_node().clone_subtree(orig_tree.const_master_root_node()); return *this; } ConfigTree* MasterConfigTree::create_tree(TemplateTree *tt, bool verbose) { MasterConfigTree *mct; mct = new MasterConfigTree(tt, verbose); return mct; } bool MasterConfigTree::read_file(string& configuration, const string& config_file, string& error_msg) { FILE* file = fopen(config_file.c_str(), "r"); if (file == NULL) { error_msg = c_format("Failed to open config file: %s\n", config_file.c_str()); return false; } static const uint32_t MCT_READBUF = 8192; char buf[MCT_READBUF + 1]; while (feof(file) == 0) { size_t bytes = fread(buf, 1, sizeof(buf) - 1, file); // NUL terminate it. buf[bytes] = '\0'; if (bytes > 0) { configuration += buf; } } fclose(file); return true; } bool MasterConfigTree::parse(const string& configuration, const string& config_file, string& error_msg) { if (ConfigTree::parse(configuration, config_file, error_msg) != true) return false; string s = show_tree(/*numbered*/ true); debug_msg("== MasterConfigTree::parse yields ==\n%s\n" "====================================\n", s.c_str()); return true; } void MasterConfigTree::execute() { debug_msg("##############################################################\n"); debug_msg("MasterConfigTree::execute\n"); list changed_modules = find_changed_modules(); list::const_iterator iter; string s; for (iter = changed_modules.begin(); iter != changed_modules.end(); ++iter) { if (! s.empty()) s += ", "; s += (*iter); } XLOG_INFO("Changed modules: %s", s.c_str()); _commit_cb = callback(this, &MasterConfigTree::config_done); commit_changes_pass2(); } void MasterConfigTree::config_done(bool success, string error_msg) { if (success) debug_msg("Configuration done: success\n"); else XLOG_ERROR("Configuration failed: %s", error_msg.c_str()); _config_failed = !success; if (! success) { _config_failed = true; _config_failed_msg = error_msg; return; } string error_msg2; if (check_commit_status(error_msg2) == false) { XLOG_ERROR("%s", error_msg2.c_str()); _config_failed = true; _config_failed_msg = error_msg2; return; } debug_msg("MasterConfigTree::config_done returning\n"); } ConfigTreeNode* MasterConfigTree::create_node(const string& segment, const string& path, const TemplateTreeNode* ttn, ConfigTreeNode* parent_node, const ConfigNodeId& node_id, uid_t user_id, bool verbose) { MasterConfigTreeNode *ctn, *parent; parent = dynamic_cast(parent_node); if (parent_node != NULL) XLOG_ASSERT(parent != NULL); ctn = new MasterConfigTreeNode(segment, path, ttn, parent, node_id, user_id, verbose); return reinterpret_cast(ctn); } list MasterConfigTree::find_changed_modules() const { debug_msg("Find changed modules\n"); set changed_modules; const_master_root_node().find_changed_modules(changed_modules); list ordered_modules; order_module_list(changed_modules, ordered_modules); return ordered_modules; } list MasterConfigTree::find_active_modules() const { debug_msg("Find active modules\n"); set active_modules; const_master_root_node().find_active_modules(active_modules); list ordered_modules; order_module_list(active_modules, ordered_modules); return ordered_modules; } list MasterConfigTree::find_inactive_modules() const { debug_msg("Find inactive modules\n"); set all_modules; list ordered_all_modules; debug_msg("All modules:\n"); const_master_root_node().find_all_modules(all_modules); order_module_list(all_modules, ordered_all_modules); set active_modules; list ordered_active_modules; debug_msg("Active modules:\n"); const_master_root_node().find_active_modules(active_modules); order_module_list(active_modules, ordered_active_modules); // Remove things that are common to both lists while (!ordered_active_modules.empty()) { list::iterator iter; bool found = false; for (iter = ordered_all_modules.begin(); iter != ordered_all_modules.end(); ++iter) { if (*iter == ordered_active_modules.front()) { ordered_all_modules.erase(iter); ordered_active_modules.pop_front(); found = true; break; } } XLOG_ASSERT(found == true); } debug_msg("Inactive Module Order: "); list::const_iterator final_iter; for (final_iter = ordered_all_modules.begin(); final_iter != ordered_all_modules.end(); ++final_iter) { debug_msg("%s ", final_iter->c_str()); } debug_msg("\n"); return ordered_all_modules; } void MasterConfigTree::order_module_list(const set& module_set, list& ordered_modules) const { multimap depends; // first depends on second set no_info; // modules we found no info about set satisfied; // modules we found no info about set additional_modules; // modules that we depend on that // aren't in module_set // // We've found the list of modules that have changed that need to // be applied. Now we need to sort them so that the modules to be // performed first are at the beginning of the list. // // Handle the degenerate cases simply if (module_set.empty()) return; set::const_iterator iter; for (iter = module_set.begin(); iter != module_set.end(); ++iter) { ModuleCommand* mc = _template_tree->find_module(*iter); if (mc == NULL) { no_info.insert(*iter); debug_msg("%s has no module info\n", (*iter).c_str()); continue; } if (mc->depends().empty()) { // // This module doesn't depend on anything else, so it can // be started early in the sequence. // ordered_modules.push_back(*iter); satisfied.insert(*iter); debug_msg("%s has no dependencies\n", (*iter).c_str()); continue; } list::const_iterator di; for (di = mc->depends().begin(); di != mc->depends().end(); ++di) { debug_msg("%s depends on %s\n", iter->c_str(), di->c_str()); depends.insert(pair(*iter, *di)); // Check that the dependency is already in our list of modules. if (module_set.find(*di)==module_set.end()) { // If not, add it. additional_modules.insert(*di); } } } debug_msg("doing additional modules\n"); // Figure out the dependencies for all the additional modules set additional_done; while (!additional_modules.empty()) { set::iterator mod_iter; mod_iter = additional_modules.begin(); ModuleCommand* mc = _template_tree->find_module(*mod_iter); if (mc == NULL) { debug_msg("%s has no info\n", (*mod_iter).c_str()); additional_done.insert(*mod_iter); additional_modules.erase(mod_iter); ordered_modules.push_back(*mod_iter); satisfied.insert(*mod_iter); continue; } if (mc->depends().empty()) { debug_msg("%s has no dependencies\n", (*mod_iter).c_str()); additional_done.insert(*mod_iter); ordered_modules.push_back(*mod_iter); satisfied.insert(*mod_iter); additional_modules.erase(mod_iter); continue; } list::const_iterator di; for (di = mc->depends().begin(); di != mc->depends().end(); ++di) { depends.insert(pair(*mod_iter, *di)); debug_msg("%s depends on %s\n", mod_iter->c_str(), di->c_str()); // Check that the dependency is already in our list of modules. if (module_set.find(*di) == module_set.end() && additional_modules.find(*di) == additional_modules.end() && additional_done.find(*di) == additional_done.end()) { // If not, add it. additional_modules.insert(*di); } } additional_done.insert(*mod_iter); additional_modules.erase(mod_iter); } debug_msg("done additional modules\n"); multimap::iterator curr_iter, next_iter; while (!depends.empty()) { bool progress_made = false; curr_iter = depends.begin(); while (curr_iter != depends.end()) { next_iter = curr_iter; ++next_iter; debug_msg("searching for dependency for %s on %s\n", curr_iter->first.c_str(), curr_iter->second.c_str()); if (satisfied.find(curr_iter->second) != satisfied.end()) { // Rule is now satisfied. string module = curr_iter->first; depends.erase(curr_iter); progress_made = true; debug_msg("dependency of %s on %s satisfied\n", module.c_str(), curr_iter->second.c_str()); if (depends.find(module) == depends.end()) { // This was the last dependency satisfied.insert(module); ordered_modules.push_back(module); debug_msg("dependencies for %s now satisfied\n", module.c_str()); } } curr_iter = next_iter; } if (progress_made == false) { XLOG_FATAL("Module dependencies cannot be satisfied"); } } // finally add the modules for which we have no information for (iter = no_info.begin(); iter != no_info.end(); ++iter) { ordered_modules.push_back(*iter); } debug_msg("Module Order: "); list::const_iterator final_iter; for (final_iter = ordered_modules.begin(); final_iter != ordered_modules.end(); ++final_iter) { debug_msg("%s ", final_iter->c_str()); } debug_msg("\n"); } void MasterConfigTree::commit_changes_pass1(CallBack cb) { string error_msg; debug_msg("##############################################################\n"); debug_msg("MasterConfigTree::commit_changes_pass1\n"); _commit_in_progress = true; list changed_modules = find_changed_modules(); list inactive_modules = find_inactive_modules(); list::const_iterator iter; debug_msg("Changed modules:\n"); for (iter = changed_modules.begin(); iter != changed_modules.end(); ++iter) { debug_msg("%s ", (*iter).c_str()); } debug_msg("\n"); // // Two passes: the first checks for errors. If no errors are // found, attempt the actual commit. // /*******************************************************************/ /* Pass 1: check for errors without actually doing anything */ /*******************************************************************/ _task_manager->reset(); _task_manager->set_do_exec(false, true); _task_manager->set_exec_id(_exec_id); _commit_cb = cb; master_root_node().initialize_commit(); if (root_node().check_config_tree(error_msg) == false) { // Something went wrong - return the error message. _commit_in_progress = false; cb->dispatch(false, error_msg); return; } // Sort the changes in order of module dependencies for (iter = changed_modules.begin(); iter != changed_modules.end(); ++iter) { if (!module_config_start(*iter, error_msg)) { _commit_in_progress = false; cb->dispatch(false, error_msg); return; } } bool needs_activate = false; bool needs_update = false; if (master_root_node().commit_changes(*_task_manager, /* do_commit = */ false, 0, 0, error_msg, needs_activate, needs_update) == false) { // Something went wrong - return the error message. _commit_in_progress = false; cb->dispatch(false, error_msg); return; } #if 0 // // XXX: don't shutdown any modules yet, because in this stage // we are checking only for errors. // for (iter = inactive_modules.begin(); iter != inactive_modules.end(); ++iter) { _task_manager->shutdown_module(*iter); } #endif // 0 _task_manager->run(callback(this, &MasterConfigTree::commit_pass1_done)); } void MasterConfigTree::commit_pass1_done(bool success, string error_msg) { debug_msg("##############################################################\n"); debug_msg("## commit_pass1_done\n"); if (success) { commit_changes_pass2(); } else { string msg = "Commit pass 1 failed: " + error_msg; XLOG_ERROR("%s", msg.c_str()); _commit_in_progress = false; _commit_cb->dispatch(false, error_msg); } } void MasterConfigTree::commit_changes_pass2() { string error_msg; debug_msg("##############################################################\n"); debug_msg("## commit_changes_pass2\n"); _commit_in_progress = true; if (root_node().check_config_tree(error_msg) == false) { XLOG_ERROR("Configuration tree error: %s", error_msg.c_str()); _commit_in_progress = false; _commit_cb->dispatch(false, error_msg); return; } /*******************************************************************/ /* Pass 2: implement the changes */ /*******************************************************************/ list changed_modules = find_changed_modules(); list inactive_modules = find_inactive_modules(); list::const_iterator iter; _task_manager->reset(); _task_manager->set_do_exec(true, false); _task_manager->set_exec_id(_exec_id); master_root_node().initialize_commit(); // Sort the changes in order of module dependencies for (iter = changed_modules.begin(); iter != changed_modules.end(); ++iter) { if (!module_config_start(*iter, error_msg)) { XLOG_ERROR("Commit failed in deciding startups"); _commit_in_progress = false; _commit_cb->dispatch(false, error_msg); return; } } bool needs_activate = false; bool needs_update = false; if (!master_root_node().commit_changes(*_task_manager, /* do_commit = */ true, 0, 0, error_msg, needs_activate, needs_update)) { // Abort the commit XLOG_ERROR("Commit failed in config tree"); _commit_in_progress = false; _commit_cb->dispatch(false, error_msg); return; } for (iter = inactive_modules.begin(); iter != inactive_modules.end(); ++iter) { _task_manager->shutdown_module(*iter); } _task_manager->run(callback(this, &MasterConfigTree::commit_pass2_done)); } void MasterConfigTree::commit_pass2_done(bool success, string error_msg) { debug_msg("##############################################################\n"); debug_msg("## commit_pass2_done\n"); if (success) debug_msg("## commit seems successful\n"); else XLOG_ERROR("Commit failed: %s", error_msg.c_str()); _commit_cb->dispatch(success, error_msg); _commit_in_progress = false; } bool MasterConfigTree::check_commit_status(string& error_msg) { debug_msg("check_commit_status\n"); bool success = master_root_node().check_commit_status(error_msg); if (success) { // If the commit was successful, clear all the temporary state. debug_msg("commit was successful, finalizing...\n"); master_root_node().finalize_commit(); debug_msg("finalizing done\n"); } return success; } string MasterConfigTree::discard_changes() { debug_msg("##############################################################\n"); debug_msg("MasterConfigTree::discard_changes\n"); string result = root_node().discard_changes(0, 0); debug_msg("##############################################################\n"); return result; } string MasterConfigTree::mark_subtree_for_deletion(const list& path_segments, uid_t user_id) { ConfigTreeNode *found = find_node(path_segments); if (found == NULL) return "ERROR"; if ((found->parent() != NULL) && found->parent()->is_tag() && found->parent()->children().size()==1) { found = found->parent(); } found->mark_subtree_for_deletion(user_id); return string("OK"); } void MasterConfigTree::delete_entire_config() { root_node().mark_subtree_for_deletion(0); root_node().undelete(); // We don't need a verification pass - this isn't allowed to fail. _commit_cb = callback(this, &MasterConfigTree::config_done); commit_changes_pass2(); } bool MasterConfigTree::lock_node(const string& /* node */, uid_t /* user_id */, uint32_t /* timeout */, uint32_t& /* holder */) { // TODO: XXX: not implemented yet return true; } bool MasterConfigTree::unlock_node(const string& /* node */, uid_t /* user_id */) { // TODO: XXX: not implemented yet return true; } bool MasterConfigTree::save_to_file(const string& filename, uid_t user_id, string& error_msg) { string dummy_error_msg; string full_filename = config_full_filename(filename); FILE* file = NULL; error_msg = ""; // // TODO: there are lots of hard-coded values below. Fix this! // // // Set the effective group to "xorp" and the effective user ID to the // uid of the user that sent the request. // if (! _is_xorp_gid_set) { error_msg = "Group \"xorp\" does not exist on this system"; return false; } _exec_id.set_uid(user_id); _exec_id.set_gid(_xorp_gid); _exec_id.save_current_exec_id(); if (_exec_id.set_effective_exec_id(error_msg) != XORP_OK) { _exec_id.restore_saved_exec_id(dummy_error_msg); return false; } #ifdef HOST_OS_WINDOWS { #else // Set a umask of 664, to allow sharing of config files between // users in group "xorp". mode_t orig_mask = umask(S_IWOTH); struct stat sb; if (stat(full_filename.c_str(), &sb) == 0) { if ((sb.st_mode & S_IFREG) == 0) { if (((sb.st_mode & S_IFMT) == S_IFCHR) || ((sb.st_mode & S_IFMT) == S_IFBLK)) { error_msg = c_format("File %s is a special device.\n", full_filename.c_str()); } else if ((sb.st_mode & S_IFMT) == S_IFIFO) { error_msg = c_format("File %s is a named pipe.\n", full_filename.c_str()); } else if ((sb.st_mode & S_IFMT) == S_IFDIR) { error_msg = c_format("File %s is a directory.\n", full_filename.c_str()); } _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return false; } #endif // ! HOST_OS_WINDOWS file = fopen(full_filename.c_str(), "r"); if (file != NULL) { // We've been asked to overwrite a file char line[80]; if (fgets(line, sizeof(line) - 1, file) == NULL) { error_msg = c_format("File %s exists, but an error occured " "when trying to check that it was OK to " "overwrite it\n", full_filename.c_str()); error_msg += "File was NOT overwritten\n"; fclose(file); _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return false; } if (strncmp(line, "/*XORP", 6) != 0) { error_msg = c_format("File %s exists, but it is not an " "existing XORP config file.\n", full_filename.c_str()); error_msg += "File was NOT overwritten\n"; fclose(file); _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return false; } fclose(file); file = NULL; } // It seems OK to overwrite this file if (unlink(full_filename.c_str()) < 0 && errno != ENOENT) { error_msg = c_format("File %s exists, and can not be " "overwritten.\n", full_filename.c_str()); error_msg += strerror(errno); if (file != NULL) { fclose(file); } _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return false; } } file = fopen(full_filename.c_str(), "w"); if (file == NULL) { error_msg = c_format("Could not create file \"%s\"", full_filename.c_str()); error_msg += strerror(errno); _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return false; } // Write the file header string header = "/*XORP Configuration File, v1.0*/\n"; size_t bytes; bytes = fwrite(header.c_str(), sizeof(char), header.size(), file); if (bytes < header.size()) { fclose(file); error_msg = c_format("Error writing to file \"%s\"\n", full_filename.c_str()); // We couldn't even write the header - clean up if we can if (unlink(full_filename.c_str()) == 0) { error_msg += "Save aborted; truncated file has been removed\n"; } else { error_msg += "Save aborted; truncated file may exist\n"; } _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return false; } // Write the config to the file string config = show_unannotated_tree(/*numbered*/ false); bytes = fwrite(config.c_str(), sizeof(char), config.size(), file); if (bytes < config.size()) { fclose(file); error_msg = c_format("Error writing to file \"%s\"\n", full_filename.c_str()); error_msg += strerror(errno); error_msg += "\n"; // // We couldn't write the config - clean up if we can // if (unlink(full_filename.c_str()) == 0) { error_msg += "Save aborted; truncated file has been removed\n"; } else { error_msg += "Save aborted; truncated file may exist\n"; } _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return false; } // Set file group correctly if (fchown(fileno(file), user_id, _xorp_gid) < 0) { // This shouldn't be able to happen, but if it does, it // shouldn't be fatal. error_msg = "WARNING: failed to set saved file to be group \"xorp\"\n"; } // Close properly and clean up if (fclose(file) != 0) { error_msg = c_format("Error closing file \"%s\"\n", full_filename.c_str()); error_msg += strerror(errno) + string("\n"); error_msg += "File may not have been written correctly\n"; _exec_id.restore_saved_exec_id(dummy_error_msg); return false; } error_msg += "Save complete\n"; _exec_id.restore_saved_exec_id(dummy_error_msg); umask(orig_mask); return true; } string MasterConfigTree::config_full_filename(const string& filename) const { string result; // // If the name of the config directory wasn't set return the filename // itself. // if (_config_directory.empty()) return (filename); XLOG_ASSERT(! filename.empty()); // // If this is an absolute filename then return the filename itself // if (is_absolute_path(filename)) return (filename); // // Concatenate the name of the config directory with the filename // result = _config_directory; if (result[result.size() - 1] != PATH_DELIMITER_CHAR) result += PATH_DELIMITER_STRING; result += filename; return (result); } void MasterConfigTree::remove_tmp_config_file() { string tmp_config_filename_value; if (root_node().expand_variable(RTRMGR_CONFIG_FILENAME_VARNAME, tmp_config_filename_value, false) != true) { tmp_config_filename_value = ""; } else { // Reset the variable root_node().set_variable(RTRMGR_CONFIG_FILENAME_VARNAME, ""); } // // Unlink the file(s). Typically, the _tmp_config_filename value // should be same as the expand_variable(RTRMGR_CONFIG_FILENAME_VARNAME) // value. However, some of the template actions may explicitly set // the value of that variable to something else, hence we need to cover // that case. // if (! _tmp_config_filename.empty()) unlink(_tmp_config_filename.c_str()); if ((! tmp_config_filename_value.empty()) && (tmp_config_filename_value != _tmp_config_filename)) { unlink(tmp_config_filename_value.c_str()); } _tmp_config_filename = ""; } bool MasterConfigTree::set_config_file_permissions(FILE* fp, uid_t user_id, string& error_msg) { #ifdef HOST_OS_WINDOWS UNUSED(fp); UNUSED(user_id); UNUSED(error_msg); return true; #else // ! HOST_OS_WINDOWS // // Set the user and group owner of the file, and change its permissions // so it is group-writable. // struct group *grp = getgrnam("xorp"); if (grp == NULL) { error_msg = c_format("group \"xorp\" does not exist on this system"); return false; } gid_t group_id = grp->gr_gid; if (fchown(fileno(fp), user_id, group_id) != 0) { error_msg = c_format("error changing the owner and group of the " "file: %s", strerror(errno)); return false; } if (fchmod(fileno(fp), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) != 0) { error_msg = c_format("error changing the mode of the file: %s", strerror(errno)); return false; } return true; #endif // ! HOST_OS_WINDOWS } bool MasterConfigTree::change_config(uid_t user_id, CallBack cb, string& error_msg) { string dummy_error_msg; // Initialize the execution ID if (_enable_program_exec_id) { _exec_id.set_uid(user_id); if (_is_xorp_gid_set) _exec_id.set_gid(_xorp_gid); } commit_changes_pass1(cb); if (config_failed()) { error_msg = config_failed_msg(); return false; } return true; } bool MasterConfigTree::apply_config_change(uid_t user_id, string& error_msg, const string& deltas, const string& deletions, ConfigChangeCallBack cb) { // // Create a copy of the local tree that will be used later to compute // the configuration changes. // if (_config_tree_copy != NULL) delete _config_tree_copy; _config_tree_copy = new MasterConfigTree(_template_tree, verbose()); *_config_tree_copy = *this; // // Apply the configuration changes // if (apply_deltas(user_id, deltas, true /* provisional_change */, true /* preserve_node_id */, error_msg) == false) { return (false); } if (apply_deletions(user_id, deletions, /* provisional_change */ true, error_msg) == false) { return (false); } // // Add nodes providing default values. Note: they shouldn't be // needed, but adding them here acts as a safety mechanism against // a client that forgets to add them. // add_default_children(); CallBack cb2 = callback(this, &MasterConfigTree::apply_config_commit_changes_cb, cb); return (change_config(user_id, cb2, error_msg)); } void MasterConfigTree::apply_config_commit_changes_cb(bool success, string error_msg, ConfigChangeCallBack cb) { string deltas, deletions; MasterConfigTree delta_tree(_template_tree, _verbose); MasterConfigTree deletion_tree(_template_tree, _verbose); // // Compute the configuration changes // XLOG_ASSERT(_config_tree_copy != NULL); _config_tree_copy->diff_configs(*this, delta_tree, deletion_tree); deltas = delta_tree.show_unannotated_tree(/*numbered*/ true); deletions = deletion_tree.show_unannotated_tree(/*numbered*/ true); cb->dispatch(success, error_msg, deltas, deletions); } bool MasterConfigTree::save_config(const string& filename, uid_t user_id, string& error_msg, ConfigSaveCallBack cb) { string save_file_config; XLOG_TRACE(_verbose, "Saving to file %s", filename.c_str()); // // Create the string that is used to temporary add a save file to the // configuration. // save_file_config = c_format(RTRMGR_SAVE_FILE_CONFIG.c_str(), filename.c_str()); // // Test whether the file name matches an entry in the template tree. // If yes, then add a save file to the configuration so the appropriate // external program will be invoked. // Otherwise, assume that the user tries to save to a local file. // do { MasterConfigTree new_tree(_template_tree, _verbose); if (new_tree.parse(save_file_config, "", error_msg) == true) { // // The file name is recognizable by the parser, hence // invoke the corresponding external methods for processing. // break; } // // Assume that the user tries to save to a local file // if (save_to_file(filename, user_id, error_msg) != true) { XLOG_TRACE(_verbose, "Failed to save file %s: %s", filename.c_str(), error_msg.c_str()); return false; } XLOG_TRACE(_verbose, "Saved file %s", filename.c_str()); // // Schedule a timer to dispatch immediately the callback // EventLoop& eventloop = xorp_client().eventloop(); _save_config_completed_timer = eventloop.new_oneoff_after( TimeVal::ZERO(), callback(this, &MasterConfigTree::save_config_done_cb, true, // success string(""), // error_msg cb)); return true; } while (false); // // Create a temporary file that would be used by the external // program to copy the configuration from. // do { FILE* fp; // Create the file fp = xorp_make_temporary_file("", "xorp_rtrmgr_tmp_config_file", _tmp_config_filename, error_msg); if (fp == NULL) { error_msg = c_format("Cannot save the configuration file: " "cannot create a temporary filename: %s", error_msg.c_str()); _tmp_config_filename = ""; return false; } // Set the file permissions if (set_config_file_permissions(fp, user_id, error_msg) != true) { error_msg = c_format("Cannot save the configuration file: %s", error_msg.c_str()); // Close and remove the file fclose(fp); remove_tmp_config_file(); return false; } // // Save the current configuration // string config = show_unannotated_tree(/*numbered*/ false); if (fwrite(config.c_str(), sizeof(char), config.size(), fp) != static_cast(config.size())) { error_msg = c_format("Cannot save the configuration file: " "error writing to a temporary file: %s", strerror(errno)); // Close and remove the file fclose(fp); remove_tmp_config_file(); return false; } // // Close the file descriptor, because we don't need it. // Note, that the created file remains on the filesystem, // so it is guaranteed to be unique when we need to use it again. // fclose(fp); break; } while (false); // // Check whether the current configuration already contains // rtrmgr configuration. // list path; path.push_back(RTRMGR_CONFIG_NODE); if (find_node(path) != NULL) { _rtrmgr_config_node_found = true; } else { _rtrmgr_config_node_found = false; } // // Add the temporary save file to the configuration // if (apply_deltas(user_id, save_file_config, true /* provisional_change */, false /* preserve_node_id */, error_msg) != true) { error_msg = c_format("Cannot save the configuration file: %s", error_msg.c_str()); remove_tmp_config_file(); discard_changes(); return false; } // // Go through the config tree, and create nodes for any defaults // specified in the template tree that aren't already configured. // add_default_children(); // Save the name of the temporary file in the local variable if (root_node().set_variable(RTRMGR_CONFIG_FILENAME_VARNAME, _tmp_config_filename.c_str()) != true) { error_msg = c_format("Cannot save the configuration file: " "cannot store temporary filename %s in internal " "variable %s", _tmp_config_filename.c_str(), RTRMGR_CONFIG_FILENAME_VARNAME.c_str()); remove_tmp_config_file(); return false; } // // Specify the callback that will be invoked after we have saved the new // configuration file so we can continue from there. // CallBack save_cb; save_cb = callback(this, &MasterConfigTree::save_config_file_sent_cb, filename, user_id, cb); if (change_config(user_id, save_cb, error_msg) != true) { remove_tmp_config_file(); discard_changes(); return false; } return true; } void MasterConfigTree::save_config_file_sent_cb(bool success, string error_msg, string filename, uid_t user_id, ConfigSaveCallBack cb) { string dummy_error_msg; // // Remove the temporary file with the configuration // remove_tmp_config_file(); if (! success) { XLOG_TRACE(_verbose, "Failed to save file %s: %s", filename.c_str(), error_msg.c_str()); discard_changes(); cb->dispatch(success, error_msg); return; } // // Check everything really worked, and finalize the commit when // the file was saved. // if (check_commit_status(error_msg) == false) { XLOG_TRACE(_verbose, "Check commit status indicates failure: %s", error_msg.c_str()); discard_changes(); cb->dispatch(false, error_msg); return; } XLOG_TRACE(_verbose, "Saved file %s", filename.c_str()); // // Create the string that is used to delete the temporary added // save file from the configuration. // string delete_save_file_config; if (_rtrmgr_config_node_found) { delete_save_file_config = c_format(RTRMGR_SAVE_FILE_CONFIG.c_str(), filename.c_str()); } else { delete_save_file_config = RTRMGR_CONFIG; } if (apply_deletions(user_id, delete_save_file_config, /* provisional_change */ true, error_msg) != true) { error_msg = c_format("Cannot save the configuration file because of " "internal error: %s", error_msg.c_str()); discard_changes(); cb->dispatch(false, error_msg); return; } // // Go through the config tree, and create nodes for any defaults // specified in the template tree that aren't already configured. // add_default_children(); // // Specify the callback that will be invoked after we have cleaned-up // so we can continue from there. // bool orig_success = success; string orig_error_msg = error_msg; CallBack cleanup_cb; cleanup_cb = callback(this, &MasterConfigTree::save_config_file_cleanup_cb, orig_success, orig_error_msg, filename, user_id, cb); if (change_config(user_id, cleanup_cb, error_msg) != true) { discard_changes(); cb->dispatch(false, error_msg); return; } } void MasterConfigTree::save_config_file_cleanup_cb(bool success, string error_msg, bool orig_success, string orig_error_msg, string filename, uid_t user_id, ConfigSaveCallBack cb) { if (! orig_success) { XLOG_TRACE(_verbose, "Failed to save file %s: %s", filename.c_str(), orig_error_msg.c_str()); discard_changes(); cb->dispatch(orig_success, orig_error_msg); return; } if (! success) { XLOG_TRACE(_verbose, "Failed to save file %s: %s", filename.c_str(), error_msg.c_str()); discard_changes(); cb->dispatch(success, error_msg); return; } // // Check everything really worked, and finalize the commit when // we cleaned up the file that was temporarily added. // if (check_commit_status(error_msg) == false) { XLOG_TRACE(_verbose, "Check commit status indicates failure: %s", error_msg.c_str()); discard_changes(); cb->dispatch(false, error_msg); return; } XLOG_TRACE(_verbose, "Cleanup completed after saving file %s", filename.c_str()); save_config_done_cb(success, error_msg, cb); UNUSED(user_id); } void MasterConfigTree::save_config_done_cb(bool success, string error_msg, ConfigSaveCallBack cb) { cb->dispatch(success, error_msg); } bool MasterConfigTree::load_config(const string& filename, uid_t user_id, string& error_msg, ConfigLoadCallBack cb) { XLOG_TRACE(_verbose, "Loading file %s", filename.c_str()); // // Create the string that is used to temporary add a load file to the // configuration. // string load_file_config = c_format(RTRMGR_LOAD_FILE_CONFIG.c_str(), filename.c_str()); // // Test whether the file name matches an entry in the template tree. // If yes, then add a load file to the configuration so the appropriate // external program will be invoked. // Otherwise, assume that the user tries to load from a local file. // do { MasterConfigTree new_tree(_template_tree, _verbose); if (new_tree.parse(load_file_config, "", error_msg) == true) { // // The file name is recognizable by the parser, hence // invoke the corresponding external methods for processing. // break; } // // Assume that the user tries to load from a local file // string deltas, deletions; if (load_from_file(filename, user_id, error_msg, deltas, deletions) != true) { XLOG_TRACE(_verbose, "Failed to load file %s: %s", filename.c_str(), error_msg.c_str()); return false; } XLOG_TRACE(_verbose, "Loading file %s", filename.c_str()); // // Commit the changes // CallBack cb2 = callback(this, &MasterConfigTree::load_config_commit_changes_cb, deltas, deletions, cb); if (change_config(user_id, cb2, error_msg) != true) { discard_changes(); return false; } return true; } while (false); // // Create a temporary file that would be used by the external // program to copy the configuration to. // do { FILE* fp; // Create the file fp = xorp_make_temporary_file("", "xorp_rtrmgr_tmp_config_file", _tmp_config_filename, error_msg); if (fp == NULL) { error_msg = c_format("Cannot load the configuration file: " "cannot create a temporary filename: %s", error_msg.c_str()); _tmp_config_filename = ""; return false; } // Set the file permissions if (set_config_file_permissions(fp, user_id, error_msg) != true) { error_msg = c_format("Cannot load the configuration file: %s", error_msg.c_str()); // Close and remove the file fclose(fp); remove_tmp_config_file(); return false; } // // Close the file descriptor, because we don't need it. // Note, that the created file remains on the filesystem, // so it is guaranteed to be unique when we need to use it again. // fclose(fp); break; } while (false); // // Check whether the current configuration already contains // rtrmgr configuration. // list path; path.push_back(RTRMGR_CONFIG_NODE); if (find_node(path) != NULL) { _rtrmgr_config_node_found = true; } else { _rtrmgr_config_node_found = false; } // // Add the temporary load file to the configuration // if (apply_deltas(user_id, load_file_config, true /* provisional_change */, false /* preserve_node_id */, error_msg) != true) { error_msg = c_format("Cannot load the configuration file: %s", error_msg.c_str()); remove_tmp_config_file(); discard_changes(); return false; } // // Go through the config tree, and create nodes for any defaults // specified in the template tree that aren't already configured. // add_default_children(); // Save the name of the temporary file in the local variable if (root_node().set_variable(RTRMGR_CONFIG_FILENAME_VARNAME, _tmp_config_filename.c_str()) != true) { error_msg = c_format("Cannot load the configuration file: " "cannot store temporary filename %s in internal " "variable %s", _tmp_config_filename.c_str(), RTRMGR_CONFIG_FILENAME_VARNAME.c_str()); remove_tmp_config_file(); return false; } // // Specify the callback that will be invoked after we have read the new // configuration file so we can continue from there. // CallBack load_cb; load_cb = callback(this, &MasterConfigTree::load_config_file_received_cb, filename, user_id, cb); if (change_config(user_id, load_cb, error_msg) != true) { remove_tmp_config_file(); discard_changes(); return false; } return true; } void MasterConfigTree::load_config_file_received_cb(bool success, string error_msg, string filename, uid_t user_id, ConfigLoadCallBack cb) { string dummy_error_msg, dummy_deltas, dummy_deletions; if (! success) { XLOG_TRACE(_verbose, "Failed to load file %s: %s", filename.c_str(), error_msg.c_str()); remove_tmp_config_file(); discard_changes(); cb->dispatch(success, error_msg, dummy_deltas, dummy_deletions); return; } // // Check everything really worked, and finalize the commit when // the file was loaded. // if (check_commit_status(error_msg) == false) { XLOG_TRACE(_verbose, "Check commit status indicates failure: %s", error_msg.c_str()); remove_tmp_config_file(); discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } XLOG_TRACE(_verbose, "Received file %s", filename.c_str()); // // Get the string with the name of the temporary file with the // new configuration. // string rtrmgr_config_filename; if (root_node().expand_variable(RTRMGR_CONFIG_FILENAME_VARNAME, rtrmgr_config_filename, false) != true) { success = false; error_msg = c_format("internal variable %s not found", RTRMGR_CONFIG_FILENAME_VARNAME.c_str()); XLOG_TRACE(_verbose, "Failed to load file %s: %s", filename.c_str(), error_msg.c_str()); error_msg = c_format("Cannot load configuration file %s because of " "internal error: %s", filename.c_str(), error_msg.c_str()); remove_tmp_config_file(); discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } // // Read the configuration from the temporary file // string rtrmgr_config_value; if (read_file(rtrmgr_config_value, rtrmgr_config_filename, error_msg) != true) { success = false; XLOG_TRACE(_verbose, "Failed to load file %s: %s", filename.c_str(), error_msg.c_str()); error_msg = c_format("Cannot load configuration file %s because " "cannot read temporary file %s with the " "configuration: %s", filename.c_str(), rtrmgr_config_filename.c_str(), error_msg.c_str()); remove_tmp_config_file(); discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } // // Remove the temporary file with the configuration // remove_tmp_config_file(); // // Create the string that is used to delete the temporary added // load file from the configuration. // string delete_load_file_config; if (_rtrmgr_config_node_found) { delete_load_file_config = c_format(RTRMGR_LOAD_FILE_CONFIG.c_str(), filename.c_str()); } else { delete_load_file_config = RTRMGR_CONFIG; } if (apply_deletions(user_id, delete_load_file_config, /* provisional_change */ true, error_msg) != true) { error_msg = c_format("Cannot load the configuration file because of " "internal error: %s", error_msg.c_str()); discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } // // Go through the config tree, and create nodes for any defaults // specified in the template tree that aren't already configured. // add_default_children(); // // Specify the callback that will be invoked after we have cleaned-up // so we can continue from there. // bool orig_success = success; string orig_error_msg = error_msg; CallBack cleanup_cb; cleanup_cb = callback(this, &MasterConfigTree::load_config_file_cleanup_cb, orig_success, orig_error_msg, rtrmgr_config_value, filename, user_id, cb); if (change_config(user_id, cleanup_cb, error_msg) != true) { discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } } void MasterConfigTree::load_config_file_cleanup_cb(bool success, string error_msg, bool orig_success, string orig_error_msg, string rtrmgr_config_value, string filename, uid_t user_id, ConfigLoadCallBack cb) { string deltas, deletions; string dummy_deltas, dummy_deletions; if (! orig_success) { XLOG_TRACE(_verbose, "Failed to load file %s: %s", filename.c_str(), orig_error_msg.c_str()); discard_changes(); cb->dispatch(orig_success, orig_error_msg, dummy_deltas, dummy_deletions); return; } if (! success) { XLOG_TRACE(_verbose, "Failed to load file %s: %s", filename.c_str(), error_msg.c_str()); discard_changes(); cb->dispatch(success, error_msg, dummy_deltas, dummy_deletions); return; } // // Check everything really worked, and finalize the commit when // we cleaned up the file that was temporarily added. // if (check_commit_status(error_msg) == false) { XLOG_TRACE(_verbose, "Check commit status indicates failure: %s", error_msg.c_str()); discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } XLOG_TRACE(_verbose, "Cleanup completed after receiving file %s", filename.c_str()); // // Test out parsing the config on a new config tree to detect any // parse errors before we reconfigure ourselves with the new config. // MasterConfigTree new_tree(_template_tree, _verbose); if (new_tree.parse(rtrmgr_config_value, filename, error_msg) != true) { cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } // // Go through the config tree, and create nodes for any defaults // specified in the template tree that aren't already configured. // new_tree.add_default_children(); // // Ok, so the new config parses. Now we need to figure out how it // differs from the existing config so we don't need to modify // anything that hasn't changed. // MasterConfigTree delta_tree(_template_tree, _verbose); MasterConfigTree deletion_tree(_template_tree, _verbose); diff_configs(new_tree, delta_tree, deletion_tree); if (! root_node().merge_deltas(user_id, delta_tree.const_root_node(), true /* provisional_change */, false /* preserve_node_id */, error_msg)) { discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } if (! root_node().merge_deletions(user_id, deletion_tree.const_root_node(), true /* provisional_change */, error_msg)) { discard_changes(); cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions); return; } // Pass these back out so we can notify other users of the change deltas = delta_tree.show_unannotated_tree(/*numbered*/ true); deletions = deletion_tree.show_unannotated_tree(/*numbered*/ true); // // Commit the changes // CallBack cb2 = callback(this, &MasterConfigTree::load_config_commit_changes_cb, deltas, deletions, cb); if (change_config(user_id, cb2, error_msg) != true) { discard_changes(); cb->dispatch(false, error_msg, deltas, deletions); return; } } void MasterConfigTree::load_config_commit_changes_cb(bool success, string error_msg, string deltas, string deletions, ConfigLoadCallBack cb) { cb->dispatch(success, error_msg, deltas, deletions); } bool MasterConfigTree::load_from_file(const string& filename, uid_t user_id, string& error_msg, string& deltas, string& deletions) { string dummy_error_msg; string full_filename = config_full_filename(filename); // // We run load_from_file as the UID of the user making the request // and as group xorp. This prevents users using the rtrmgr to // attempt to load files they wouldn't normally have had the // permission to read. Otherwise it's possible that they could // load configs they shouldn't have access to, or get parts of // protected files reported to them in error messages. // // // Set the effective group to "xorp" and the effective user ID to the // uid of the user that sent the request. // if (! _is_xorp_gid_set) { error_msg = "Group \"xorp\" does not exist on this system"; return false; } _exec_id.set_uid(user_id); _exec_id.set_gid(_xorp_gid); _exec_id.save_current_exec_id(); if (_exec_id.set_effective_exec_id(error_msg) != XORP_OK) { _exec_id.restore_saved_exec_id(dummy_error_msg); return false; } string configuration; if (! read_file(configuration, full_filename, error_msg)) { _exec_id.restore_saved_exec_id(dummy_error_msg); return false; } // Revert UID and GID now we've done reading the file _exec_id.restore_saved_exec_id(dummy_error_msg); // // Test out parsing the config on a new config tree to detect any // parse errors before we reconfigure ourselves with the new config. // MasterConfigTree new_tree(_template_tree, _verbose); if (new_tree.parse(configuration, full_filename, error_msg) != true) { return false; } // // Go through the config tree, and create nodes for any defaults // specified in the template tree that aren't already configured. // new_tree.add_default_children(); // // Ok, so the new config parses. Now we need to figure out how it // differs from the existing config so we don't need to modify // anything that hasn't changed. // MasterConfigTree delta_tree(_template_tree, _verbose); MasterConfigTree deletion_tree(_template_tree, _verbose); diff_configs(new_tree, delta_tree, deletion_tree); if (! root_node().merge_deltas(user_id, delta_tree.const_root_node(), true /* provisional_change */, false /* preserve_node_id */, error_msg)) { discard_changes(); return false; } if (! root_node().merge_deletions(user_id, deletion_tree.const_root_node(), true /* provisional_change */, error_msg)) { discard_changes(); return false; } // Pass these back out so we can notify other users of the change deltas = delta_tree.show_unannotated_tree(/*numbered*/ true); deletions = deletion_tree.show_unannotated_tree(/*numbered*/ true); // // The config is loaded. We haven't yet committed it, but that // happens elsewhere so we can handle the callbacks correctly. // return true; } void MasterConfigTree::set_config_directory(const string& config_directory) { _config_directory = config_directory; } void MasterConfigTree::diff_configs(const MasterConfigTree& new_tree, MasterConfigTree& delta_tree, MasterConfigTree& deletion_tree) { // // Clone the existing config tree into the deletion tree. // Clone the new config tree into the delta tree. // deletion_tree = *((MasterConfigTree*)(this)); delta_tree = new_tree; deletion_tree.retain_deletion_nodes(new_tree, false); delta_tree.retain_different_nodes(*((ConfigTree*)(this)), true); debug_msg("=========================================================\n"); debug_msg("ORIG:\n"); debug_msg("%s", tree_str().c_str()); debug_msg("=========================================================\n"); debug_msg("NEW:\n"); debug_msg("%s", new_tree.tree_str().c_str()); debug_msg("=========================================================\n"); debug_msg("=========================================================\n"); debug_msg("DELTAS:\n"); debug_msg("%s", delta_tree.tree_str().c_str()); debug_msg("=========================================================\n"); debug_msg("DELETIONS:\n"); debug_msg("%s", deletion_tree.tree_str().c_str()); debug_msg("=========================================================\n"); } bool MasterConfigTree::module_config_start(const string& module_name, string& error_msg) { ModuleCommand *cmd = _template_tree->find_module(module_name); if (cmd == NULL) { error_msg = c_format("Module %s is not registered with the " "TemplateTree, but is needed to satisfy a " "dependency\n", module_name.c_str()); return false; } if (_task_manager->add_module(*cmd, error_msg) != XORP_OK) { return false; } return true; } #if 0 // TODO bool MasterConfigTree::module_shutdown(const string& module_name, string& error_msg) { ModuleCommand *cmd = _template_tree->find_module(module_name); if (cmd == NULL) { error_msg = c_format("Module %s is not registered with the " "TemplateTree, but is needed to satisfy a " "dependency\n", module_name.c_str()); return false; } _task_manager->shutdown_module(*cmd); return true; } #endif // 0