Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4120,3 +4120,10 @@ When setting a property like MORE to the a spell or skill defname, trying to rea
- Fixed: Hit chance to paralyzed targets with sphere custom (0) formula is now 80-100% (was 0-10%).
- Added: Implemented Hit chance increase / decrease to sphere custom formula (0).
- Fixed: Equiping armor and weapon with spelleffect could crash the SphereServer (#1538)

24-03-2026, Mulambo
- Fixed: issue when web client sent a web identity packet and Sphere didn't let him log in
- Added: Classic UO Web Identity packet validation support (#1357): 2 new sphere.ini settings: `WebIdentity` and `WebIdentityForce`:
- If you add `WebIdentity` value from your Classic UO Web Management, received WebIdentity will be checked and validated
- If you add `WebIdentity` and set `WebIdentityForce` to 1, it will prohibit any client without web identity to log in
- If you don't add `WebIdentity`, behaviour will stay as it was
14 changes: 12 additions & 2 deletions src/game/CServerConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,8 @@ enum RC_TYPE
RC_VERSION,
RC_WALKBUFFER,
RC_WALKREGEN,
RC_WEBIDENTITY,
RC_WEBIDENTITYFORCE,
RC_WOOLGROWTHTIME, // m_iWoolGrowthTime
RC_WOPCOLOR,
RC_WOPFONT,
Expand Down Expand Up @@ -1031,7 +1033,9 @@ const CAssocReg CServerConfig::sm_szLoadKeys[RC_QTY + 1]
{ "VERSION", { ELEM_VOID, 0 }},
{ "WALKBUFFER", { ELEM_INT, static_cast<uint>OFFSETOF(CServerConfig,m_iWalkBuffer) }},
{ "WALKREGEN", { ELEM_INT, static_cast<uint>OFFSETOF(CServerConfig,m_iWalkRegen) }},
{ "WOOLGROWTHTIME", { ELEM_INT, static_cast<uint>OFFSETOF(CServerConfig,m_iWoolGrowthTime) }},
{ "WEBIDENTITY", { ELEM_CSTRING, static_cast<uint>OFFSETOF(CServerConfig,m_sWebIdentity) }},
{ "WEBIDENTITYFORCE", { ELEM_BOOL, static_cast<uint>OFFSETOF(CServerConfig,m_sWebIdentityForce) }},
{ "WOOLGROWTHTIME", { ELEM_INT, static_cast<uint>OFFSETOF(CServerConfig,m_iWoolGrowthTime) }},
{ "WOPCOLOR", { ELEM_INT, static_cast<uint>OFFSETOF(CServerConfig,m_iWordsOfPowerColor) }},
{ "WOPFONT", { ELEM_INT, static_cast<uint>OFFSETOF(CServerConfig,m_iWordsOfPowerFont) }},
{ "WOPPLAYER", { ELEM_BOOL, static_cast<uint>OFFSETOF(CServerConfig,m_fWordsOfPowerPlayer) }},
Expand Down Expand Up @@ -1518,7 +1522,13 @@ bool CServerConfig::r_LoadVal( CScript &s )
case RC_WALKBUFFER:
m_iWalkBuffer = s.GetArgVal() * MSECS_PER_TENTH;
break;
case RC_MEDITATIONMOVEMENTABORT:
case RC_WEBIDENTITY:
m_sWebIdentity = s.GetArgStr();
break;
case RC_WEBIDENTITYFORCE:
m_sWebIdentityForce = s.GetArgVal() > 0;
break;
case RC_MEDITATIONMOVEMENTABORT:
_fMeditationMovementAbort = s.GetArgVal() > 0 ? true : false;
break;
default:
Expand Down
5 changes: 5 additions & 0 deletions src/game/CServerConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ extern class CServerConfig : public CResourceHolder
bool m_fCUOStatus; // Enable or disable the response to ConnectUO pings
bool m_fUOGStatus; // Enable or disable the response to UOGateway pings

// Web identity secret from Classic UO Web Shard management.
CSString m_sWebIdentity = {false};
// Force only clients with web identity.
bool m_sWebIdentityForce;

int64 m_iWalkBuffer; // Walk limiting code: buffer size (in tenths of second).
int m_iWalkRegen; // Walk limiting code: regen speed (%)

Expand Down
12 changes: 12 additions & 0 deletions src/game/clients/CClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ class CClient : public CSObjListRec, public CScriptObj, public CChatChanMember,
int64 m_timeLastEventWalk; // Last time we got a walk event from client
int64 m_timeNextEventWalk; // Fastwalk prevention: only allow more walk requests after this timer

// Web Identity.
struct CWebIdentity
{
bool m_fReceived{false};
CSString m_sUserId;
CSString m_sConnectingIp;
CSString m_sExternalAuthProvider;
CSString m_sExternalAuthUsername;
CSString m_sExternalAuthId;
CSString m_sRole;
} m_webIdentity;

// Context of the targetting setup. depends on CLIMODE_TYPE m_Targ_Mode
union
{
Expand Down
8 changes: 7 additions & 1 deletion src/game/clients/CClientLog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ bool CClient::addLoginErr(byte code)
"Timeout / Wrong encryption / Unknown error",
"Invalid client version. See the CLIENTVERSION setting in " SPHERE_FILE ".ini",
"Invalid character selected (chosen character does not exist)",
"AuthID is not correct. This normally means that the client did not log in via the login server",
"AuthID is not correct. This means that the client did not log in via the login server or Web Identity secret doesn't match",
"The account details entered are invalid (username or password is too short, too long or contains invalid characters). This can sometimes be caused by incorrect/missing encryption keys",
"The account details entered are invalid (username or password is too short, too long or contains invalid characters). This can sometimes be caused by incorrect/missing encryption keys",
"Encryption error: packet length does not match what was expected",
Expand Down Expand Up @@ -848,6 +848,12 @@ bool CClient::xProcessClientSetup( CEvent * pEvent, uint uiLen )
ASSERT( pEvent != nullptr );
ASSERT( uiLen > 0 );

// Web identity packet. Validation is handled in packet itself.
if (pEvent->Default.m_Cmd == XCMD_Spy && uiLen == 149)
{
return true;
}

// Try all client versions on the msg.
if ( !m_Crypt.Init( m_net->m_seed, pEvent->m_Raw, uiLen, GetNetState()->isClientKR() ) )
{
Expand Down
4 changes: 2 additions & 2 deletions src/network/CNetworkInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,10 @@ bool CNetworkInput::processUnknownClientData(CNetState* state, Packet* buffer)
fHTTPReq = (uiOrigRemainingLength >= 5 && memcmp(pOrigRemainingData, "GET /", 5) == 0) ||
(uiOrigRemainingLength >= 6 && memcmp(pOrigRemainingData, "POST /", 6) == 0);
}
if (!fHTTPReq && (uiOrigRemainingLength > INT8_MAX))
if (!fHTTPReq && (uiOrigRemainingLength > UINT8_MAX))
{
g_Log.EventWarn("%x:Client connected with a seed length of %u exceeding max length limit of %d, disconnecting.\n",
state->id(), uiOrigRemainingLength, INT8_MAX);
state->id(), uiOrigRemainingLength, UINT8_MAX);
return false;
}

Expand Down
95 changes: 91 additions & 4 deletions src/network/receive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1943,6 +1943,20 @@ bool PacketServerSelect::onReceive(CNetState* net)

uint server = readInt16();

// Web Identity validation.
if (g_Cfg.m_sWebIdentity.IsValid() && g_Cfg.m_sWebIdentityForce)
{
CClient* client = net->getClient();
ASSERT(client);

// We did not receive validation packet (0xa4). The packet itself is validated elsewhere.
if (!client->m_webIdentity.m_fReceived)
{
client->addLoginErr(PacketLoginError::BadAuthID);
return false;
}
}

net->getClient()->Login_Relay(server);
return true;
}
Expand All @@ -1962,12 +1976,85 @@ PacketSystemInfo::PacketSystemInfo() : Packet(149)
bool PacketSystemInfo::onReceive(CNetState* net)
{
ADDTOCALLSTACK("PacketSystemInfo::onReceive");
UnreferencedParameter(net);

skip(148);
return true;
}
// Not using web identity.
if (!g_Cfg.m_sWebIdentity.IsValid())
{
UnreferencedParameter(net);

skip(148);
return true;
}

// Web identity check.
char clientType[7] = {};
readStringASCII(clientType, 6, false);
const uint8 version = readByte();

// Type or version doesn't match, might not be Web Identity.
if (strcmp(clientType, "CUOWEB") != 0 || version != 1) {
skip(141);

return true;
}

// We are using web identity, and we passed the version check. Assign data we received from it.
CClient* client = net->getClient();
ASSERT(client);

// Skip timestamp.
skip(4);

// Buffer for reading data from packet.
char dataBuffer[30];

// Remaining length of Web Identity packet (so we don't eat bytes from another packet).
int length = 137;

// Read the received secret and compare it to our secret.
length -= readStringNullASCII(dataBuffer, length);

// Secret is not valid.
if (strcmp(dataBuffer, g_Cfg.m_sWebIdentity) != 0)
{
skip(length);
client->addLoginErr(PacketLoginError::BadAuthID);
return false;
}

// Client is validated, we can now populate Web Identity data.
CClient::CWebIdentity &Identity = client->m_webIdentity;
Identity.m_fReceived = true;

// User ID.
length -= readStringNullASCII(dataBuffer, length);
Identity.m_sUserId = dataBuffer;

// Connecting IP.
length -= readStringNullASCII(dataBuffer, length);
Identity.m_sConnectingIp = dataBuffer;

// External Auth Provider.
length -= readStringNullASCII(dataBuffer, length);
Identity.m_sExternalAuthProvider = dataBuffer;

// External Auth Username.
length -= readStringNullASCII(dataBuffer, length);
Identity.m_sExternalAuthUsername = dataBuffer;

// External Auth ID.
length -= readStringNullASCII(dataBuffer, length);
Identity.m_sExternalAuthId = dataBuffer;

// Role.
length -= readStringNullASCII(dataBuffer, length);
Identity.m_sRole = dataBuffer;

// Skip the rest of the packet.
skip(length);

return true;
}

/***************************************************************************
*
Expand Down
6 changes: 6 additions & 0 deletions src/sphere.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,12 @@ CUOStatus=1
// If enabled, it returns: Name, Age, Clients, Items, Chars and Memory
UOGStatus=1

// Classic UO web identity.
// Enable by uncommenting and placing your generated secret in shard management.
//WebIdentity=YOUR_SHARD_SECRET
// Set to 1 to reject all clients without WebIdentity secret.
WebIdentityForce=0

// Add these Resource Sections to the defname list, so that they can be accessible via DEFLIST.*
[RESOURCELIST]
ITEMDEF
Expand Down
Loading