Cope with clients that politely close their connections (such as PlasmaClient) rather than resetting them (like UruExplorer). This is the least invasive way of solving the problem given the current code, however it is a pretty hacky solution and that code would probably better be reorganized somehow.
- patch Show Spoiler
- Code: Select all
Index: DrizzlePrp/src/shared/CryptoBytestream.java
===================================================================
--- DrizzlePrp/src/shared/CryptoBytestream.java (revision 2708)
+++ DrizzlePrp/src/shared/CryptoBytestream.java (working copy)
@@ -35,6 +35,10 @@
{
try{
int r = in.read();
+ if (r == -1) {
+ // this is not actually true - the connection was gracefully closed, not reset, but "connection reset" is what moulserver.ConnectionState.run() expects to detect a closed connection (because that's what UruExplorer does)
+ throw new java.net.SocketException("Connection reset");
+ }
return b.ByteToInt32(cipher.returnByte((byte)r));
}catch(IOException e){
throw new nested(e);
@@ -43,8 +47,12 @@
public byte readByte()
{
try{
- byte r = (byte)in.read();
- return cipher.returnByte(r);
+ int r = in.read();
+ if (r == -1) {
+ // this is not actually true - the connection was gracefully closed, not reset, but "connection reset" is what moulserver.ConnectionState.run() expects to detect a closed connection (because that's what UruExplorer does)
+ throw new java.net.SocketException("Connection reset");
+ }
+ return cipher.returnByte((byte)r);
}catch(IOException e){
throw new nested(e);
}
Support creation and authentication of e-mail-style user accounts (with challenge/response authentication) in addition to plain ones (with one-way password hash) (PlasmaClient/libHSPlasma only supported the former prior to 2010-10-27). Tested for both cases with both PlasmaClient and UruExplorer.
- patch Show Spoiler
- Code: Select all
Index: DrizzlePrp/src/moulserver/AuthServer.java
===================================================================
--- DrizzlePrp/src/moulserver/AuthServer.java (revision 2708)
+++ DrizzlePrp/src/moulserver/AuthServer.java (working copy)
@@ -233,16 +233,15 @@
//int b4 = c.readShort(); //0
//short b5 = c.readShort();
ClientRegisterReply reply = new ClientRegisterReply();
- reply.serverchallenge = 0; //should be random I guess :P
- //reply.serverchallenge = sock.rng.nextInt();
- //reply.serverchallenge = 0x8b835969;
+ reply.serverchallenge = RandomUtils.rng.nextInt();
+ cs.serverchallenge = reply.serverchallenge;
SendMsg(cs,reply);
}
//else if(msgId==AuthServer.kCli2Auth_AcctLoginRequest)
else if(klass==AcctLoginRequest.class)
{
- m.msg("AuthServer AcctLoginRequest");
AcctLoginRequest request = (AcctLoginRequest)msg;
+ m.msg("AuthServer AcctLoginRequest: " + request.accountName.toString());
Database.accountinfo user = manager.database.GetUser(request.accountName.toString());
AcctLoginReply reply = new AcctLoginReply();
reply.transId = request.transId;
@@ -255,10 +254,7 @@
}
else
{
- //byte[] expectedhash = shared.CryptHashes.GetHash(new byte[]{}, CryptHashes.Hashtype.sha1);
- byte[] givenhash = request.getProperPasswordHash(user.accountname);
- boolean isPasswordCorrect = b.isEqual(user.passwordhash, givenhash);
- if(!isPasswordCorrect)
+ if(!request.checkPassword(user.passwordhash, cs.serverchallenge))
{
reply.result = ENetError.kNetErrAuthenticationFailed;
reply.accountUUID = Guid.none();
@@ -1775,85 +1788,67 @@
authToken.writeAsUtf16Sized16(c);
OS.writeAsUtf16Sized16(c);
}
-
- public byte[] getProperPasswordHash(String username)
+
+ public static byte[] getStoredHash(String username, String password)
{
- if(!isUsernameEmailForm(username))
- {
+ if (isUsernameEmailForm(username)) {
+ byte[] pwbs = b.Utf16ToBytes(password);
+ byte[] unbs = b.Utf16ToBytes(username.toLowerCase());
+ byte[] both = new byte[pwbs.length+unbs.length];
+ b.CopyBytes(pwbs, both, 0);
+ b.CopyBytes(unbs, both, pwbs.length);
+ both[pwbs.length-1] = 0;
+ both[pwbs.length-2] = 0;
+ both[both.length-1] = 0;
+ both[both.length-2] = 0;
+ jonelo.jacksum.adapt.gnu.crypto.hash.Sha0 sha0 = new jonelo.jacksum.adapt.gnu.crypto.hash.Sha0();
+ sha0.update(both, 0, both.length);
+ return sha0.digest();
+ }
+ else {
+ return shared.CryptHashes.GetHash(b.StringToBytes(password), CryptHashes.Hashtype.sha1);
+ }
+ }
+
+ public static byte[] getTransmittedHash(String username, byte[] storedhash, int serverchallenge, int clientchallenge)
+ {
+ if (isUsernameEmailForm(username)) {
+ byte[] all = new byte[4+4+storedhash.length];
+ b.loadInt32IntoBytes(clientchallenge, all, 0);
+ b.loadInt32IntoBytes(serverchallenge, all, 4);
+ b.CopyBytes(storedhash, all, 8);
+ jonelo.jacksum.adapt.gnu.crypto.hash.Sha0 sha0 = new jonelo.jacksum.adapt.gnu.crypto.hash.Sha0();
+ sha0.update(all, 0, all.length);
+ return sha0.digest();
+ }
+ else {
//fix how Cyan produces it as b3 b2 b1 b0 b7 b6 b5 b4, etc.
byte[] r = new byte[20];
for(int i=0;i<5;i++)
{
for(int j=0;j<4;j++)
{
- r[i*4+j] = passwordHash[i*4+(3-j)];
+ r[i*4+j] = storedhash[i*4+(3-j)];
}
}
return r;
}
- else
- {
- return passwordHash;
- }
}
-
- private void setIntHash(byte[] properhash)
+ public static boolean isUsernameEmailForm(String username)
{
- byte[] r = new byte[20];
- for(int i=0;i<5;i++)
- {
- for(int j=0;j<4;j++)
- {
- r[i*4+j] = properhash[i*4+(3-j)];
- }
- }
- this.passwordHash = r;
- }
- public boolean isUsernameEmailForm(String username)
- {
return username.matches(".+\\@.+\\..+"); //x@x.x, correctly allowing weird things like a.b@c@b.a@d.
}
public void setPassword(String username, String password, int serverchallenge, int clientchallenge)
{
//type=1 when username is simple, like on Talcum, but when long one like email address on Moulagain, it uses the other form(type=2), and it's unreversed, bizarrely.
//after testing, I find that if it looks like x@x.x, then it uses the 2nd type. Other forms don't, including @x.x, x@x., and x@.x
- boolean isEmailForm = isUsernameEmailForm(username);
- //isEmailForm = true;
- if(!isEmailForm)
- {
- byte[] pwbytes = b.StringToBytes(password);
- byte[] hash = shared.CryptHashes.GetHash(pwbytes, CryptHashes.Hashtype.sha1);
-
- setIntHash(hash);
- }
- else
- {
- byte[] pwbs = b.Utf16ToBytes(password/*+(char)0*/);
- byte[] unbs = b.Utf16ToBytes(username.toLowerCase()/*+(char)0*/);
- byte[] both = new byte[pwbs.length+unbs.length];
- b.CopyBytes(pwbs, both, 0);
- b.CopyBytes(unbs, both, pwbs.length);
- both[pwbs.length-1] = 0;
- both[pwbs.length-2] = 0;
- both[both.length-1] = 0;
- both[both.length-2] = 0;
- jonelo.jacksum.adapt.gnu.crypto.hash.Sha0 sha0 = new jonelo.jacksum.adapt.gnu.crypto.hash.Sha0();
- sha0.update(both, 0, both.length);
- byte[] UnAndPwHash = sha0.digest();
-
- byte[] all = new byte[4+4+UnAndPwHash.length];
- b.loadInt32IntoBytes(clientchallenge, all, 0);
- b.loadInt32IntoBytes(serverchallenge, all, 4);
- b.CopyBytes(UnAndPwHash, all, 8);
- sha0.update(all, 0, all.length);
- byte[] hash = sha0.digest();
-
- this.passwordHash = hash;
- }
-
- //shared.CryptHashes.GetHash(null, CryptHashes.Hashtype.md5)
-
+ this.passwordHash = getTransmittedHash(username, getStoredHash(username, password), serverchallenge, clientchallenge);
}
+
+ public boolean checkPassword(byte[] storedhash, int serverchallenge)
+ {
+ return b.isEqual(passwordHash, getTransmittedHash(accountName.toString(), storedhash, serverchallenge, clientchallenge));
+ }
}
Index: DrizzlePrp/src/moulserver/Database.java
===================================================================
--- DrizzlePrp/src/moulserver/Database.java (revision 2708)
+++ DrizzlePrp/src/moulserver/Database.java (working copy)
@@ -321,8 +321,7 @@
public void AddUser(String username, String password)
{
- byte[] pwbytes = b.StringToBytes(password);
- byte[] hash = shared.CryptHashes.GetHash(pwbytes, CryptHashes.Hashtype.sha1);
+ byte[] hash = AuthServer.AcctLoginRequest.getStoredHash(username, password);
byte[] guid = Guid.newRandomPlayer();
int flags = 8; //normal
int billingtype = 1; //normal
Index: DrizzlePrp/src/moulserver/ConnectionState.java
===================================================================
--- DrizzlePrp/src/moulserver/ConnectionState.java (revision 2708)
+++ DrizzlePrp/src/moulserver/ConnectionState.java (working copy)
@@ -48,6 +48,7 @@
//server things
ChunkSendHandler chunksendhandler = new ChunkSendHandler(); //used for file and auth servers. They will each have a separate ConnectionState, so this is fine.
+ int serverchallenge = 0; // set at Cli2Auth_ClientRegisterRequest, read at Cli2Auth_AcctLoginRequest
Database.accountinfo account = null;
Integer playerId = null; //0 means in-game, but unset player. I.e. the StartUp Age.
Integer playerInfoIdx = null;