package org.dynmap; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; import java.util.Set; import org.dynmap.common.DynmapCommandSender; import org.dynmap.common.DynmapPlayer; import org.dynmap.servlet.LoginServlet; public class WebAuthManager { private HashMap pwdhash_by_userid = new HashMap(); private HashMap pending_registrations = new HashMap(); private String hashsalt; private File pfile; public static final String WEBAUTHFILE = "webauth.txt"; private static final String HASHSALT = "$HASH_SALT$"; private static final String PWDHASH_PREFIX = "hash."; private SecureRandom rnd = new SecureRandom(); private DynmapCore core; private String publicRegistrationURL; public WebAuthManager(DynmapCore core) { this.core = core; pfile = new File(core.getDataFolder(), WEBAUTHFILE); if(pfile.canRead()) { FileReader rf = null; try { rf = new FileReader(pfile); Properties p = new Properties(); p.load(rf); hashsalt = p.getProperty(HASHSALT); for(String k : p.stringPropertyNames()) { if(k.equals(HASHSALT)) { hashsalt = p.getProperty(k); } else if(k.startsWith(PWDHASH_PREFIX)) { /* Load password hashes */ pwdhash_by_userid.put(k.substring(PWDHASH_PREFIX.length()).toLowerCase(), p.getProperty(k)); } } } catch (IOException iox) { Log.severe("Cannot read " + WEBAUTHFILE); } finally { if(rf != null) { try { rf.close(); } catch (IOException iox) {} } } } if(hashsalt == null) { /* No hashsalt */ hashsalt = Long.toHexString(rnd.nextLong()); } } public boolean save() { boolean success = false; FileWriter fw = null; try { fw = new FileWriter(pfile); Properties p = new Properties(); p.setProperty(HASHSALT, hashsalt); /* Save salt */ for(String k : pwdhash_by_userid.keySet()) { p.setProperty(PWDHASH_PREFIX + k, pwdhash_by_userid.get(k)); } p.store(fw, "DO NOT EDIT THIS FILE"); success = true; } catch (IOException iox) { Log.severe("Error writing " + WEBAUTHFILE); } finally { if(fw != null) { try { fw.close(); } catch (IOException iox) {} } } if(success) core.events.trigger("loginupdated", null); return success; } private String makeHash(String pwd) { String check = hashsalt + pwd; try { byte[] checkbytes = check.getBytes("UTF-8"); MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] rslt = md.digest(checkbytes); String rslthash = ""; for(int i = 0; i < rslt.length; i++) { rslthash += String.format("%02X", 0xFF & (int)rslt[i]); } return rslthash; } catch (NoSuchAlgorithmException nsax) { } catch (UnsupportedEncodingException uex) { } return null; } public boolean checkLogin(String uid, String pwd) { uid = uid.toLowerCase(); if(uid.equals(LoginServlet.USERID_GUEST)) { return true; } String hash = pwdhash_by_userid.get(uid); if(hash == null) { return false; } if(core.getServer().isPlayerBanned(uid)) { return false; } String checkhash = makeHash(pwd); return hash.equals(checkhash); } public boolean registerLogin(String uid, String pwd, String passcode) { uid = uid.toLowerCase(); if(uid.equals(LoginServlet.USERID_GUEST)) { return false; } if(core.getServer().isPlayerBanned(uid)) { return false; } passcode = passcode.toLowerCase(); String kcode = pending_registrations.remove(uid); if(kcode == null) { return false; } if(!kcode.equals(passcode)) { return false; } String hash = makeHash(pwd); pwdhash_by_userid.put(uid, hash); return save(); } public boolean unregisterLogin(String uid) { if(uid.equals(LoginServlet.USERID_GUEST)) { return true; } uid = uid.toLowerCase(); pwdhash_by_userid.remove(uid); return save(); } public boolean isRegistered(String uid) { if(uid.equals(LoginServlet.USERID_GUEST)) { return false; } uid = uid.toLowerCase(); return pwdhash_by_userid.containsKey(uid); } boolean processCompletedRegister(String uid, String pc, String hash) { uid = uid.toLowerCase(); if(uid.equals(LoginServlet.USERID_GUEST)) { return false; } if(core.getServer().isPlayerBanned(uid)) { return false; } String kcode = pending_registrations.remove(uid); if(kcode == null) { return false; } pc = pc.toLowerCase(); if(!kcode.equals(pc)) { return false; } pwdhash_by_userid.put(uid, hash); return save(); } public static final boolean checkUserName(String name) { int nlen = name.length(); if ((nlen > 0) && (nlen <= 16)) { for (int i = 0; i < nlen; i++) { if ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".indexOf(name.charAt(i)) < 0) { return false; } } return true; } return false; } public boolean processWebRegisterCommand(DynmapCore core, DynmapCommandSender sender, DynmapPlayer player, String[] args) { String uid = null; boolean other = false; if(args.length > 1) { if(!core.checkPlayerPermission(sender, "webregister.other")) { sender.sendMessage("Not authorized to set web login information for other players"); return true; } uid = args[1]; other = true; } else if (player == null) { /* Console? */ sender.sendMessage("Must provide user ID to register web login"); return true; } else { uid = player.getName(); } if (checkUserName(uid) == false) { sender.sendMessage("Invalid user ID"); return true; } String regkey = String.format("%04d-%04d", rnd.nextInt(10000), rnd.nextInt(10000)); pending_registrations.put(uid.toLowerCase(), regkey.toLowerCase()); sender.sendMessage("Registration pending for user ID: " + uid); sender.sendMessage("Registration code: " + regkey); publicRegistrationURL = core.configuration.getString("publicURL", "index.html"); sender.sendMessage("Enter ID and code on registration web page (" + publicRegistrationURL.toString() + ") to complete registration"); if(other) { DynmapPlayer p = core.getServer().getPlayer(uid); if(p != null) { p.sendMessage("The registration of your account for web access has been started."); p.sendMessage("To complete the process, access the Login page on the Dynmap map"); p.sendMessage("Registration code: " + regkey); p.sendMessage("The user ID must match your account ID, but the password should NOT be the same."); } } core.events.trigger("loginupdated", null); return true; } String getLoginPHP(boolean wrap) { StringBuilder sb = new StringBuilder(); if (wrap) { sb.append(" \'").append(esc(pwdhash_by_userid.get(uid))).append("\',\n"); } sb.append(");\n"); /* Create registration table */ sb.append("$pendingreg = array(\n"); for(String uid : pending_registrations.keySet()) { sb.append(" \'").append(esc(uid)).append("\' => \'").append(esc(pending_registrations.get(uid))).append("\',\n"); } sb.append(");\n"); if (wrap) { sb.append("?>\n"); } return sb.toString(); } public static String esc(String s) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < s.length(); i++) { char c = s.charAt(i); if(c == '\\') sb.append("\\\\"); else if(c == '\'') sb.append("\\\'"); else sb.append(c); } return sb.toString(); } String getAccessPHP(boolean wrap) { StringBuilder sb = new StringBuilder(); if (wrap) { sb.append(" mid = new ArrayList(); /* Create world access list */ sb.append("$worldaccess = array(\n"); for(DynmapWorld w : core.getMapManager().getWorlds()) { if(w.isProtected()) { String perm = "world." + w.getName(); sb.append(" \'").append(esc(w.getName())).append("\' => \'"); for(String uid : pwdhash_by_userid.keySet()) { if(core.getServer().checkPlayerPermission(uid, perm)) { sb.append("[").append(esc(uid)).append("]"); } } sb.append("\',\n"); } for(MapType mt : w.maps) { if(mt.isProtected()) { mid.add(w.getName() + "." + mt.getPrefix()); } } } sb.append(");\n"); /* Create map access list */ sb.append("$mapaccess = array(\n"); for(String id : mid) { String perm = "map." + id; sb.append(" \'").append(esc(id)).append("\' => \'"); for(String uid : pwdhash_by_userid.keySet()) { if(core.getServer().checkPlayerPermission(uid, perm)) { sb.append("[").append(esc(uid)).append("]"); } } sb.append("\',\n"); } sb.append(");\n"); HashSet cantseeall = new HashSet(); String perm = "playermarkers.seeall"; sb.append("$seeallmarkers = \'"); for(String uid : pwdhash_by_userid.keySet()) { if(core.getServer().checkPlayerPermission(uid, perm)) { sb.append("[").append(esc(uid)).append("]"); } else { cantseeall.add(uid); } } sb.append("\';\n"); /* Add visibility lists for each player that doesn't see everything */ sb.append("$playervisible = array(\n"); for(String id : cantseeall) { id = id.toLowerCase(); Set vis = core.getPlayersVisibleToPlayer(id); if((vis.size() == 1) && vis.contains(id)) continue; sb.append(" \'").append(esc(id)).append("\' => \'"); for(String uid : vis) { sb.append("[").append(esc(uid)).append("]"); } sb.append("\',\n"); } sb.append(");\n"); core.getDefaultMapStorage().addPaths(sb, core); if (wrap) { sb.append("?>\n"); } return sb.toString(); } static String getDisabledAccessPHP(DynmapCore core, boolean wrap) { StringBuilder sb = new StringBuilder(); if (wrap) { sb.append("\n"); } return sb.toString(); } boolean pendingRegisters() { return (pending_registrations.size() > 0); } Set getUserIDs() { HashSet lst = new HashSet(); lst.addAll(pwdhash_by_userid.keySet()); lst.addAll(pending_registrations.keySet()); return lst; } }