/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2019 Matt Schatz * Copyright (C) 2018 linuxdaemon * Copyright (C) 2012-2013 Attila Molnar * Copyright (C) 2012, 2014, 2017-2018, 2020-2025 Sadie Powell * Copyright (C) 2012 Robby * Copyright (C) 2012 ChrisTX * Copyright (C) 2009-2010 Daniel De Graaf * Copyright (C) 2007 Dennis Friis * Copyright (C) 2006-2008 Robin Burchell * * This file is part of InspIRCd. InspIRCd 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 . */ #include #ifndef _WIN32 # include #endif #include "inspircd.h" #include "timeutils.h" #include "utility/string.h" #include "xline.h" bool InspIRCd::IsValidMask(const std::string& mask) { const char* dest = mask.c_str(); int exclamation = 0; int atsign = 0; for (const char* i = dest; *i; i++) { /* out of range character, bad mask */ if (*i < 32 || *i > 126) { return false; } switch (*i) { case '!': exclamation++; break; case '@': atsign++; break; } } /* valid masks only have 1 ! and @ */ if (exclamation != 1 || atsign != 1) return false; if (mask.length() > ServerInstance->Config->Limits.GetMaxMask()) return false; return true; } void InspIRCd::StripColor(std::string& line) { for (size_t idx = 0; idx < line.length(); ) { switch (line[idx]) { case '\x02': // Bold case '\x1D': // Italic case '\x11': // Monospace case '\x16': // Reverse case '\x1E': // Strikethrough case '\x1F': // Underline case '\x0F': // Reset line.erase(idx, 1); break; case '\x03': // Color { auto start = idx; while (++idx < line.length() && idx - start < 6) { const auto chr = line[idx]; if (chr != ',' && (chr < '0' || chr > '9')) break; } line.erase(start, idx - start); idx = start; break; } case '\x04': // Hex Color { auto start = idx; while (++idx < line.length() && idx - start < 14) { const auto chr = line[idx]; if (chr != ',' && (chr < '0' || chr > '9') && (chr < 'A' || chr > 'F') && (chr < 'a' || chr > 'f')) break; } line.erase(start, idx - start); idx = start; break; } default: // Non-formatting character. idx++; break; } } } void InspIRCd::ProcessColors(std::string& line) { static const insp::flat_map formats = { { '\\', "\\" }, // Escape { '{', "{" }, // Escape { '}', "}" }, // Escape { 'b', "\x02" }, // Bold { 'c', "\x03" }, // Color { 'h', "\x04" }, // Hex Color { 'i', "\x1D" }, // Italic { 'm', "\x11" }, // Monospace { 'r', "\x16" }, // Reverse { 's', "\x1E" }, // Strikethrough { 'u', "\x1F" }, // Underline { 'x', "\x0F" }, // Reset }; static const insp::flat_map colors = { { "white", 0 }, { "black", 1 }, { "blue", 2 }, { "green", 3 }, { "red", 4 }, { "brown", 5 }, { "magenta", 6 }, { "orange", 7 }, { "yellow", 8 }, { "light green", 9 }, { "cyan", 10 }, { "light cyan", 11 }, { "light blue", 12 }, { "pink", 13 }, { "gray", 14 }, { "grey", 14 }, { "light gray", 15 }, { "light grey", 15 }, { "default", 99 }, }; for (size_t idx = 0; idx < line.length(); ) { if (line[idx] != '\\') { // Regular character. idx++; continue; } auto start = idx; if (++idx >= line.length()) continue; // Stray \ at the end of the string; skip. const auto chr = line[idx]; const auto it = formats.find(chr); if (it == formats.end()) continue; // Unknown escape, skip. line.replace(start, 2, it->second); idx = start + it->second.length(); if (chr != 'c') continue; // Only colors can have values. start = idx; if (idx >= line.length() || line[idx] != '{') continue; // No color value. const auto fgend = line.find_first_of(",}", idx + 1); if (fgend == std::string::npos) { // Malformed color value, strip. line.erase(start); break; } size_t bgend = std::string::npos; if (line[fgend] == ',') { bgend = line.find_first_of('}', fgend + 1); if (bgend == std::string::npos) { // Malformed color value, strip. line.erase(start); break; } } const auto fg = colors.find(line.substr(start + 1, fgend - start - 1)); auto tmp = ConvToStr(fg == colors.end() ? 99 : fg->second); if (bgend != std::string::npos) { const auto bg = colors.find(line.substr(fgend + 1, bgend - fgend - 1)); tmp.push_back(','); tmp.append(ConvToStr(bg == colors.end() ? 99 : bg->second)); } const auto end = bgend == std::string::npos ? fgend : bgend; line.replace(start, end - start + 1, tmp); } } bool InspIRCd::IsHost(const std::string_view& host, bool allowsimple) { // Hostnames must be non-empty and shorter than the maximum hostname length. if (host.empty() || host.length() > ServerInstance->Config->Limits.MaxHost) return false; unsigned int numdashes = 0; unsigned int numdots = 0; bool seendot = false; const auto hostend = host.end() - 1; for (auto iter = host.begin(); iter != host.end(); ++iter) { const auto chr = static_cast(*iter); // If the current character is a label separator. if (chr == '.') { numdots++; // Consecutive separators are not allowed and dashes can not exist at the start or end // of labels and separators must only exist between labels. if (seendot || numdashes || iter == host.begin() || iter == hostend) return false; seendot = true; continue; } // If this point is reached then the character is not a dot. seendot = false; // If the current character is a dash. if (chr == '-') { // Consecutive separators are not allowed and dashes can not exist at the start or end // of labels and separators must only exist between labels. if (seendot || numdashes >= 2 || iter == host.begin() || iter == hostend) return false; numdashes += 1; continue; } // If this point is reached then the character is not a dash. numdashes = 0; // Alphanumeric characters are allowed at any position. if ((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z')) continue; return false; } // Whilst simple hostnames (e.g. localhost) are valid we do not allow the server to use // them to prevent issues with clients that differentiate between short client and server // prefixes by checking whether the nickname contains a dot. return numdots || allowsimple; } bool InspIRCd::IsSID(const std::string_view& str) { /* Returns true if the string given is exactly 3 characters long, * starts with a digit, and the other two characters are A-Z or digits */ return ((str.length() == 3) && isdigit(str[0]) && ((str[1] >= 'A' && str[1] <= 'Z') || isdigit(str[1])) && ((str[2] >= 'A' && str[2] <= 'Z') || isdigit(str[2]))); } namespace { constexpr const auto SECONDS_PER_MINUTE = 60; constexpr const auto SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60; constexpr const auto SECONDS_PER_DAY = SECONDS_PER_HOUR * 24; constexpr const auto SECONDS_PER_WEEK = SECONDS_PER_DAY * 7; constexpr const auto SECONDS_PER_YEAR = (SECONDS_PER_DAY * 365); constexpr const auto SECONDS_PER_AVG_YEAR = SECONDS_PER_YEAR + (SECONDS_PER_HOUR * 6); } /** A lookup table of values for multiplier characters used by * Duration::{Try,}From(). In this lookup table, the indexes for * the ascii values 'm' and 'M' have the value '60', the indexes * for the ascii values 'D' and 'd' have a value of '86400', etc. */ static constexpr unsigned int duration_multi[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SECONDS_PER_DAY, 0, 0, 0, SECONDS_PER_HOUR, 0, 0, 0, 0, SECONDS_PER_MINUTE, 0, 0, 0, 0, 0, 1, 0, 0, 0, SECONDS_PER_WEEK, 0, SECONDS_PER_AVG_YEAR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SECONDS_PER_DAY, 0, 0, 0, SECONDS_PER_HOUR, 0, 0, 0, 0, SECONDS_PER_MINUTE, 0, 0, 0, 0, 0, 1, 0, 0, 0, SECONDS_PER_WEEK, 0, SECONDS_PER_AVG_YEAR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; bool Duration::TryFrom(const std::string& str, unsigned long& duration) { unsigned long total = 0; unsigned long subtotal = 0; /* Iterate each item in the string, looking for number or multiplier */ for (const auto chr : str) { /* Found a number, queue it onto the current number */ if (chr >= '0' && chr <= '9') { subtotal = (subtotal * 10) + (chr - '0'); } else { /* Found something that's not a number, find out how much * it multiplies the built up number by, multiply the total * and reset the built up number. */ unsigned int multiplier = duration_multi[static_cast(chr)]; if (multiplier == 0) return false; total += subtotal * multiplier; /* Next subtotal please */ subtotal = 0; } } /* Any trailing values built up are treated as raw seconds */ duration = total + subtotal; return true; } unsigned long Duration::From(const std::string& str) { unsigned long out = 0; Duration::TryFrom(str, out); return out; } bool Duration::IsValid(const std::string& duration) { for (const auto c : duration) { if (((c >= '0') && (c <= '9'))) continue; if (!duration_multi[static_cast(c)]) return false; } return true; } std::string Duration::ToString(unsigned long duration) { if (duration == 0) return "0s"; std::string ret; const auto years = (duration / SECONDS_PER_YEAR); if (years) { ret = FMT::format("{}y", years); duration -= (years * SECONDS_PER_YEAR); } const auto weeks = (duration / SECONDS_PER_WEEK); if (weeks) { ret += FMT::format("{}w", weeks); duration -= (weeks * SECONDS_PER_WEEK); } const auto days = (duration / SECONDS_PER_DAY); if (days) { ret += FMT::format("{}d", days); duration -= (days * SECONDS_PER_DAY); } const auto hours = (duration / SECONDS_PER_HOUR); if (hours) { ret += FMT::format("{}h", hours); duration -= (hours * SECONDS_PER_HOUR); } const auto minutes = (duration / SECONDS_PER_MINUTE); if (minutes) { ret += FMT::format("{}m", minutes); duration -= (minutes * SECONDS_PER_MINUTE); } if (duration) ret += FMT::format("{}s", duration); return ret; } std::string Duration::ToLongString(unsigned long duration, bool brief) { if (duration == 0) return "0 seconds"; if (brief) { // This will get inlined when compiled with optimisations. auto nearest = [](unsigned long seconds, unsigned long roundto) { if ((seconds % roundto) <= (roundto / 2)) return seconds - (seconds % roundto); return seconds - (seconds % roundto) + roundto; }; // In order to get a shorter result we round to the nearest period. if (duration >= SECONDS_PER_YEAR) duration = nearest(duration, SECONDS_PER_DAY); // Nearest day if its more than a year else if (duration >= SECONDS_PER_DAY) duration = nearest(duration, SECONDS_PER_HOUR); // Nearest hour if its more than a day else if (duration >= SECONDS_PER_HOUR) duration = nearest(duration, SECONDS_PER_MINUTE); // Nearest minute if its more than an hour } std::string ret; const auto years = (duration / SECONDS_PER_YEAR); if (years) { ret = FMT::format("{} {}", years, years == 1 ? "year" : "years"); duration -= (years * SECONDS_PER_YEAR); } const auto weeks = (duration / SECONDS_PER_WEEK); if (weeks) { ret += ret.empty() ? "" : ", "; ret += FMT::format("{} {}", weeks, weeks == 1 ? "week" : "weeks"); duration -= (weeks * SECONDS_PER_WEEK); } const auto days = (duration / SECONDS_PER_DAY); if (days) { ret += ret.empty() ? "" : ", "; ret += FMT::format("{} {}", days, days == 1 ? "day" : "days"); duration -= (days * SECONDS_PER_DAY); } const auto hours = (duration / SECONDS_PER_HOUR); if (hours) { ret += ret.empty() ? "" : ", "; ret += FMT::format("{} {}", hours, hours == 1 ? "hour" : "hours"); duration -= (hours * SECONDS_PER_HOUR); } const auto minutes = (duration / SECONDS_PER_MINUTE); if (minutes) { ret += ret.empty() ? "" : ", "; ret += FMT::format("{} {}", minutes, minutes == 1 ? "minute" : "minutes"); duration -= (minutes * SECONDS_PER_MINUTE); } if (duration) { ret += ret.empty() ? "" : ", "; ret += FMT::format("{} {}", duration, duration == 1 ? "second" : "seconds"); } const auto first_comma = ret.find(','); const auto last_comma = ret.rfind(','); if (first_comma != std::string::npos) { if (first_comma == last_comma) { // BEFORE: 1 minute, 2 seconds // AFTER: 1 minute and 2 seconds ret.replace(last_comma, 1, " and"); } else { // BEFORE: 1 hour, 2 minutes, 3 seconds // AFTER: 1 hour, 2 minutes, and 3 seconds ret.insert(last_comma + 1, " and"); } } return ret; } std::string Time::ToString(time_t curtime, const char* format, bool utc) { #ifdef _WIN32 if (curtime < 0) curtime = 0; #endif struct tm* timeinfo = utc ? gmtime(&curtime) : localtime(&curtime); if (!timeinfo) { curtime = 0; timeinfo = localtime(&curtime); } // If the calculated year exceeds four digits or is less than the year 1000, // the behavior of asctime() is undefined if (timeinfo->tm_year + 1900 > 9999) timeinfo->tm_year = 9999 - 1900; else if (timeinfo->tm_year + 1900 < 1000) timeinfo->tm_year = 0; // This is the default format used by asctime without the terminating new line. if (!format) format = Time::DEFAULT_SHORT; char buffer[512]; if (!strftime(buffer, sizeof(buffer), format, timeinfo)) buffer[0] = '\0'; return buffer; } std::string InspIRCd::GenRandomStr(size_t length) const { static const 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', }; std::string buf; buf.reserve(length); for (size_t idx = 0; idx < length; ++idx) buf.push_back(chars[GenRandomInt(std::size(chars))]); return buf; } // NOTE: this has a slight bias for lower values if max is not a power of 2. // Don't use it if that matters. unsigned long InspIRCd::GenRandomInt(unsigned long max) const { unsigned long rv; GenRandom(reinterpret_cast(&rv), sizeof(rv)); return rv % max; } // This is overridden by a higher-quality algorithm when TLS support is loaded void InspIRCd::DefaultGenRandom(char* output, size_t max) { #ifdef HAS_GETENTROPY if (getentropy(output, max) == 0) return; #endif #ifdef HAS_ARC4RANDOM_BUF arc4random_buf(output, max); #else static std::random_device device; static std::mt19937 engine(device()); static std::uniform_int_distribution dist(CHAR_MIN, CHAR_MAX); for (size_t i = 0; i < max; ++i) output[i] = static_cast(dist(engine)); #endif }