Mercurial > irccd
view irccdctl/irccdctl.cpp @ 62:424cd7780372 stable-2
Merge from release-2.0
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 01 Mar 2016 08:53:30 +0100 |
parents | b3298e9d02c2 |
children |
line wrap: on
line source
/* * irccdctl.cpp -- main irccdctl class * * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <algorithm> #include <cassert> #include <iterator> #include <memory> #include <string> #include <unordered_map> #include <irccd-config.h> #include <elapsed-timer.h> #include <filesystem.h> #include <ini.h> #include <json.h> #include <logger.h> #include <options.h> #include <path.h> #include <sockets.h> #include <system.h> #include <util.h> #include "irccdctl.h" namespace irccd { using namespace net; using namespace net::address; using namespace net::option; using namespace net::protocol; using namespace std::placeholders; using namespace std::chrono_literals; /* * Config file format * ------------------------------------------------------------------ * * [connect] * type = "ip | unix" * * # if ip * host = "" * port = number * domain = "ipv4 | ipv6", default: ipv4 * * # if unix * path = "" * * [alias] * name = replacement */ /* * Initialize a connection from the configuration file * ------------------------------------------------------------------ */ void Irccdctl::usage() const { log::warning() << "usage: " << sys::programName() << " [options...] <command> [command-options...] [command-args...]\n\n"; log::warning() << "General options:\n"; log::warning() << "\tc, --config file\tspecify the configuration file\n"; log::warning() << "\t--help\t\t\tshow this help\n"; log::warning() << "\t-t, --type type\t\tspecify connection type\n"; log::warning() << "\t-v, --verbose\t\tbe verbose\n\n"; log::warning() << "Available options for type ip and ipv6 (-t, --type):\n"; log::warning() << "\t-h, --host address\tconnect to the specified address\n"; log::warning() << "\t-p, --port port\t\tuse the specified port number\n\n"; log::warning() << "Available options for type unix (-t, --type):\n"; log::warning() << "\t-P, --path file\t\tconnect to the specified socket file\n\n"; log::warning() << "General commands:\n"; log::warning() << "\thelp\t\t\tShow an help topic\n"; log::warning() << "\twatch\t\t\tStart listening to irccd\n\n"; log::warning() << "Plugin management:\n"; log::warning() << "\tplugin-info\t\tGet plugin information\n"; log::warning() << "\tplugin-list\t\tList all loaded plugins\n"; log::warning() << "\tplugin-load\t\tLoad a plugin\n"; log::warning() << "\tplugin-reload\t\tReload a plugin\n"; log::warning() << "\tplugin-unload\t\tUnload a plugin\n\n"; log::warning() << "Server management:\n"; log::warning() << "\tserver-cmode\t\tChange a channel mode\n"; log::warning() << "\tserver-cnotice\t\tSend a channel notice\n"; log::warning() << "\tserver-connect\t\tConnect to a server\n"; log::warning() << "\tserver-disconnect\tDisconnect from a server\n"; log::warning() << "\tserver-info\t\tGet server information\n"; log::warning() << "\tserver-invite\t\tInvite someone to a channel\n"; log::warning() << "\tserver-join\t\tJoin a channel\n"; log::warning() << "\tserver-kick\t\tKick someone from a channel\n"; log::warning() << "\tserver-list\t\tList all servers\n"; log::warning() << "\tserver-me\t\tSend a CTCP Action (same as /me)\n"; log::warning() << "\tserver-message\t\tSend a message to someone or a channel\n"; log::warning() << "\tserver-mode\t\tChange a user mode\n"; log::warning() << "\tserver-notice\t\tSend a private notice\n"; log::warning() << "\tserver-nick\t\tChange your nickname\n"; log::warning() << "\tserver-part\t\tLeave a channel\n"; log::warning() << "\tserver-reconnect\tReconnect one or all servers\n"; log::warning() << "\tserver-topic\t\tChange a channel topic\n"; log::warning() << "\nFor more information on a command, type " << sys::programName() << " help <command>" << std::endl; std::exit(1); } void Irccdctl::readConnectIp(const ini::Section &sc) { ini::Section::const_iterator it; /* host */ std::string host; if ((it = sc.find("host")) == sc.end()) throw std::invalid_argument("missing host parameter"); host = it->value(); /* port */ int port; if ((it = sc.find("port")) == sc.end()) throw std::invalid_argument("missing port parameter"); try { port = std::stoi(it->value()); } catch (...) { throw std::invalid_argument("invalid port number: " + it->value()); } /* domain */ Ip::Type domain{Ip::v4}; if ((it = sc.find("domain")) != sc.end()) { if (it->value() == "ipv6") domain = Ip::v6; else if (it->value() == "ipv4") domain = Ip::v4; else throw std::invalid_argument("invalid domain: " + it->value()); } m_connection = std::make_unique<ConnectionBase<Ip>>(Ip{host, port, domain}); } void Irccdctl::readConnectUnix(const ini::Section &sc) { #if !defined(IRCCD_SYSTEM_WINDOWS) auto it = sc.find("path"); if (it == sc.end()) throw std::invalid_argument("missing path parameter"); m_connection = std::make_unique<ConnectionBase<Local>>(Local{it->value(), false}); #else (void)sc; throw std::invalid_argument("unix connection not supported on Windows"); #endif } void Irccdctl::readConnect(const ini::Section &sc) { auto it = sc.find("type"); if (it == sc.end()) throw std::invalid_argument("missing type parameter"); if (it->value() == "ip") readConnectIp(sc); else if (it->value() == "unix") readConnectUnix(sc); else throw std::invalid_argument("invalid type given: " + it->value()); } void Irccdctl::readGeneral(const ini::Section &sc) { auto verbose = sc.find("verbose"); if (verbose != sc.end()) log::setVerbose(util::isBoolean(verbose->value())); } void Irccdctl::readAliases(const ini::Section &sc) { for (const ini::Option &option : sc) { /* This is the alias name */ Alias alias(option.key()); if (m_commands.count(option.key()) > 0) throw std::invalid_argument("there is already a command named " + option.key()); /* Iterate over the list of commands to execute for this alias */ for (const std::string &repl : option) { /* This is the alias split string */ std::vector<std::string> list = util::split(repl, " \t"); if (list.size() < 1) throw std::invalid_argument("alias require at least one argument"); /* First argument is the command/alias to execute */ std::string command = list[0]; /* This is the alias arguments */ std::vector<AliasArg> args; for (auto it = list.begin() + 1; it != list.end(); ++it) args.push_back(std::move(*it)); alias.push_back({std::move(command), std::move(args)}); } /* Show for debugging purpose */ log::debug() << "alias " << option.key() << ":" << std::endl; for (const auto &cmd : alias) { log::debug() << " " << cmd.command() << " "; log::debug() << util::join(cmd.args().begin(), cmd.args().end(), ' ') << std::endl; } m_aliases.emplace(option.key(), std::move(alias)); } } void Irccdctl::read(const std::string &path, const parser::Result &options) { ini::Document doc(ini::File{path}); ini::Document::const_iterator it = doc.find("connect"); /* Do not try to read [connect] if specified at command line */ if (it != doc.end() && options.count("-t") == 0 && options.count("--type") == 0) readConnect(*it); if ((it = doc.find("general")) != doc.end()) readGeneral(*it); if ((it = doc.find("alias")) != doc.end()) readAliases(*it); } /* * Initialize a connection from the command line * ------------------------------------------------------------------ */ void Irccdctl::parseConnectIp(const parser::Result &options, bool ipv6) { parser::Result::const_iterator it; /* host (-h or --host) */ std::string host; if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end()) throw std::invalid_argument("missing host argument (-h or --host)"); host = it->second; /* port (-p or --port) */ int port; if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) throw std::invalid_argument("missing port argument (-p or --port)"); try { port = std::stoi(it->second); } catch (...) { throw std::invalid_argument("invalid port number: " + it->second); } m_connection = std::make_unique<ConnectionBase<Ip>>(Ip{host, port, (ipv6) ? Ip::v6 : Ip::v4}); } void Irccdctl::parseConnectUnix(const parser::Result &options) { #if !defined(IRCCD_SYSTEM_WINDOWS) parser::Result::const_iterator it; if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end()) throw std::invalid_argument("missing path parameter (-P or --path)"); m_connection = std::make_unique<ConnectionBase<Local>>(Local{it->second, false}); #else (void)options; throw std::invalid_argument("unix connection not supported on Windows"); #endif } void Irccdctl::parseConnect(const parser::Result &options) { assert(options.count("-t") > 0 || options.count("--type") > 0); auto it = options.find("-t"); if (it == options.end()) it = options.find("--type"); if (it->second == "ip" || it->second == "ipv6") return parseConnectIp(options, it->second == "ipv6"); if (it->second == "unix") return parseConnectUnix(options); throw std::invalid_argument("invalid type given: " + it->second); } parser::Result Irccdctl::parse(int &argc, char **&argv) const { /* 1. Parse command line options */ parser::Options def{ { "-c", true }, { "--config", true }, { "-h", true }, { "--help", false }, { "--host", true }, { "-p", true }, { "--port", true }, { "-P", true }, { "--path", true }, { "-t", true }, { "--type", true }, { "-v", false }, { "--verbose", false } }; parser::Result result; try { result = parser::read(argc, argv, def); if (result.count("--help") != 0) usage(); // NOTREACHED if (result.count("-v") != 0 || result.count("--verbose") != 0) log::setVerbose(true); } catch (const std::exception &ex) { log::warning() << sys::programName() << ": " << ex.what() << std::endl; usage(); } return result; } void Irccdctl::exec(const Command &cmd, std::vector<std::string> args) { cmd.exec(*this, args); } void Irccdctl::exec(const Alias &alias, std::vector<std::string> argsCopy) { for (const AliasCommand &cmd : alias) { std::vector<std::string> args(argsCopy); std::vector<std::string> cmdArgs; std::vector<std::string>::size_type toremove = 0; /* 1. Append command name before */ cmdArgs.push_back(cmd.command()); for (const AliasArg &arg : cmd.args()) { if (arg.isPlaceholder()) { if (args.size() < arg.index() + 1) throw std::invalid_argument("missing argument for placeholder %" + std::to_string(arg.index())); cmdArgs.push_back(args[arg.index()]); if (arg.index() + 1 > toremove) toremove = arg.index() + 1; } else { cmdArgs.push_back(arg.value()); } } assert(toremove <= args.size()); /* 2. Remove the arguments that been placed in placeholders */ args.erase(args.begin(), args.begin() + toremove); /* 3. Now append the rest of arguments */ std::copy(args.begin(), args.end(), std::back_inserter(cmdArgs)); /* 4. Finally try to execute */ exec(cmdArgs); } } void Irccdctl::exec(std::vector<std::string> args) { assert(args.size() > 0); auto name = args[0]; auto alias = m_aliases.find(name); /* Remove name */ args.erase(args.begin()); if (alias != m_aliases.end()) { exec(alias->second, args); } else { auto cmd = m_commands.find(name); if (cmd != m_commands.end()) exec(*cmd->second, args); else throw std::invalid_argument("no alias or command named " + name); } } void Irccdctl::connect() { log::info() << sys::programName() << ": connecting to irccd..." << std::endl; /* Try to connect */ m_connection->connect(30000); /* Get irccd information */ json::Value object = m_connection->next(30000); if (!object.contains("program") || object.at("program").toString() != "irccd") throw std::runtime_error("not an irccd server"); /* Get values */ m_major = object.at("major").toInt(); m_minor = object.at("minor").toInt(); m_patch = object.at("patch").toInt(); m_javascript = object.valueOr("javascript", json::Type::Boolean, false).toBool(); m_ssl = object.valueOr("ssl", json::Type::Boolean, false).toBool(); log::info() << std::boolalpha; log::info() << sys::programName() << ": connected to irccd " << m_major << "." << m_minor << "." << m_patch << std::endl; log::info() << sys::programName() << ": javascript: " << m_javascript << ", ssl supported: " << m_ssl << std::endl; } void Irccdctl::run(int argc, char **argv) { /* 1. Read command line arguments */ parser::Result result = parse(argc, argv); /* * 2. Open optional config by command line or by searching it * * The connection to irccd is searched in the following order : * * 1. From the command line if specified * 2. From the configuration file specified by -c * 3. From the configuration file searched through directories */ try { if (result.count("-t") > 0 || result.count("--type") > 0) parseConnect(result); auto it = result.find("-c"); if (it != result.end() || (it = result.find("--config")) != result.end()) { read(it->second, result); } else { for (const std::string &dir : path::list(path::PathConfig)) { std::string path = dir + "irccdctl.conf"; if (fs::exists(path)) read(path, result); } } } catch (const std::exception &ex) { log::warning() << sys::programName() << ": " << ex.what() << std::endl; std::exit(1); } if (argc <= 0) { usage(); // NOTREACHED } /* help does not require connection */ if (std::strcmp(argv[0], "help") != 0) { if (!m_connection) { log::warning() << sys::programName() << ": no connection specified" << std::endl; std::exit(1); } connect(); } /* Build a vector of arguments */ std::vector<std::string> args; for (int i = 0; i < argc; ++i) args.push_back(argv[i]); exec(args); } } // !irccd