[Loginserver] Worldserver Name Sanitization (#1739)

* Sanitize bad words in server names

* Add config options and enforcement for dev/test servers and servers starting with a special character

* Refine bad word logic

* Add installer to dev/test servers

* Change server prefixes

* Special char prefix

* Formatting

* Remove multi words

* Add server types enum

* Add error constants

* Remove sanitize from world level

* Use strn0cpy
This commit is contained in:
Chris Miles 2021-11-12 23:02:05 -06:00 committed by GitHub
parent 8b83a13560
commit a9d1034298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 726 additions and 243 deletions

View File

@ -19,32 +19,33 @@
#include <cctype>
#ifdef _WINDOWS
#include <windows.h>
#include <windows.h>
#define snprintf _snprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#define snprintf _snprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#else
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#endif
#ifndef va_copy
#define va_copy(d,s) ((d) = (s))
#define va_copy(d,s) ((d) = (s))
#endif
// original source:
// https://github.com/facebook/folly/blob/master/folly/String.cpp
//
const std::string vStringFormat(const char* format, va_list args)
const std::string vStringFormat(const char *format, va_list args)
{
std::string output;
va_list tmpargs;
va_list tmpargs;
va_copy(tmpargs,args);
va_copy(tmpargs, args);
int characters_used = vsnprintf(nullptr, 0, format, tmpargs);
va_end(tmpargs);
@ -52,7 +53,7 @@ const std::string vStringFormat(const char* format, va_list args)
if (characters_used > 0) {
output.resize(characters_used + 1);
va_copy(tmpargs,args);
va_copy(tmpargs, args);
characters_used = vsnprintf(&output[0], output.capacity(), format, tmpargs);
va_end(tmpargs);
@ -60,8 +61,9 @@ const std::string vStringFormat(const char* format, va_list args)
// We shouldn't have a format error by this point, but I can't imagine what error we
// could have by this point. Still, return empty string;
if (characters_used < 0)
if (characters_used < 0) {
output.clear();
}
}
return output;
}
@ -87,8 +89,9 @@ const std::string str_toupper(std::string s)
const std::string ucfirst(std::string s)
{
std::string output = s;
if (!s.empty())
if (!s.empty()) {
output[0] = static_cast<char>(::toupper(s[0]));
}
return output;
}
@ -102,18 +105,20 @@ const std::string StringFormat(const char *format, ...)
return output;
}
std::vector<std::string> SplitString(const std::string &str, const char delim) {
std::vector<std::string> SplitString(const std::string &str, const char delim)
{
std::vector<std::string> ret;
std::string::size_type start = 0;
auto end = str.find(delim);
std::string::size_type start = 0;
auto end = str.find(delim);
while (end != std::string::npos) {
ret.emplace_back(str, start, end - start);
start = end + 1;
end = str.find(delim, start);
end = str.find(delim, start);
}
// this will catch the last word since the string is unlikely to end with a delimiter
if (str.length() > start)
if (str.length() > start) {
ret.emplace_back(str, start, str.length() - start);
}
return ret;
}
@ -153,14 +158,16 @@ std::string get_between(const std::string &s, std::string start_delim, std::stri
return "";
}
std::string::size_type search_deliminated_string(const std::string &haystack, const std::string &needle, const char deliminator)
std::string::size_type
search_deliminated_string(const std::string &haystack, const std::string &needle, const char deliminator)
{
// this shouldn't go out of bounds, even without obvious bounds checks
auto pos = haystack.find(needle);
while (pos != std::string::npos) {
auto c = haystack[pos + needle.length()];
if ((c == '\0' || c == deliminator) && (pos == 0 || haystack[pos - 1] == deliminator))
if ((c == '\0' || c == deliminator) && (pos == 0 || haystack[pos - 1] == deliminator)) {
return pos;
}
pos = haystack.find(needle, pos + needle.length());
}
return std::string::npos;
@ -180,7 +187,7 @@ std::string implode(std::string glue, std::vector<std::string> src)
}
std::string final_output = output.str();
final_output.resize (output.str().size () - glue.size());
final_output.resize(output.str().size() - glue.size());
return final_output;
}
@ -202,80 +209,83 @@ std::vector<std::string> wrap(std::vector<std::string> &src, std::string charact
return new_vector;
}
std::string EscapeString(const std::string &s) {
std::string EscapeString(const std::string &s)
{
std::string ret;
size_t sz = s.length();
for(size_t i = 0; i < sz; ++i) {
size_t sz = s.length();
for (size_t i = 0; i < sz; ++i) {
char c = s[i];
switch(c) {
case '\x00':
ret += "\\x00";
break;
case '\n':
ret += "\\n";
break;
case '\r':
ret += "\\r";
break;
case '\\':
ret += "\\\\";
break;
case '\'':
ret += "\\'";
break;
case '\"':
ret += "\\\"";
break;
case '\x1a':
ret += "\\x1a";
break;
default:
ret.push_back(c);
break;
switch (c) {
case '\x00':
ret += "\\x00";
break;
case '\n':
ret += "\\n";
break;
case '\r':
ret += "\\r";
break;
case '\\':
ret += "\\\\";
break;
case '\'':
ret += "\\'";
break;
case '\"':
ret += "\\\"";
break;
case '\x1a':
ret += "\\x1a";
break;
default:
ret.push_back(c);
break;
}
}
return ret;
}
std::string EscapeString(const char *src, size_t sz) {
std::string EscapeString(const char *src, size_t sz)
{
std::string ret;
for(size_t i = 0; i < sz; ++i) {
for (size_t i = 0; i < sz; ++i) {
char c = src[i];
switch(c) {
case '\x00':
ret += "\\x00";
break;
case '\n':
ret += "\\n";
break;
case '\r':
ret += "\\r";
break;
case '\\':
ret += "\\\\";
break;
case '\'':
ret += "\\'";
break;
case '\"':
ret += "\\\"";
break;
case '\x1a':
ret += "\\x1a";
break;
default:
ret.push_back(c);
break;
switch (c) {
case '\x00':
ret += "\\x00";
break;
case '\n':
ret += "\\n";
break;
case '\r':
ret += "\\r";
break;
case '\\':
ret += "\\\\";
break;
case '\'':
ret += "\\'";
break;
case '\"':
ret += "\\\"";
break;
case '\x1a':
ret += "\\x1a";
break;
default:
ret.push_back(c);
break;
}
}
return ret;
}
bool StringIsNumber(const std::string &s) {
bool StringIsNumber(const std::string &s)
{
try {
auto r = stod(s);
return true;
@ -285,15 +295,18 @@ bool StringIsNumber(const std::string &s) {
}
}
void ToLowerString(std::string &s) {
void ToLowerString(std::string &s)
{
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
}
void ToUpperString(std::string &s) {
void ToUpperString(std::string &s)
{
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
}
std::string JoinString(const std::vector<std::string>& ar, const std::string &delim) {
std::string JoinString(const std::vector<std::string> &ar, const std::string &delim)
{
std::string ret;
for (size_t i = 0; i < ar.size(); ++i) {
if (i != 0) {
@ -313,7 +326,7 @@ void find_replace(std::string &string_subject, const std::string &search_string,
}
size_t start_pos = 0;
while((start_pos = string_subject.find(search_string, start_pos)) != std::string::npos) {
while ((start_pos = string_subject.find(search_string, start_pos)) != std::string::npos) {
string_subject.replace(start_pos, search_string.length(), replace_string);
start_pos += replace_string.length();
}
@ -334,9 +347,9 @@ void ParseAccountString(const std::string &s, std::string &account, std::string
auto split = SplitString(s, ':');
if (split.size() == 2) {
loginserver = split[0];
account = split[1];
account = split[1];
}
else if(split.size() == 1) {
else if (split.size() == 1) {
account = split[0];
}
}
@ -345,9 +358,11 @@ void ParseAccountString(const std::string &s, std::string &account, std::string
// normal strncpy doesnt put a null term on copied strings, this one does
// ref: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcecrt/htm/_wcecrt_strncpy_wcsncpy.asp
char* strn0cpy(char* dest, const char* source, uint32 size) {
if (!dest)
char *strn0cpy(char *dest, const char *source, uint32 size)
{
if (!dest) {
return 0;
}
if (size == 0 || source == 0) {
dest[0] = 0;
return dest;
@ -359,123 +374,159 @@ char* strn0cpy(char* dest, const char* source, uint32 size) {
// String N w/null Copy Truncated?
// return value =true if entire string(source) fit, false if it was truncated
bool strn0cpyt(char* dest, const char* source, uint32 size) {
if (!dest)
bool strn0cpyt(char *dest, const char *source, uint32 size)
{
if (!dest) {
return 0;
}
if (size == 0 || source == 0) {
dest[0] = 0;
return false;
}
strncpy(dest, source, size);
dest[size - 1] = 0;
return (bool)(source[strlen(dest)] == 0);
return (bool) (source[strlen(dest)] == 0);
}
const char *MakeLowerString(const char *source) {
const char *MakeLowerString(const char *source)
{
static char str[128];
if (!source)
if (!source) {
return nullptr;
}
MakeLowerString(source, str);
return str;
}
void MakeLowerString(const char *source, char *target) {
void MakeLowerString(const char *source, char *target)
{
if (!source || !target) {
*target = 0;
return;
}
while (*source)
{
while (*source) {
*target = tolower(*source);
target++; source++;
target++;
source++;
}
*target = 0;
}
uint32 hextoi(const char* num) {
if (num == nullptr)
uint32 hextoi(const char *num)
{
if (num == nullptr) {
return 0;
}
int len = strlen(num);
if (len < 3)
if (len < 3) {
return 0;
}
if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X'))
if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) {
return 0;
}
uint32 ret = 0;
int mul = 1;
for (int i = len - 1; i >= 2; i--) {
if (num[i] >= 'A' && num[i] <= 'F')
uint32 ret = 0;
int mul = 1;
for (int i = len - 1; i >= 2; i--) {
if (num[i] >= 'A' && num[i] <= 'F') {
ret += ((num[i] - 'A') + 10) * mul;
else if (num[i] >= 'a' && num[i] <= 'f')
}
else if (num[i] >= 'a' && num[i] <= 'f') {
ret += ((num[i] - 'a') + 10) * mul;
else if (num[i] >= '0' && num[i] <= '9')
}
else if (num[i] >= '0' && num[i] <= '9') {
ret += (num[i] - '0') * mul;
else
}
else {
return 0;
}
mul *= 16;
}
return ret;
}
uint64 hextoi64(const char* num) {
if (num == nullptr)
uint64 hextoi64(const char *num)
{
if (num == nullptr) {
return 0;
}
int len = strlen(num);
if (len < 3)
if (len < 3) {
return 0;
}
if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X'))
if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) {
return 0;
}
uint64 ret = 0;
int mul = 1;
for (int i = len - 1; i >= 2; i--) {
if (num[i] >= 'A' && num[i] <= 'F')
uint64 ret = 0;
int mul = 1;
for (int i = len - 1; i >= 2; i--) {
if (num[i] >= 'A' && num[i] <= 'F') {
ret += ((num[i] - 'A') + 10) * mul;
else if (num[i] >= 'a' && num[i] <= 'f')
}
else if (num[i] >= 'a' && num[i] <= 'f') {
ret += ((num[i] - 'a') + 10) * mul;
else if (num[i] >= '0' && num[i] <= '9')
}
else if (num[i] >= '0' && num[i] <= '9') {
ret += (num[i] - '0') * mul;
else
}
else {
return 0;
}
mul *= 16;
}
return ret;
}
bool atobool(const char* iBool) {
bool atobool(const char *iBool)
{
if (iBool == nullptr)
if (iBool == nullptr) {
return false;
if (!strcasecmp(iBool, "true"))
}
if (!strcasecmp(iBool, "true")) {
return true;
if (!strcasecmp(iBool, "false"))
}
if (!strcasecmp(iBool, "false")) {
return false;
if (!strcasecmp(iBool, "yes"))
}
if (!strcasecmp(iBool, "yes")) {
return true;
if (!strcasecmp(iBool, "no"))
}
if (!strcasecmp(iBool, "no")) {
return false;
if (!strcasecmp(iBool, "on"))
}
if (!strcasecmp(iBool, "on")) {
return true;
if (!strcasecmp(iBool, "off"))
}
if (!strcasecmp(iBool, "off")) {
return false;
if (!strcasecmp(iBool, "enable"))
}
if (!strcasecmp(iBool, "enable")) {
return true;
if (!strcasecmp(iBool, "disable"))
}
if (!strcasecmp(iBool, "disable")) {
return false;
if (!strcasecmp(iBool, "enabled"))
}
if (!strcasecmp(iBool, "enabled")) {
return true;
if (!strcasecmp(iBool, "disabled"))
}
if (!strcasecmp(iBool, "disabled")) {
return false;
if (!strcasecmp(iBool, "y"))
}
if (!strcasecmp(iBool, "y")) {
return true;
if (!strcasecmp(iBool, "n"))
}
if (!strcasecmp(iBool, "n")) {
return false;
if (atoi(iBool))
}
if (atoi(iBool)) {
return true;
}
return false;
}
@ -484,21 +535,19 @@ char *CleanMobName(const char *in, char *out)
{
unsigned i, j;
for (i = j = 0; i < strlen(in); i++)
{
for (i = j = 0; i < strlen(in); i++) {
// convert _ to space.. any other conversions like this? I *think* this
// is the only non alpha char that's not stripped but converted.
if (in[i] == '_')
{
if (in[i] == '_') {
out[j++] = ' ';
}
else
{
if (isalpha(in[i]) || (in[i] == '`')) // numbers, #, or any other crap just gets skipped
else {
if (isalpha(in[i]) || (in[i] == '`')) { // numbers, #, or any other crap just gets skipped
out[j++] = in[i];
}
}
}
out[j] = 0; // terimnate the string before returning it
out[j] = 0; // terimnate the string before returning it
return out;
}
@ -506,8 +555,9 @@ char *CleanMobName(const char *in, char *out)
void RemoveApostrophes(std::string &s)
{
for (unsigned int i = 0; i < s.length(); ++i)
if (s[i] == '\'')
if (s[i] == '\'') {
s[i] = '_';
}
}
char *RemoveApostrophes(const char *s)
@ -517,8 +567,9 @@ char *RemoveApostrophes(const char *s)
strcpy(NewString, s);
for (unsigned int i = 0; i < strlen(NewString); ++i)
if (NewString[i] == '\'')
if (NewString[i] == '\'') {
NewString[i] = '_';
}
return NewString;
}
@ -537,11 +588,12 @@ const char *ConvertArrayF(float input, char *returnchar)
bool isAlphaNumeric(const char *text)
{
for (unsigned int charIndex = 0; charIndex<strlen(text); charIndex++) {
for (unsigned int charIndex = 0; charIndex < strlen(text); charIndex++) {
if ((text[charIndex] < 'a' || text[charIndex] > 'z') &&
(text[charIndex] < 'A' || text[charIndex] > 'Z') &&
(text[charIndex] < '0' || text[charIndex] > '9'))
(text[charIndex] < '0' || text[charIndex] > '9')) {
return false;
}
}
return true;
@ -596,11 +648,10 @@ std::string numberToWords(unsigned long long int n)
}
// first letter capitalized and rest made lower case
std::string FormatName(const std::string& char_name)
std::string FormatName(const std::string &char_name)
{
std::string formatted(char_name);
if (!formatted.empty())
{
if (!formatted.empty()) {
std::transform(formatted.begin(), formatted.end(), formatted.begin(), ::tolower);
formatted[0] = ::toupper(formatted[0]);
}
@ -620,6 +671,12 @@ bool IsAllowedWorldServerCharacterList(char c)
void SanitizeWorldServerName(char *name)
{
std::string server_long_name = name;
strcpy(name, SanitizeWorldServerName(server_long_name).c_str());
}
std::string SanitizeWorldServerName(std::string server_long_name)
{
server_long_name.erase(
std::remove_if(
server_long_name.begin(),
@ -632,5 +689,333 @@ void SanitizeWorldServerName(char *name)
server_long_name = trim(server_long_name);
strcpy(name, server_long_name.c_str());
// bad word filter
for (auto &piece: split_string(server_long_name, " ")) {
for (auto &word: GetBadWords()) {
// for shorter words that can actually be part of legitimate words
// make sure that it isn't part of another word by matching on a space
if (str_tolower(piece) == word) {
find_replace(
server_long_name,
piece,
repeat("*", (int) word.length())
);
continue;
}
auto pos = str_tolower(piece).find(word);
if (str_tolower(piece).find(word) != std::string::npos && piece.length() > 4 && word.length() > 4) {
auto found_word = piece.substr(pos, word.length());
std::string replaced_piece = piece.substr(pos, word.length());
find_replace(
server_long_name,
replaced_piece,
repeat("*", (int) word.length())
);
}
}
}
return server_long_name;
}
std::string repeat(std::string s, int n)
{
std::string s1 = s;
for (int i = 1; i < n; i++) {
s += s1;
}
return s;
}
std::vector<std::string> GetBadWords()
{
return std::vector<std::string>{
"2g1c",
"acrotomophilia",
"anal",
"anilingus",
"anus",
"apeshit",
"arsehole",
"ass",
"asshole",
"assmunch",
"autoerotic",
"babeland",
"bangbros",
"bangbus",
"bareback",
"barenaked",
"bastard",
"bastardo",
"bastinado",
"bbw",
"bdsm",
"beaner",
"beaners",
"beaver",
"beastiality",
"bestiality",
"bimbos",
"birdlock",
"bitch",
"bitches",
"blowjob",
"blumpkin",
"bollocks",
"bondage",
"boner",
"boob",
"boobs",
"bukkake",
"bulldyke",
"bullshit",
"bung",
"bunghole",
"busty",
"butt",
"buttcheeks",
"butthole",
"camel toe",
"camgirl",
"camslut",
"camwhore",
"carpetmuncher",
"cialis",
"circlejerk",
"clit",
"clitoris",
"clusterfuck",
"cock",
"cocks",
"coprolagnia",
"coprophilia",
"cornhole",
"coon",
"coons",
"creampie",
"cum",
"cumming",
"cumshot",
"cumshots",
"cunnilingus",
"cunt",
"darkie",
"daterape",
"deepthroat",
"dendrophilia",
"dick",
"dildo",
"dingleberry",
"dingleberries",
"doggiestyle",
"doggystyle",
"dolcett",
"domination",
"dominatrix",
"dommes",
"hump",
"dvda",
"ecchi",
"ejaculation",
"erotic",
"erotism",
"escort",
"eunuch",
"fag",
"faggot",
"fecal",
"felch",
"fellatio",
"feltch",
"femdom",
"figging",
"fingerbang",
"fingering",
"fisting",
"footjob",
"frotting",
"fuck",
"fuckin",
"fucking",
"fucktards",
"fudgepacker",
"futanari",
"gangbang",
"gangbang",
"gaysex",
"genitals",
"goatcx",
"goatse",
"gokkun",
"goodpoop",
"goregasm",
"grope",
"g-spot",
"guro",
"handjob",
"hentai",
"homoerotic",
"honkey",
"hooker",
"horny",
"humping",
"incest",
"intercourse",
"jailbait",
"jigaboo",
"jiggaboo",
"jiggerboo",
"jizz",
"juggs",
"kike",
"kinbaku",
"kinkster",
"kinky",
"knobbing",
"livesex",
"lolita",
"lovemaking",
"masturbate",
"masturbating",
"masturbation",
"milf",
"mong",
"motherfucker",
"muffdiving",
"nambla",
"nawashi",
"negro",
"neonazi",
"nigga",
"nigger",
"nimphomania",
"nipple",
"nipples",
"nsfw",
"nude",
"nudity",
"nutten",
"nympho",
"nymphomania",
"octopussy",
"omorashi",
"orgasm",
"orgy",
"paedophile",
"paki",
"panties",
"panty",
"pedobear",
"pedophile",
"pegging",
"penis",
"pikey",
"pissing",
"pisspig",
"playboy",
"ponyplay",
"poof",
"poon",
"poontang",
"punany",
"poopchute",
"porn",
"porno",
"pornography",
"pthc",
"pubes",
"pussy",
"queaf",
"queef",
"quim",
"raghead",
"rape",
"raping",
"rapist",
"rectum",
"rimjob",
"rimming",
"sadism",
"santorum",
"scat",
"schlong",
"scissoring",
"semen",
"sex",
"sexcam",
"sexo",
"sexy",
"sexual",
"sexually",
"sexuality",
"shemale",
"shibari",
"shit",
"shitblimp",
"shitty",
"shota",
"shrimping",
"skeet",
"slanteye",
"slut",
"s&m",
"smut",
"snatch",
"snowballing",
"sodomize",
"sodomy",
"spastic",
"spic",
"splooge",
"spooge",
"spunk",
"strapon",
"strappado",
"suck",
"sucks",
"swastika",
"swinger",
"threesome",
"throating",
"thumbzilla",
"tight white",
"tit",
"tits",
"titties",
"titty",
"topless",
"tosser",
"towelhead",
"tranny",
"tribadism",
"tubgirl",
"tushy",
"twat",
"twink",
"twinkie",
"undressing",
"upskirt",
"urophilia",
"vagina",
"viagra",
"vibrator",
"vorarephilia",
"voyeur",
"voyeurweb",
"voyuer",
"vulva",
"wank",
"wetback",
"whore",
"worldsex",
"xx",
"xxx",
"yaoi",
"yiffy",
"zoophilia"
};
}

View File

@ -208,7 +208,11 @@ void RemoveApostrophes(std::string &s);
std::string convert2digit(int n, std::string suffix);
std::string numberToWords(unsigned long long int n);
std::string FormatName(const std::string& char_name);
bool IsAllowedWorldServerCharacterList(char c);
void SanitizeWorldServerName(char *name);
std::string SanitizeWorldServerName(std::string server_long_name);
std::string repeat(std::string s, int n);
std::vector<std::string> GetBadWords();
template<typename InputIterator, typename OutputIterator>
auto CleanMobName(InputIterator first, InputIterator last, OutputIterator result)

View File

@ -22,7 +22,7 @@ SET(eqlogin_headers
loginserver_command_handler.h
loginserver_webserver.h
login_server.h
login_structures.h
login_types.h
options.h
server_manager.h
world_server.h

View File

@ -7,44 +7,9 @@
#include "../common/eq_stream_intf.h"
#include "../common/net/dns.h"
#include "../common/net/daybreak_connection.h"
#include "login_structures.h"
#include "login_types.h"
#include <memory>
enum LSClientVersion {
cv_titanium,
cv_sod
};
enum LSClientStatus {
cs_not_sent_session_ready,
cs_waiting_for_login,
cs_creating_account,
cs_failed_to_login,
cs_logged_in
};
namespace LS {
namespace ServerStatusFlags {
enum eServerStatusFlags {
Up = 0, // default
Down = 1,
Unused = 2,
Locked = 4 // can be combined with Down to show "Locked (Down)"
};
}
namespace ServerTypeFlags {
enum eServerTypeFlags {
None = 0,
Standard = 1,
Unknown2 = 2,
Unknown4 = 4,
Preferred = 8,
Legends = 16 // can be combined with Preferred flag to override color in Legends section with Preferred color (green)
};
}
}
/**
* Client class, controls a single client and it's connection to the login server
*/

View File

@ -4,39 +4,34 @@
#pragma pack(1)
// unencrypted base message header in all packets
struct LoginBaseMessage_Struct
{
struct LoginBaseMessage_Struct {
int32_t sequence; // request type/login sequence (2: handshake, 3: login, 4: serverlist, ...)
bool compressed; // true: deflated
int8_t encrypt_type; // 1: invert (unused) 2: des (2 for encrypted player logins and order expansions) (client uses what it sent, ignores in reply)
int32_t unk3; // unused?
};
struct LoginBaseReplyMessage_Struct
{
struct LoginBaseReplyMessage_Struct {
bool success; // 0: failure (shows error string) 1: success
int32_t error_str_id; // last error eqlsstr id, default: 101 (no error)
char str[1]; // variable length, unknown (may be unused, this struct is a common pattern elsewhere)
};
struct LoginHandShakeReply_Struct
{
LoginBaseMessage_Struct base_header;
struct LoginHandShakeReply_Struct {
LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
char unknown[1]; // variable length string
char unknown[1]; // variable length string
};
// for reference, login buffer is variable (minimum size 8 due to encryption)
struct PlayerLogin_Struct
{
struct PlayerLogin_Struct {
LoginBaseMessage_Struct base_header;
char username[1];
char password[1];
char username[1];
char password[1];
};
// variable length, can use directly if not serializing strings
struct PlayerLoginReply_Struct
{
struct PlayerLoginReply_Struct {
// base header excluded to make struct data easier to encrypt
//LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
@ -59,8 +54,7 @@ struct PlayerLoginReply_Struct
};
// variable length, for reference
struct LoginClientServerData_Struct
{
struct LoginClientServerData_Struct {
char ip[1];
int32_t server_type; // legends, preferred, standard
int32_t server_id;
@ -72,29 +66,78 @@ struct LoginClientServerData_Struct
};
// variable length, for reference
struct ServerListReply_Struct
{
LoginBaseMessage_Struct base_header;
struct ServerListReply_Struct {
LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
int32_t server_count;
int32_t server_count;
LoginClientServerData_Struct servers[0];
};
struct PlayEverquestRequest_Struct {
LoginBaseMessage_Struct base_header;
uint32 server_number;
uint32 server_number;
};
// SCJoinServerReply
struct PlayEverquestResponse_Struct {
LoginBaseMessage_Struct base_header;
LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
uint32 server_number;
uint32 server_number;
};
#pragma pack()
enum LSClientVersion {
cv_titanium,
cv_sod
};
enum LSClientStatus {
cs_not_sent_session_ready,
cs_waiting_for_login,
cs_creating_account,
cs_failed_to_login,
cs_logged_in
};
namespace LS {
namespace ServerStatusFlags {
enum eServerStatusFlags {
Up = 0, // default
Down = 1,
Unused = 2,
Locked = 4 // can be combined with Down to show "Locked (Down)"
};
}
namespace ServerTypeFlags {
enum eServerTypeFlags {
None = 0,
Standard = 1,
Unknown2 = 2,
Unknown4 = 4,
Preferred = 8,
Legends = 16 // can be combined with Preferred flag to override color in Legends section with Preferred color (green)
};
}
enum ServerType {
Standard = 3,
Preferred = 2,
Legends = 1,
};
namespace ErrStr {
constexpr static int NO_ERROR = 101; // No Error
constexpr static int SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later.
constexpr static int ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information.
constexpr static int ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information.
constexpr static int WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later.
constexpr static int ERROR_1018_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again.
constexpr static int UNKNOWN_ERROR = 102; // Error - Unknown Error Occurred
};
}
#endif

View File

@ -12,6 +12,8 @@
"worldservers": {
"unregistered_allowed": true,
"show_player_count": false,
"dev_test_servers_list_bottom": false,
"special_character_start_list_bottom": false,
"reject_duplicate_servers": false
},
"web_api": {

View File

@ -62,9 +62,30 @@ void LoadServerConfig()
"worldservers",
"reject_duplicate_servers",
false
));
server.options.AllowUnregistered(server.config.GetVariableBool("worldservers", "unregistered_allowed", true));
)
);
server.options.SetShowPlayerCount(server.config.GetVariableBool("worldservers", "show_player_count", false));
server.options.AllowUnregistered(
server.config.GetVariableBool(
"worldservers",
"unregistered_allowed",
true
)
);
server.options.SetWorldDevTestServersListBottom(
server.config.GetVariableBool(
"worldservers",
"dev_test_servers_list_bottom",
false
)
);
server.options.SetWorldSpecialCharacterStartListBottom(
server.config.GetVariableBool(
"worldservers",
"special_character_start_list_bottom",
false
)
);
/**
* Account
@ -242,6 +263,14 @@ int main(int argc, char **argv)
LogInfo("[Config] [WorldServer] IsRejectingDuplicateServers [{0}]", server.options.IsRejectingDuplicateServers());
LogInfo("[Config] [WorldServer] IsUnregisteredAllowed [{0}]", server.options.IsUnregisteredAllowed());
LogInfo("[Config] [WorldServer] ShowPlayerCount [{0}]", server.options.IsShowPlayerCountEnabled());
LogInfo(
"[Config] [WorldServer] DevAndTestServersListBottom [{0}]",
server.options.IsWorldDevTestServersListBottom()
);
LogInfo(
"[Config] [WorldServer] SpecialCharactersStartListBottom [{0}]",
server.options.IsWorldSpecialCharacterStartListBottom()
);
LogInfo("[Config] [Security] GetEncryptionMode [{0}]", server.options.GetEncryptionMode());
LogInfo("[Config] [Security] IsTokenLoginAllowed [{0}]", server.options.IsTokenLoginAllowed());
LogInfo("[Config] [Security] IsPasswordLoginAllowed [{0}]", server.options.IsPasswordLoginAllowed());

View File

@ -121,6 +121,20 @@ public:
{
Options::show_player_count = show_player_count;
}
inline bool IsWorldDevTestServersListBottom() const { return world_dev_test_servers_list_bottom; }
inline void SetWorldDevTestServersListBottom(bool dev_test_servers_list_bottom)
{
Options::world_dev_test_servers_list_bottom = dev_test_servers_list_bottom;
}
inline bool IsWorldSpecialCharacterStartListBottom() const
{
return world_special_character_start_list_bottom;
}
inline void SetWorldSpecialCharacterStartListBottom(bool world_special_character_start_list_bottom)
{
Options::world_special_character_start_list_bottom = world_special_character_start_list_bottom;
}
private:
bool allow_unregistered;
@ -129,6 +143,8 @@ private:
bool dump_in_packets;
bool dump_out_packets;
bool reject_duplicate_servers;
bool world_dev_test_servers_list_bottom;
bool world_special_character_start_list_bottom;
bool allow_token_login;
bool allow_password_login;
bool show_player_count;

View File

@ -1,6 +1,6 @@
#include "server_manager.h"
#include "login_server.h"
#include "login_structures.h"
#include "login_types.h"
#include <stdlib.h>
#include "../common/eqemu_logsys.h"

View File

@ -1,6 +1,6 @@
#include "world_server.h"
#include "login_server.h"
#include "login_structures.h"
#include "login_types.h"
#include "../common/ip_util.h"
#include "../common/string_util.h"
@ -198,15 +198,15 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne
LogDebug("User-To-World Response received");
}
auto *user_to_world_response = (UsertoWorldResponseLegacy_Struct *) packet.Data();
auto *r = (UsertoWorldResponseLegacy_Struct *) packet.Data();
LogDebug("Trying to find client with user id of [{0}]", user_to_world_response->lsaccountid);
Client *client = server.client_manager->GetClient(user_to_world_response->lsaccountid, "eqemu");
LogDebug("Trying to find client with user id of [{0}]", r->lsaccountid);
Client *client = server.client_manager->GetClient(r->lsaccountid, "eqemu");
if (client) {
LogDebug(
"Found client with user id of [{0}] and account name of [{1}]",
user_to_world_response->lsaccountid,
r->lsaccountid,
client->GetAccountName()
);
@ -217,9 +217,9 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne
auto *per = (PlayEverquestResponse_Struct *) outapp->pBuffer;
per->base_header.sequence = client->GetPlaySequence();
per->server_number = client->GetPlayServerID();
per->server_number = client->GetPlayServerID();
if (user_to_world_response->response > 0) {
if (r->response > 0) {
per->base_reply.success = true;
SendClientAuth(
client->GetConnection()->GetRemoteAddr(),
@ -230,27 +230,27 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne
);
}
switch (user_to_world_response->response) {
switch (r->response) {
case UserToWorldStatusSuccess:
per->base_reply.error_str_id = 101;
per->base_reply.error_str_id = LS::ErrStr::NO_ERROR;
break;
case UserToWorldStatusWorldUnavail:
per->base_reply.error_str_id = 326;
per->base_reply.error_str_id = LS::ErrStr::SERVER_UNAVAILABLE;
break;
case UserToWorldStatusSuspended:
per->base_reply.error_str_id = 337;
per->base_reply.error_str_id = LS::ErrStr::ACCOUNT_SUSPENDED;
break;
case UserToWorldStatusBanned:
per->base_reply.error_str_id = 338;
per->base_reply.error_str_id = LS::ErrStr::ACCOUNT_BANNED;
break;
case UserToWorldStatusWorldAtCapacity:
per->base_reply.error_str_id = 339;
per->base_reply.error_str_id = LS::ErrStr::WORLD_MAX_CAPACITY;
break;
case UserToWorldStatusAlreadyOnline:
per->base_reply.error_str_id = 111;
per->base_reply.error_str_id = LS::ErrStr::ERROR_1018_ACTIVE_CHARACTER;
break;
default:
per->base_reply.error_str_id = 102;
per->base_reply.error_str_id = LS::ErrStr::UNKNOWN_ERROR;
}
if (server.options.IsWorldTraceOn()) {
@ -275,7 +275,7 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne
else {
LogError(
"Received User-To-World Response for [{0}] but could not find the client referenced!",
user_to_world_response->lsaccountid
r->lsaccountid
);
}
}
@ -335,7 +335,7 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac
auto *per = (PlayEverquestResponse_Struct *) outapp->pBuffer;
per->base_header.sequence = client->GetPlaySequence();
per->server_number = client->GetPlayServerID();
per->server_number = client->GetPlayServerID();
LogDebug(
"Found sequence and play of [{0}] [{1}]",
@ -358,25 +358,25 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac
switch (user_to_world_response->response) {
case UserToWorldStatusSuccess:
per->base_reply.error_str_id = 101;
per->base_reply.error_str_id = LS::ErrStr::NO_ERROR;
break;
case UserToWorldStatusWorldUnavail:
per->base_reply.error_str_id = 326;
per->base_reply.error_str_id = LS::ErrStr::SERVER_UNAVAILABLE;
break;
case UserToWorldStatusSuspended:
per->base_reply.error_str_id = 337;
per->base_reply.error_str_id = LS::ErrStr::ACCOUNT_SUSPENDED;
break;
case UserToWorldStatusBanned:
per->base_reply.error_str_id = 338;
per->base_reply.error_str_id = LS::ErrStr::ACCOUNT_BANNED;
break;
case UserToWorldStatusWorldAtCapacity:
per->base_reply.error_str_id = 339;
per->base_reply.error_str_id = LS::ErrStr::WORLD_MAX_CAPACITY;
break;
case UserToWorldStatusAlreadyOnline:
per->base_reply.error_str_id = 111;
per->base_reply.error_str_id = LS::ErrStr::ERROR_1018_ACTIVE_CHARACTER;
break;
default:
per->base_reply.error_str_id = 102;
per->base_reply.error_str_id = LS::ErrStr::UNKNOWN_ERROR;
}
if (server.options.IsTraceOn()) {
@ -571,6 +571,12 @@ void WorldServer::Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info
GetServerLongName(),
GetRemoteIp()
);
WorldServer::FormatWorldServerName(
new_world_server_info_packet->server_long_name,
world_registration.server_list_type
);
SetLongName(new_world_server_info_packet->server_long_name);
}
/**
@ -1025,21 +1031,21 @@ bool WorldServer::ValidateWorldServerAdminLogin(
return false;
}
void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_local_ip) const
void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip) const
{
// see LoginClientServerData_Struct
if (use_local_ip) {
out.WriteString(GetLocalIP());
} else {
}
else {
out.WriteString(GetRemoteIP());
}
switch (GetServerListID())
{
case 1:
switch (GetServerListID()) {
case LS::ServerType::Legends:
out.WriteInt32(LS::ServerTypeFlags::Legends);
break;
case 2:
case LS::ServerType::Preferred:
out.WriteInt32(LS::ServerTypeFlags::Preferred);
break;
default:
@ -1348,3 +1354,37 @@ void WorldServer::OnKeepAlive(EQ::Timer *t)
ServerPacket pack(ServerOP_KeepAlive, 0);
m_connection->SendPacket(&pack);
}
void WorldServer::FormatWorldServerName(char *name, int8 server_list_type)
{
std::string server_long_name = name;
server_long_name = trim(server_long_name);
bool name_set_to_bottom = false;
if (server_list_type == LS::ServerType::Standard) {
if (server.options.IsWorldDevTestServersListBottom()) {
std::string s = str_tolower(server_long_name);
if (s.find("dev") != std::string::npos) {
server_long_name = fmt::format("|D| {}", server_long_name);
name_set_to_bottom = true;
}
else if (s.find("test") != std::string::npos) {
server_long_name = fmt::format("|T| {}", server_long_name);
name_set_to_bottom = true;
}
else if (s.find("installer") != std::string::npos) {
server_long_name = fmt::format("|I| {}", server_long_name);
name_set_to_bottom = true;
}
}
if (server.options.IsWorldSpecialCharacterStartListBottom() && !name_set_to_bottom) {
auto first_char = server_long_name.c_str()[0];
if (IsAllowedWorldServerCharacterList(first_char) && first_char != '|') {
server_long_name = fmt::format("|*| {}", server_long_name);
name_set_to_bottom = true;
}
}
}
strn0cpy(name, server_long_name.c_str(), 201);
}

View File

@ -194,6 +194,7 @@ private:
void OnKeepAlive(EQ::Timer *t);
std::unique_ptr<EQ::Timer> m_keepalive;
static void FormatWorldServerName(char *name, int8 server_list_type);
};
#endif

View File

@ -603,8 +603,6 @@ void LoginServer::SendInfo()
WorldConfig::SetLocalAddress(l->local_ip_address);
}
SanitizeWorldServerName(l->server_long_name);
LogInfo(
"[LoginServer::SendInfo] protocol_version [{}] server_version [{}] long_name [{}] short_name [{}] account_name [{}] remote_ip_address [{}] local_ip [{}]",
l->protocol_version,