/* * This file is part of the Haven & Hearth game client. * Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and * Björn Johannessen <johannessen.bjorn@gmail.com> * * Redistribution and/or modification of this file is subject to the * terms of the GNU Lesser General Public License, version 3, as * published by the Free Software Foundation. * * 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. * * Other parts of this source tree adhere to other copying * rights. Please see the file `COPYING' in the root directory of the * source tree for details. * * A copy the GNU Lesser General Public License is distributed along * with the source tree of which this file is a part in the file * `doc/LPGL-3'. If it is missing for any reason, please see the Free * Software Foundation's website at <http://www.fsf.org/>, or write * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ package haven; import java.io.*; import java.net.*; import java.security.MessageDigest; public class AuthClient { private static final SslHelper ssl; private Socket sk; private InputStream skin; private OutputStream skout; static { ssl = new SslHelper(); try { ssl.trust(ssl.loadX509(Resource.class.getResourceAsStream("authsrv.crt"))); } catch(Exception e) { throw(new RuntimeException(e)); } } public AuthClient(String host, int port) throws IOException { sk = ssl.connect(host, port); skin = sk.getInputStream(); skout = sk.getOutputStream(); } private static byte[] digest(byte[] pw) { MessageDigest dig; try { dig = MessageDigest.getInstance("SHA-256"); } catch(java.security.NoSuchAlgorithmException e) { throw(new RuntimeException(e)); } dig.update(pw); return(dig.digest()); } public String trypasswd(String user, byte[] phash) throws IOException { Message rpl = cmd("pw", user, phash); String stat = rpl.string(); if(stat.equals("ok")) { String acct = rpl.string(); return(acct); } else if(stat.equals("no")) { return(null); } else { throw(new RuntimeException("Unexpected reply `" + stat + "' from auth server")); } } public String trytoken(String user, byte[] token) throws IOException { Message rpl = cmd("token", user, token); String stat = rpl.string(); if(stat.equals("ok")) { String acct = rpl.string(); return(acct); } else if(stat.equals("no")) { return(null); } else { throw(new RuntimeException("Unexpected reply `" + stat + "' from auth server")); } } public byte[] getcookie() throws IOException { Message rpl = cmd("cookie"); String stat = rpl.string(); if(stat.equals("ok")) { return(rpl.bytes(32)); } else { throw(new RuntimeException("Unexpected reply `" + stat + "' from auth server")); } } public byte[] gettoken() throws IOException { Message rpl = cmd("mktoken"); String stat = rpl.string(); if(stat.equals("ok")) { return(rpl.bytes(32)); } else { throw(new RuntimeException("Unexpected reply `" + stat + "' from auth server")); } } public void close() throws IOException { sk.close(); } private void sendmsg(Message msg) throws IOException { if(msg.blob.length > 65535) throw(new RuntimeException("Too long message in AuthClient (" + msg.blob.length + " bytes)")); byte[] buf = new byte[msg.blob.length + 2]; buf[0] = (byte)((msg.blob.length & 0xff00) >> 8); buf[1] = (byte)(msg.blob.length & 0x00ff); System.arraycopy(msg.blob, 0, buf, 2, msg.blob.length); skout.write(buf); } private void esendmsg(Object... args) throws IOException { Message buf = new Message(0); for(Object arg : args) { if(arg instanceof String) { buf.addstring((String)arg); } else if(arg instanceof byte[]) { buf.addbytes((byte[])arg); } else { throw(new RuntimeException("Illegal argument to esendmsg: " + arg.getClass())); } } sendmsg(buf); } private static void readall(InputStream in, byte[] buf) throws IOException { int rv; for(int i = 0; i < buf.length; i += rv) { rv = in.read(buf, i, buf.length - i); if(rv < 0) throw(new IOException("Premature end of input")); } } private Message recvmsg() throws IOException { byte[] header = new byte[2]; readall(skin, header); int len = (Utils.ub(header[0]) << 8) | Utils.ub(header[1]); byte[] buf = new byte[len]; readall(skin, buf); return(new Message(0, buf)); } public Message cmd(Object... args) throws IOException { esendmsg(args); return(recvmsg()); } public static abstract class Credentials { public abstract String tryauth(AuthClient cl) throws IOException; public abstract String name(); public void discard() {} protected void finalize() { discard(); } public static class AuthException extends RuntimeException { public AuthException(String msg) { super(msg); } } } public static class NativeCred extends Credentials { public final String username; private byte[] phash; public NativeCred(String username, byte[] phash) { this.username = username; if((this.phash = phash).length != 32) throw(new IllegalArgumentException("Password hash must be 32 bytes")); } private static byte[] ohdearjava(String a) { try { return(digest(a.getBytes("utf-8"))); } catch(UnsupportedEncodingException e) { throw(new RuntimeException(e)); } } public NativeCred(String username, String pw) { this(username, ohdearjava(pw)); } public String name() { return(username); } public String tryauth(AuthClient cl) throws IOException { Message rpl = cl.cmd("pw", username, phash); String stat = rpl.string(); if(stat.equals("ok")) { String acct = rpl.string(); return(acct); } else if(stat.equals("no")) { String err = rpl.string(); throw(new AuthException(err)); } else { throw(new RuntimeException("Unexpected reply `" + stat + "' from auth server")); } } public void discard() { if(phash != null) { for(int i = 0; i < phash.length; i++) phash[i] = 0; phash = null; } } } public static class TokenCred extends Credentials implements Serializable { public final String acctname; public final byte[] token; public TokenCred(String acctname, byte[] token) { this.acctname = acctname; if((this.token = token).length != 32) throw(new IllegalArgumentException("Token must be 32 bytes")); } public String name() { throw(new UnsupportedOperationException()); } public String tryauth(AuthClient cl) throws IOException { Message rpl = cl.cmd("token", acctname, token); String stat = rpl.string(); if(stat.equals("ok")) { String acct = rpl.string(); return(acct); } else if(stat.equals("no")) { String err = rpl.string(); throw(new AuthException(err)); } else { throw(new RuntimeException("Unexpected reply `" + stat + "' from auth server")); } } } public static void main(final String[] args) throws Exception { Thread t = new HackThread(new Runnable() { public void run() { try { AuthClient test = new AuthClient("127.0.0.1", 1871); try { String acct = new NativeCred(args[0], args[1]).tryauth(test); if(acct == null) { System.err.println("failed"); return; } System.out.println(acct); System.out.println(Utils.byte2hex(test.getcookie())); } finally { test.close(); } } catch(Exception e) { throw(new RuntimeException(e)); } } }, "Test"); t.start(); t.join(); } }