diff options
Diffstat (limited to 'modules/operserv/logsearch.cpp')
-rw-r--r-- | modules/operserv/logsearch.cpp | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/modules/operserv/logsearch.cpp b/modules/operserv/logsearch.cpp new file mode 100644 index 000000000..608e6da59 --- /dev/null +++ b/modules/operserv/logsearch.cpp @@ -0,0 +1,191 @@ +/* + * Anope IRC Services + * + * Copyright (C) 2012-2017 Anope Team <team@anope.org> + * + * This file is part of Anope. Anope is free software; you can + * redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software + * Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see see <http://www.gnu.org/licenses/>. + */ + +#include "module.h" + +static const unsigned int HARDMAX = 65536; + +class CommandOSLogSearch : public Command +{ + static inline Anope::string CreateLogName(const Anope::string &file, time_t t = Anope::CurTime) + { + char timestamp[32]; + + tm *tm = localtime(&t); + + strftime(timestamp, sizeof(timestamp), "%Y%m%d", tm); + + return Anope::LogDir + "/" + file + "." + timestamp; + } + + public: + CommandOSLogSearch(Module *creator) : Command(creator, "operserv/logsearch", 1, 3) + { + this->SetDesc(_("Searches logs for a matching pattern")); + this->SetSyntax(_("[+\037days\037d] [+\037limit\037l] \037pattern\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + int days = 7, replies = 50; + + unsigned i; + for (i = 0; i < params.size() && params[i][0] == '+'; ++i) + { + switch (params[i][params[i].length() - 1]) + { + case 'd': + if (params[i].length() > 2) + { + Anope::string dur = params[i].substr(1, params[i].length() - 2); + try + { + days = convertTo<int>(dur); + if (days <= 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + source.Reply(_("Invalid duration \002{0}\002, using \002{1}\002 days."), dur, days); + } + } + break; + case 'l': + if (params[i].length() > 2) + { + Anope::string dur = params[i].substr(1, params[i].length() - 2); + try + { + replies = convertTo<int>(dur); + if (replies <= 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + source.Reply(_("Invalid limit \002{0}\002, using \002{1}\002."), dur, replies); + } + } + break; + default: + source.Reply(_("Unknown parameter: {0}"), params[i]); + } + } + + if (i >= params.size()) + { + this->OnSyntaxError(source, ""); + return; + } + + Anope::string search_string = params[i++]; + for (; i < params.size(); ++i) + search_string += " " + params[i]; + + logger.Admin(source, _("{source} used {command} for {2}"), search_string); + + bool wildcard = search_string.find_first_of("?*") != Anope::string::npos; + bool regex = search_string.empty() == false && search_string[0] == '/' && search_string[search_string.length() - 1] == '/'; + + const Anope::string &logfile_name = Config->GetModule(this->GetOwner())->Get<Anope::string>("logname"); + std::vector<Anope::string> matches; + for (int d = days - 1; d >= 0; --d) + { + Anope::string lf_name = CreateLogName(logfile_name, Anope::CurTime - (d * 86400)); + + this->logger.Debug("Searching {0}", lf_name); + + std::fstream fd(lf_name.c_str(), std::ios_base::in); + if (!fd.is_open()) + continue; + + for (Anope::string buf; std::getline(fd, buf.str());) + if (Anope::Match(buf, "*" + search_string + "*")) + { + bool match = false; + + if (regex) + match = Anope::Match(buf, search_string, false, true); + else if (wildcard) + match = Anope::Match(buf, "*" + search_string + "*"); + else + match = buf.find_ci(search_string) != Anope::string::npos; + + if (!match) + continue; + + matches.push_back(buf); + + if (matches.size() >= HARDMAX) + break; + } + + fd.close(); + } + + unsigned int found = matches.size(); + if (!found) + { + source.Reply(_("No matches for \002{0}\002 found."), search_string); + return; + } + + if (found >= HARDMAX) + { + source.Reply(_("Too many results for \002{0}\002."), search_string); + return; + } + + if (matches.size() > static_cast<unsigned int>(replies)) + { + matches.erase(matches.begin(), matches.begin() + (matches.size() - static_cast<unsigned int>(replies))); + } + + source.Reply(_("Matches for \002{0}\002:"), search_string); + unsigned int count = 0; + for (const Anope::string &str : matches) + source.Reply("#{0}: {1}", ++count, str); + source.Reply(_("Showed \002{0}/{1}\002 matches for \002{2}\002."), matches.size(), found, search_string); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command searches the Services logfiles for messages that match the given pattern." + " The day and limit argument may be used to specify how many days of logs to search and the number of replies to limit to." + " By default this command searches one week of logs, and limits replies to 50.\n" + "\n" + "Example:\n" + " {0} +21d +500l Anope\n" + " Searches the last 21 days worth of logs for messages containing \"Anope\" and lists the most recent 500 of them."), + source.GetCommand()); + return true; + } +}; + +class OSLogSearch : public Module +{ + CommandOSLogSearch commandoslogsearch; + + public: + OSLogSearch(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoslogsearch(this) + { + } +}; + +MODULE_INIT(OSLogSearch) |