/* * Anope IRC Services * * Copyright (C) 2003-2017 Anope Team * * 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 . */ #include "services.h" #include "build.h" #include "modules.h" #include "lists.h" #include "config.h" #include "bots.h" #include "language.h" #include "sockets.h" #include "event.h" #include #include #include #ifndef _WIN32 #include #include #endif NumberList::NumberList(const Anope::string &list, bool descending, std::function nf, std::function ef) : endf(ef) { Anope::string error; commasepstream sep(list); Anope::string token; bool is_valid = true; std::set numbers; sep.GetToken(token); if (token.empty()) token = list; do { size_t t = token.find('-'); if (t == Anope::string::npos) { try { unsigned num = convertTo(token, error, false); if (error.empty()) numbers.insert(num); } catch (const ConvertException &) { error = "1"; } if (!error.empty()) { is_valid = false; return; } } else { Anope::string error2; try { unsigned num1 = convertTo(token.substr(0, t), error, false); unsigned num2 = convertTo(token.substr(t + 1), error2, false); if (error.empty() && error2.empty()) for (unsigned i = num1; i <= num2; ++i) numbers.insert(i); } catch (const ConvertException &) { error = "1"; } if (!error.empty() || !error2.empty()) { is_valid = false; return; } } } while (sep.GetToken(token)); if (!is_valid) return; if (descending) std::for_each(numbers.rbegin(), numbers.rend(), nf); else std::for_each(numbers.begin(), numbers.end(), nf); } NumberList::~NumberList() { endf(); } ListFormatter::ListFormatter(NickServ::Account *acc) : nc(acc) { } ListFormatter &ListFormatter::AddColumn(const Anope::string &name) { this->columns.push_back(name); return *this; } void ListFormatter::AddEntry(const ListEntry &entry) { this->entries.push_back(entry); } bool ListFormatter::IsEmpty() const { return this->entries.empty(); } void ListFormatter::Process(std::vector &buffer) { std::vector tcolumns; std::map lenghts; std::set breaks; for (unsigned i = 0; i < this->columns.size(); ++i) { tcolumns.push_back(Language::Translate(this->nc, this->columns[i].c_str())); lenghts[this->columns[i]] = tcolumns[i].length(); } for (unsigned i = 0; i < this->entries.size(); ++i) { ListEntry &e = this->entries[i]; for (unsigned j = 0; j < this->columns.size(); ++j) if (e[this->columns[j]].length() > lenghts[this->columns[j]]) lenghts[this->columns[j]] = e[this->columns[j]].length(); } unsigned length = 0; for (std::map::iterator it = lenghts.begin(), it_end = lenghts.end(); it != it_end; ++it) { if (length > Config->LineWrap) { breaks.insert(it->first); length = 0; } else { length += it->second; } } /* Only put a list header if more than 1 column */ if (this->columns.size() > 1) { Anope::string s; for (unsigned i = 0; i < this->columns.size(); ++i) { if (breaks.count(this->columns[i])) { buffer.push_back(s); s = " "; } else if (!s.empty()) s += " "; s += tcolumns[i]; if (i + 1 != this->columns.size()) for (unsigned j = tcolumns[i].length(); j < lenghts[this->columns[i]]; ++j) s += " "; } buffer.push_back(s); } for (unsigned i = 0; i < this->entries.size(); ++i) { ListEntry &e = this->entries[i]; Anope::string s; for (unsigned j = 0; j < this->columns.size(); ++j) { if (breaks.count(this->columns[j])) { buffer.push_back(s); s = " "; } else if (!s.empty()) s += " "; s += e[this->columns[j]]; if (j + 1 != this->columns.size()) for (unsigned k = e[this->columns[j]].length(); k < lenghts[this->columns[j]]; ++k) s += " "; } buffer.push_back(s); } } InfoFormatter::InfoFormatter(NickServ::Account *acc) : nc(acc), longest(0) { } void InfoFormatter::Process(std::vector &buffer) { buffer.clear(); for (std::vector >::iterator it = this->replies.begin(), it_end = this->replies.end(); it != it_end; ++it) { Anope::string s; for (unsigned i = it->first.length(); i < this->longest; ++i) s += " "; s += it->first + ": " + Language::Translate(this->nc, it->second.c_str()); buffer.push_back(s); } } Anope::string& InfoFormatter::operator[](const Anope::string &key) { Anope::string tkey = Language::Translate(this->nc, key.c_str()); if (tkey.length() > this->longest) this->longest = tkey.length(); this->replies.push_back(std::make_pair(tkey, "")); return this->replies.back().second; } void InfoFormatter::AddOption(const Anope::string &opt) { Anope::string options = Language::Translate(this->nc, "Options"); Anope::string *optstr = NULL; for (std::vector >::iterator it = this->replies.begin(), it_end = this->replies.end(); it != it_end; ++it) { if (it->first == options) { optstr = &it->second; break; } } if (!optstr) optstr = &(*this)[_("Options")]; if (!optstr->empty()) *optstr += ", "; *optstr += Language::Translate(nc, opt.c_str()); } bool Anope::IsFile(const Anope::string &filename) { struct stat fileinfo; if (!stat(filename.c_str(), &fileinfo)) return true; return false; } time_t Anope::DoTime(const Anope::string &s) { if (s.empty()) return 0; int amount = 0; Anope::string end; try { amount = convertTo(s, end, false); if (!end.empty()) { switch (end[0]) { case 's': return amount; case 'm': return amount * 60; case 'h': return amount * 3600; case 'd': return amount * 86400; case 'w': return amount * 86400 * 7; case 'y': return amount * 86400 * 365; default: break; } } } catch (const ConvertException &) { amount = -1; } return amount; } Anope::string Anope::Duration(time_t t, NickServ::Account *nc) { /* We first calculate everything */ time_t years = t / 31536000; time_t days = (t / 86400) % 365; time_t hours = (t / 3600) % 24; time_t minutes = (t / 60) % 60; time_t seconds = (t) % 60; if (!years && !days && !hours && !minutes) return stringify(seconds) + " " + (seconds != 1 ? Language::Translate(nc, _("seconds")) : Language::Translate(nc, _("second"))); else { bool need_comma = false; Anope::string buffer; if (years) { buffer = stringify(years) + " " + (years != 1 ? Language::Translate(nc, _("years")) : Language::Translate(nc, _("year"))); need_comma = true; } if (days) { buffer += need_comma ? ", " : ""; buffer += stringify(days) + " " + (days != 1 ? Language::Translate(nc, _("days")) : Language::Translate(nc, _("day"))); need_comma = true; } if (hours) { buffer += need_comma ? ", " : ""; buffer += stringify(hours) + " " + (hours != 1 ? Language::Translate(nc, _("hours")) : Language::Translate(nc, _("hour"))); need_comma = true; } if (minutes) { buffer += need_comma ? ", " : ""; buffer += stringify(minutes) + " " + (minutes != 1 ? Language::Translate(nc, _("minutes")) : Language::Translate(nc, _("minute"))); } return buffer; } } Anope::string Anope::strftime(time_t t, NickServ::Account *nc, bool short_output) { tm tm = *localtime(&t); char buf[BUFSIZE]; strftime(buf, sizeof(buf), Language::Translate(nc, _("%b %d %H:%M:%S %Y %Z")), &tm); if (short_output) return buf; if (t < Anope::CurTime) return Anope::string(buf) + " " + Anope::printf(Language::Translate(nc, _("(%s ago)")), Duration(Anope::CurTime - t, nc).c_str(), nc); else if (t > Anope::CurTime) return Anope::string(buf) + " " + Anope::printf(Language::Translate(nc, _("(%s from now)")), Duration(t - Anope::CurTime, nc).c_str(), nc); else return Anope::string(buf) + " " + Language::Translate(nc, _("(now)")); } Anope::string Anope::Expires(time_t expires, NickServ::Account *nc) { if (!expires) return Language::Translate(nc, _("does not expire")); else if (expires <= Anope::CurTime) return Language::Translate(nc, _("expires momentarily")); else { char buf[256]; time_t diff = expires - Anope::CurTime + 59; if (diff >= 86400) { int days = diff / 86400; snprintf(buf, sizeof(buf), Language::Translate(nc, days == 1 ? _("expires in %d day") : _("expires in %d days")), days); } else { if (diff <= 3600) { int minutes = diff / 60; snprintf(buf, sizeof(buf), Language::Translate(nc, minutes == 1 ? _("expires in %d minute") : _("expires in %d minutes")), minutes); } else { int hours = diff / 3600, minutes; diff -= hours * 3600; minutes = diff / 60; snprintf(buf, sizeof(buf), Language::Translate(nc, hours == 1 && minutes == 1 ? _("expires in %d hour, %d minute") : (hours == 1 && minutes != 1 ? _("expires in %d hour, %d minutes") : (hours != 1 && minutes == 1 ? _("expires in %d hours, %d minute") : _("expires in %d hours, %d minutes")))), hours, minutes); } } return buf; } } bool Anope::Match(const Anope::string &str, const Anope::string &mask, bool case_sensitive, bool use_regex) { size_t s = 0, m = 0, str_len = str.length(), mask_len = mask.length(); if (use_regex && Config->regex_flags && mask_len >= 2 && mask[0] == '/' && mask[mask.length() - 1] == '/') { Anope::string stripped_mask = mask.substr(1, mask_len - 2); // This is often called with the same mask multiple times in a row, so cache it static Anope::string pattern; static std::regex r; if (pattern != stripped_mask) { try { r.assign(stripped_mask.str(), Config->regex_flags); pattern = stripped_mask; } catch (const std::regex_error &error) { Anope::Logger.Debug(error.what()); } } if (pattern == stripped_mask) if (std::regex_search(str.str(), r)) return true; // Fall through to non regex match } while (s < str_len && m < mask_len && mask[m] != '*') { char string = str[s], wild = mask[m]; if (case_sensitive) { if (wild != string && wild != '?') return false; } else { if (Anope::tolower(wild) != Anope::tolower(string) && wild != '?') return false; } ++m; ++s; } size_t sp = Anope::string::npos, mp = Anope::string::npos; while (s < str_len) { char string = str[s], wild = mask[m]; if (wild == '*') { if (++m == mask_len) return 1; mp = m; sp = s + 1; } else if (case_sensitive) { if (wild == string || wild == '?') { ++m; ++s; } else { m = mp; s = sp++; } } else { if (Anope::tolower(wild) == Anope::tolower(string) || wild == '?') { ++m; ++s; } else { m = mp; s = sp++; } } } if (m < mask_len && mask[m] == '*') ++m; return m == mask_len; } void Anope::Encrypt(const Anope::string &src, Anope::string &dest) { EventReturn MOD_RESULT = EventManager::Get()->Dispatch(&Event::Encrypt::OnEncrypt, src, dest); static_cast(MOD_RESULT); } Anope::string Anope::printf(const char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); return buf; } Anope::string Anope::Hex(const Anope::string &data) { const char hextable[] = "0123456789abcdef"; size_t l = data.length(); Anope::string rv; for (size_t i = 0; i < l; ++i) { unsigned char c = data[i]; rv += hextable[c >> 4]; rv += hextable[c & 0xF]; } return rv; } Anope::string Anope::Hex(const char *data, unsigned len) { const char hextable[] = "0123456789abcdef"; Anope::string rv; for (size_t i = 0; i < len; ++i) { unsigned char c = data[i]; rv += hextable[c >> 4]; rv += hextable[c & 0xF]; } return rv; } void Anope::Unhex(const Anope::string &src, Anope::string &dest) { size_t len = src.length(); Anope::string rv; for (size_t i = 0; i + 1 < len; i += 2) { char h = Anope::tolower(src[i]), l = Anope::tolower(src[i + 1]); unsigned char byte = (h >= 'a' ? h - 'a' + 10 : h - '0') << 4; byte += (l >= 'a' ? l - 'a' + 10 : l - '0'); rv += byte; } dest = rv; } void Anope::Unhex(const Anope::string &src, char *dest, size_t sz) { Anope::string d; Anope::Unhex(src, d); memcpy(dest, d.c_str(), std::min(d.length() + 1, sz)); } int Anope::LastErrorCode() { #ifndef _WIN32 return errno; #else return GetLastError(); #endif } const Anope::string Anope::LastError() { #ifndef _WIN32 return strerror(errno); #else char errbuf[513]; DWORD err = GetLastError(); if (!err) return ""; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0, errbuf, 512, NULL); return errbuf; #endif } Anope::string Anope::Version() { #ifdef VERSION_GIT return Anope::Format("{0}.{1}.{2}{3}-{4}", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_EXTRA, VERSION_GIT); #else return Anope::Format("{0}.{1}.{2}{3}", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_EXTRA); #endif } Anope::string Anope::VersionShort() { return Anope::Format("{0}.{1}.{2}", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); } Anope::string Anope::VersionBuildTime() { #ifdef REPRODUCIBLE_BUILD return "unknown"; #else return Anope::COMPILED; #endif } Anope::string Anope::VersionFlags() { Anope::string flags; #ifdef DEBUG_BUILD flags.append('D'); #endif #ifdef VERSION_GIT flags.append('G'); #endif #ifdef _WIN32 flags.append('W'); #endif if (flags.empty()) flags = "none"; return flags; } int Anope::VersionMajor() { return VERSION_MAJOR; } int Anope::VersionMinor() { return VERSION_MINOR; } int Anope::VersionPatch() { return VERSION_PATCH; } Anope::string Anope::NormalizeBuffer(const Anope::string &buf) { Anope::string newbuf; for (unsigned i = 0, end = buf.length(); i < end; ++i) { switch (buf[i]) { /* ctrl char */ case 1: /* Bold ctrl char */ case 2: break; /* Color ctrl char */ case 3: /* If the next character is a digit, its also removed */ if (isdigit(buf[i + 1])) { ++i; /* not the best way to remove colors * which are two digit but no worse then * how the Unreal does with +S - TSL */ if (isdigit(buf[i + 1])) ++i; /* Check for background color code * and remove it as well */ if (buf[i + 1] == ',') { ++i; if (isdigit(buf[i + 1])) ++i; /* not the best way to remove colors * which are two digit but no worse then * how the Unreal does with +S - TSL */ if (isdigit(buf[i + 1])) ++i; } } break; /* line feed char */ case 10: /* carriage returns char */ case 13: /* Reverse ctrl char */ case 22: /* Italic ctrl char */ case 29: /* Underline ctrl char */ case 31: break; /* A valid char gets copied into the new buffer */ default: newbuf += buf[i]; } } return newbuf; } Anope::string Anope::Resolve(const Anope::string &host, int type) { Anope::string result = host; addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = type; Anope::Logger.Debug2("Resolver: BlockingQuery: Looking up {0}", host); addrinfo *addrresult = NULL; if (getaddrinfo(host.c_str(), NULL, &hints, &addrresult) == 0) { sockaddrs addr; memcpy(&addr, addrresult->ai_addr, addrresult->ai_addrlen); result = addr.addr(); Anope::Logger.Debug2("Resolver: {0} -> {1}", host, result); freeaddrinfo(addrresult); } return result; } Anope::string Anope::Random(size_t len) { char chars[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; Anope::string buf; for (size_t i = 0; i < len; ++i) buf.append(chars[rand() % sizeof(chars)]); return buf; } const kwarg *FormatInfo::GetKwarg(const Anope::string &name) const { for (const kwarg &kw : parameters) if (kw.name == name) return &kw; return nullptr; } void FormatInfo::Format() { size_t start = 0; size_t s = format.find('{', start); while (s != Anope::string::npos) { size_t e = format.find('}', s + 1); if (e == Anope::string::npos) break; Anope::string key = format.substr(s + 1, e - s - 1); // Find replacement for key const kwarg *arg = GetKwarg(key); format.erase(s, e - s + 1); if (arg != nullptr) format.insert(s, arg->value); start = s + (arg != nullptr ? arg->value.length() : 0); s = format.find('{', start); } } const Anope::string &FormatInfo::GetFormat() const { return format; } Anope::string Anope::ProcessEnvVars(const Anope::string &in) { Anope::string out; size_t pos = 0; for (;;) { size_t start = in.find("${", pos); if (start == Anope::string::npos) { // copy from pos out.append(in.substr(pos)); break; } if (start > 0 && in[start - 1] == '\\') { // literal ${ out.append(in.substr(pos, start - pos - 1)); out.append("${"); pos = start + 2; continue; } // copy from pos to start out.append(in.substr(pos, start - pos)); size_t end = in.find("}", start); if (end == Anope::string::npos) break; Anope::string name, def; size_t mid = in.find(":-", pos); if (mid != Anope::string::npos && mid < end) { name = in.substr(start + 2, mid - start - 2); def = in.substr(mid + 2, end - mid - 2); } else { name = in.substr(start + 2, end - start - 2); } const char *env = getenv(name.c_str()); if (env) out.append(env); else out.append(def); pos = end + 1; } return out; }