summaryrefslogtreecommitdiff
path: root/modules/operserv/logsearch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/operserv/logsearch.cpp')
-rw-r--r--modules/operserv/logsearch.cpp191
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..60bde65c9
--- /dev/null
+++ b/modules/operserv/logsearch.cpp
@@ -0,0 +1,191 @@
+/*
+ * Anope IRC Services
+ *
+ * Copyright (C) 2012-2016 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> &params) 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)