1 /* OperServ core functions 2 * 3 * (C) 2003-2020 Anope Team 4 * Contact us at team@anope.org 5 * 6 * Please read COPYING and README for further details. 7 * 8 * Based on the original code of Epona by Lara. 9 * Based on the original code of Services by Andy Church. 10 */ 11 12 #include "module.h" 13 14 static unsigned int HARDMAX = 65536; 15 16 class CommandOSLogSearch : public Command 17 { CreateLogName(const Anope::string & file,time_t t=Anope::CurTime)18 static inline Anope::string CreateLogName(const Anope::string &file, time_t t = Anope::CurTime) 19 { 20 char timestamp[32]; 21 22 tm *tm = localtime(&t); 23 24 strftime(timestamp, sizeof(timestamp), "%Y%m%d", tm); 25 26 return Anope::LogDir + "/" + file + "." + timestamp; 27 } 28 29 public: CommandOSLogSearch(Module * creator)30 CommandOSLogSearch(Module *creator) : Command(creator, "operserv/logsearch", 1, 3) 31 { 32 this->SetDesc(_("Searches logs for a matching pattern")); 33 this->SetSyntax(_("[+\037days\037d] [+\037limit\037l] \037pattern\037")); 34 } 35 Execute(CommandSource & source,const std::vector<Anope::string> & params)36 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 37 { 38 int days = 7, replies = 50; 39 40 unsigned i; 41 for (i = 0; i < params.size() && params[i][0] == '+'; ++i) 42 { 43 switch (params[i][params[i].length() - 1]) 44 { 45 case 'd': 46 if (params[i].length() > 2) 47 { 48 Anope::string dur = params[i].substr(1, params[i].length() - 2); 49 try 50 { 51 days = convertTo<int>(dur); 52 if (days <= 0) 53 throw ConvertException(); 54 } 55 catch (const ConvertException &) 56 { 57 source.Reply(_("Invalid duration %s, using %d days."), dur.c_str(), days); 58 } 59 } 60 break; 61 case 'l': 62 if (params[i].length() > 2) 63 { 64 Anope::string dur = params[i].substr(1, params[i].length() - 2); 65 try 66 { 67 replies = convertTo<int>(dur); 68 if (replies <= 0) 69 throw ConvertException(); 70 } 71 catch (const ConvertException &) 72 { 73 source.Reply(_("Invalid limit %s, using %d."), dur.c_str(), replies); 74 } 75 } 76 break; 77 default: 78 source.Reply(_("Unknown parameter: %s"), params[i].c_str()); 79 } 80 } 81 82 if (i >= params.size()) 83 { 84 this->OnSyntaxError(source, ""); 85 return; 86 } 87 88 Anope::string search_string = params[i++]; 89 for (; i < params.size(); ++i) 90 search_string += " " + params[i]; 91 92 Log(LOG_ADMIN, source, this) << "for " << search_string; 93 94 bool wildcard = search_string.find_first_of("?*") != Anope::string::npos; 95 bool regex = search_string.empty() == false && search_string[0] == '/' && search_string[search_string.length() - 1] == '/'; 96 97 const Anope::string &logfile_name = Config->GetModule(this->owner)->Get<const Anope::string>("logname"); 98 std::vector<Anope::string> matches; 99 for (int d = days - 1; d >= 0; --d) 100 { 101 Anope::string lf_name = CreateLogName(logfile_name, Anope::CurTime - (d * 86400)); 102 Log(LOG_DEBUG) << "Searching " << lf_name; 103 std::fstream fd(lf_name.c_str(), std::ios_base::in); 104 if (!fd.is_open()) 105 continue; 106 107 for (Anope::string buf, token; std::getline(fd, buf.str());) 108 { 109 bool match = false; 110 111 if (regex) 112 match = Anope::Match(buf, search_string, false, true); 113 else if (wildcard) 114 match = Anope::Match(buf, "*" + search_string + "*"); 115 else 116 match = buf.find_ci(search_string) != Anope::string::npos; 117 118 if (match) 119 { 120 matches.push_back(buf); 121 122 if (matches.size() >= HARDMAX) 123 break; 124 } 125 } 126 127 fd.close(); 128 } 129 130 unsigned int found = matches.size(); 131 if (!found) 132 { 133 source.Reply(_("No matches for \002%s\002 found."), search_string.c_str()); 134 return; 135 } 136 137 if (matches.size() >= HARDMAX) 138 { 139 source.Reply(_("Too many results for \002%s\002."), search_string.c_str()); 140 return; 141 } 142 143 if (matches.size() > static_cast<unsigned int>(replies)) 144 { 145 matches.erase(matches.begin(), matches.begin() + (matches.size() - static_cast<unsigned int>(replies))); 146 } 147 148 source.Reply(_("Matches for \002%s\002:"), search_string.c_str()); 149 unsigned int count = 0; 150 for (std::vector<Anope::string>::iterator it = matches.begin(), it_end = matches.end(); it != it_end; ++it) 151 source.Reply("#%d: %s", ++count, it->c_str()); 152 source.Reply(_("Showed %d/%d matches for \002%s\002."), matches.size(), found, search_string.c_str()); 153 } 154 OnHelp(CommandSource & source,const Anope::string & subcommand)155 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 156 { 157 this->SendSyntax(source); 158 source.Reply(" "); 159 source.Reply(_("This command searches the Services logfiles for messages\n" 160 "that match the given pattern. The day and limit argument\n" 161 "may be used to specify how many days of logs to search\n" 162 "and the number of replies to limit to. By default this\n" 163 "command searches one week of logs, and limits replies\n" 164 "to 50.\n" 165 " \n" 166 "For example:\n" 167 " \002LOGSEARCH +21d +500l Anope\002\n" 168 " Searches the last 21 days worth of logs for messages\n" 169 " containing Anope and lists the most recent 500 of them.")); 170 return true; 171 } 172 }; 173 174 class OSLogSearch : public Module 175 { 176 CommandOSLogSearch commandoslogsearch; 177 178 public: OSLogSearch(const Anope::string & modname,const Anope::string & creator)179 OSLogSearch(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), 180 commandoslogsearch(this) 181 { 182 } 183 }; 184 185 MODULE_INIT(OSLogSearch) 186