diff --git a/src/main/java/org/to2mbn/authlibinjector/AuthlibInjector.java b/src/main/java/org/to2mbn/authlibinjector/AuthlibInjector.java index fcb3994..9fb32ec 100644 --- a/src/main/java/org/to2mbn/authlibinjector/AuthlibInjector.java +++ b/src/main/java/org/to2mbn/authlibinjector/AuthlibInjector.java @@ -2,12 +2,16 @@ package org.to2mbn.authlibinjector; import static java.util.Optional.empty; import static java.util.Optional.of; +import static org.to2mbn.authlibinjector.util.IOUtils.readURL; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.text.MessageFormat; import java.util.Optional; import java.util.function.Consumer; import org.to2mbn.authlibinjector.transform.ClassTransformer; +import org.to2mbn.authlibinjector.transform.SkinWhitelistTransformUnit; +import org.to2mbn.authlibinjector.transform.YggdrasilApiTransformUnit; +import org.to2mbn.authlibinjector.transform.YggdrasilKeyTransformUnit; public final class AuthlibInjector { @@ -19,60 +23,82 @@ public final class AuthlibInjector { private AuthlibInjector() {} private static boolean booted = false; + private static boolean debug = "true".equals(System.getProperty("org.to2mbn.authlibinjector.debug")); - public static void log(String message, Object... args) { + public static void info(String message, Object... args) { System.err.println("[authlib-injector] " + MessageFormat.format(message, args)); } + public static void debug(String message, Object... args) { + if (debug) { + info(message, args); + } + } + public static void bootstrap(Consumer transformerRegistry) { if (booted) { - log("already booted, skipping"); + info("already booted, skipping"); return; } booted = true; - Optional optionalConfig = configure(); + Optional optionalConfig = configure(); if (!optionalConfig.isPresent()) { - log("no config is found, exiting"); + info("no config available"); return; } - InjectorConfig config = optionalConfig.get(); + transformerRegistry.accept(createTransformer(optionalConfig.get())); + } + + private static Optional configure() { + String apiRoot = System.getProperty("org.to2mbn.authlibinjector.config"); + if (apiRoot == null) return empty(); + info("api root: {0}", apiRoot); + + String metadataResponse = System.getProperty("org.to2mbn.authlibinjector.config.prefetched"); + + if (metadataResponse == null) { + info("fetching metadata"); + try { + metadataResponse = readURL(apiRoot); + } catch (IOException e) { + info("unable to fetch metadata: {0}", e); + return empty(); + } + + } else { + info("prefetched metadata detected"); + } + + debug("metadata: {0}", metadataResponse); + + YggdrasilConfiguration configuration; + try { + configuration = YggdrasilConfiguration.parse(apiRoot, metadataResponse); + } catch (IOException e) { + info("unable to parse metadata: {0}\n" + + "metadata to parse:\n" + + "{1}", + e, metadataResponse); + return empty(); + } + debug("parsed metadata: {0}", configuration); + return of(configuration); + } + + private static ClassTransformer createTransformer(YggdrasilConfiguration config) { ClassTransformer transformer = new ClassTransformer(); - - if (config.isDebug()) transformer.debug = true; - + transformer.debugSaveClass = debug; for (String ignore : nonTransformablePackages) transformer.ignores.add(ignore); - config.applyTransformers(transformer.units); - transformerRegistry.accept(transformer); - } + transformer.units.add(new YggdrasilApiTransformUnit(config.getApiRoot())); + transformer.units.add(new SkinWhitelistTransformUnit(config.getSkinDomains().toArray(new String[0]))); + config.getDecodedPublickey().ifPresent( + key -> transformer.units.add(new YggdrasilKeyTransformUnit(key.getEncoded()))); - private static Optional configure() { - String url = System.getProperty("org.to2mbn.authlibinjector.config"); - if (url == null) { - return empty(); - } - log("trying to config remotely: {0}", url); - - InjectorConfig config = new InjectorConfig(); - config.setDebug("true".equals(System.getProperty("org.to2mbn.authlibinjector.debug"))); - - RemoteConfiguration remoteConfig; - try { - remoteConfig = RemoteConfiguration.fetch(url); - } catch (IOException e) { - log("unable to configure remotely: {0}", e); - return empty(); - } - - if (config.isDebug()) { - log("fetched remote config: {0}", remoteConfig); - } - - remoteConfig.applyToInjectorConfig(config); - return of(config); + return transformer; } } diff --git a/src/main/java/org/to2mbn/authlibinjector/InjectorConfig.java b/src/main/java/org/to2mbn/authlibinjector/InjectorConfig.java deleted file mode 100644 index 2c385f7..0000000 --- a/src/main/java/org/to2mbn/authlibinjector/InjectorConfig.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.to2mbn.authlibinjector; - -import static org.to2mbn.authlibinjector.util.KeyUtils.decodePublicKey; -import java.util.List; -import org.to2mbn.authlibinjector.transform.SkinWhitelistTransformUnit; -import org.to2mbn.authlibinjector.transform.TransformUnit; -import org.to2mbn.authlibinjector.transform.YggdrasilApiTransformUnit; -import org.to2mbn.authlibinjector.transform.YggdrasilKeyTransformUnit; - -public class InjectorConfig { - - private String apiRoot; - private List skinWhitelistDomains; - private String publicKey; - private boolean debug; - - public String getApiRoot() { - return apiRoot; - } - - public void setApiRoot(String apiRoot) { - this.apiRoot = apiRoot; - } - - public List getSkinWhitelistDomains() { - return skinWhitelistDomains; - } - - public void setSkinWhitelistDomains(List skinWhitelistDomains) { - this.skinWhitelistDomains = skinWhitelistDomains; - } - - public String getPublicKey() { - return publicKey; - } - - public void setPublicKey(String publicKey) { - this.publicKey = publicKey; - } - - public boolean isDebug() { - return debug; - } - - public void setDebug(boolean debug) { - this.debug = debug; - } - - public void applyTransformers(List units) { - units.add(new YggdrasilApiTransformUnit(apiRoot)); - units.add(new SkinWhitelistTransformUnit(skinWhitelistDomains.toArray(new String[0]))); - if (publicKey != null) { - units.add(new YggdrasilKeyTransformUnit(decodePublicKey(publicKey))); - } - } -} diff --git a/src/main/java/org/to2mbn/authlibinjector/RemoteConfiguration.java b/src/main/java/org/to2mbn/authlibinjector/YggdrasilConfiguration.java similarity index 73% rename from src/main/java/org/to2mbn/authlibinjector/RemoteConfiguration.java rename to src/main/java/org/to2mbn/authlibinjector/YggdrasilConfiguration.java index 6cd364c..8dbd123 100644 --- a/src/main/java/org/to2mbn/authlibinjector/RemoteConfiguration.java +++ b/src/main/java/org/to2mbn/authlibinjector/YggdrasilConfiguration.java @@ -6,8 +6,6 @@ import static java.util.Objects.requireNonNull; import static java.util.Optional.empty; import static java.util.Optional.of; import static java.util.Optional.ofNullable; -import static org.to2mbn.authlibinjector.util.HttpRequester.http; -import static org.to2mbn.authlibinjector.util.IOUtils.asJson; import static org.to2mbn.authlibinjector.util.KeyUtils.decodePublicKey; import static org.to2mbn.authlibinjector.util.KeyUtils.loadX509PublicKey; import java.io.IOException; @@ -21,13 +19,13 @@ import java.util.TreeMap; import org.to2mbn.authlibinjector.internal.org.json.JSONException; import org.to2mbn.authlibinjector.internal.org.json.JSONObject; -public class RemoteConfiguration { +public class YggdrasilConfiguration { - public static RemoteConfiguration fetch(String apiRoot) throws IOException { + public static YggdrasilConfiguration parse(String apiRoot, String metadataResponse) throws IOException { if (!apiRoot.endsWith("/")) apiRoot += "/"; try { - JSONObject response = asJson(http.request("GET", apiRoot)); + JSONObject response = new JSONObject(metadataResponse); List skinDomains = new ArrayList<>(); ofNullable(response.optJSONArray("skinDomains")) @@ -54,7 +52,7 @@ public class RemoteConfiguration { .map(JSONObject::toMap) .ifPresent(it -> it.forEach((k, v) -> meta.put(k, String.valueOf(v)))); - return new RemoteConfiguration(apiRoot, unmodifiableList(skinDomains), signaturePublickey, unmodifiableMap(meta), decodedPublickey); + return new YggdrasilConfiguration(apiRoot, unmodifiableList(skinDomains), signaturePublickey, unmodifiableMap(meta), decodedPublickey); } catch (JSONException e) { throw new IOException("Invalid json", e); } @@ -66,7 +64,7 @@ public class RemoteConfiguration { private Optional decodedPublickey; private Map meta; - public RemoteConfiguration(String apiRoot, List skinDomains, Optional signaturePublickey, Map meta, Optional decodedPublickey) { + public YggdrasilConfiguration(String apiRoot, List skinDomains, Optional signaturePublickey, Map meta, Optional decodedPublickey) { this.apiRoot = requireNonNull(apiRoot); this.skinDomains = requireNonNull(skinDomains); this.signaturePublickey = requireNonNull(signaturePublickey); @@ -94,14 +92,8 @@ public class RemoteConfiguration { return decodedPublickey; } - public void applyToInjectorConfig(InjectorConfig config) { - config.setApiRoot(apiRoot); - config.setPublicKey(signaturePublickey.orElse(null)); - config.setSkinWhitelistDomains(skinDomains); - } - @Override public String toString() { - return "RemoteConfiguration [apiRoot=" + apiRoot + ", skinDomains=" + skinDomains + ", signaturePublickey=" + signaturePublickey + ", meta=" + meta + "]"; + return "YggdrasilConfiguration [apiRoot=" + apiRoot + ", skinDomains=" + skinDomains + ", signaturePublickey=" + signaturePublickey + ", meta=" + meta + "]"; } } diff --git a/src/main/java/org/to2mbn/authlibinjector/javaagent/AuthlibInjectorPremain.java b/src/main/java/org/to2mbn/authlibinjector/javaagent/AuthlibInjectorPremain.java index fe3d98d..3154223 100644 --- a/src/main/java/org/to2mbn/authlibinjector/javaagent/AuthlibInjectorPremain.java +++ b/src/main/java/org/to2mbn/authlibinjector/javaagent/AuthlibInjectorPremain.java @@ -1,14 +1,14 @@ package org.to2mbn.authlibinjector.javaagent; import static org.to2mbn.authlibinjector.AuthlibInjector.bootstrap; -import static org.to2mbn.authlibinjector.AuthlibInjector.log; +import static org.to2mbn.authlibinjector.AuthlibInjector.info; import java.lang.instrument.Instrumentation; public class AuthlibInjectorPremain { public static void premain(String arg, Instrumentation instrumentation) { try { - log("launched from javaagent"); + info("launched from javaagent"); if (arg != null && !arg.isEmpty()) { System.setProperty("org.to2mbn.authlibinjector.config", arg); } diff --git a/src/main/java/org/to2mbn/authlibinjector/transform/ClassTransformer.java b/src/main/java/org/to2mbn/authlibinjector/transform/ClassTransformer.java index 389ddc6..75f37aa 100644 --- a/src/main/java/org/to2mbn/authlibinjector/transform/ClassTransformer.java +++ b/src/main/java/org/to2mbn/authlibinjector/transform/ClassTransformer.java @@ -1,6 +1,7 @@ package org.to2mbn.authlibinjector.transform; -import static org.to2mbn.authlibinjector.AuthlibInjector.log; +import static org.to2mbn.authlibinjector.AuthlibInjector.debug; +import static org.to2mbn.authlibinjector.AuthlibInjector.info; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; @@ -21,7 +22,7 @@ public class ClassTransformer implements ClassFileTransformer { public List units = new ArrayList<>(); public Set ignores = new HashSet<>(); - public boolean debug; + public boolean debugSaveClass; private static class TransformHandle { @@ -51,7 +52,7 @@ public class ClassTransformer implements ClassFileTransformer { ClassReader reader = new ClassReader(classBuffer); reader.accept(optionalVisitor.get(), 0); if (currentModified) { - log("transform {0} using {1}", className, unit); + info("transform {0} using {1}", className, unit); modified = true; classBuffer = writer.toByteArray(); } @@ -82,18 +83,16 @@ public class ClassTransformer implements ClassFileTransformer { units.forEach(handle::accept); if (handle.getResult().isPresent()) { byte[] classBuffer = handle.getResult().get(); - if (debug) { + if (debugSaveClass) { saveClassFile(className, classBuffer); } return classBuffer; } else { - if (debug) { - log("no transform performed on {0}", className); - } + debug("no transform performed on {0}", className); return null; } } catch (Throwable e) { - log("unable to transform {0}: {1}", internalClassName, e); + info("unable to transform {0}: {1}", internalClassName, e); e.printStackTrace(); } } @@ -104,7 +103,7 @@ public class ClassTransformer implements ClassFileTransformer { try { Files.write(Paths.get(className + "_dump.class"), classBuffer, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { - log("unable to dump class {0}: {1}", className, e); + info("unable to dump class {0}: {1}", className, e); e.printStackTrace(); } } diff --git a/src/main/java/org/to2mbn/authlibinjector/transform/LdcTransformUnit.java b/src/main/java/org/to2mbn/authlibinjector/transform/LdcTransformUnit.java index 83bca53..986c330 100644 --- a/src/main/java/org/to2mbn/authlibinjector/transform/LdcTransformUnit.java +++ b/src/main/java/org/to2mbn/authlibinjector/transform/LdcTransformUnit.java @@ -1,7 +1,7 @@ package org.to2mbn.authlibinjector.transform; import static org.objectweb.asm.Opcodes.ASM6; -import static org.to2mbn.authlibinjector.AuthlibInjector.log; +import static org.to2mbn.authlibinjector.AuthlibInjector.info; import java.util.Optional; import java.util.function.Function; import org.objectweb.asm.ClassVisitor; @@ -29,7 +29,7 @@ public class LdcTransformUnit implements TransformUnit { Optional transformed = ldcMapper.apply((String) cst); if (transformed.isPresent() && !transformed.get().equals(cst)) { modifiedCallback.run(); - log("transform [{0}] to [{1}]", cst, transformed.get()); + info("transform [{0}] to [{1}]", cst, transformed.get()); super.visitLdcInsn(transformed.get()); } else { super.visitLdcInsn(cst); diff --git a/src/main/java/org/to2mbn/authlibinjector/util/HttpRequester.java b/src/main/java/org/to2mbn/authlibinjector/util/HttpRequester.java deleted file mode 100644 index 666e7f0..0000000 --- a/src/main/java/org/to2mbn/authlibinjector/util/HttpRequester.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.to2mbn.authlibinjector.util; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.to2mbn.authlibinjector.util.IOUtils.asString; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Map; - -public class HttpRequester { - - /** Common http requester */ - public static final HttpRequester http = new HttpRequester(); - - private int timeout = 15000; - - public int getTimeout() { - return timeout; - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public String request(String method, String url) throws IOException { - return request(method, url, null); - } - - public String request(String method, String url, Map headers) throws IOException { - HttpURLConnection conn = createConnection(url, headers); - conn.setRequestMethod(method); - try { - conn.connect(); - try (InputStream in = conn.getInputStream()) { - return asString(in); - } - } catch (IOException e) { - try (InputStream in = conn.getErrorStream()) { - return readErrorStream(in, e); - } - } finally { - conn.disconnect(); - } - } - - public String requestWithPayload(String method, String url, Object payload, String contentType) throws IOException { - return requestWithPayload(method, url, payload, contentType, null); - } - - public String requestWithPayload(String method, String url, Object payload, String contentType, Map headers) throws IOException { - byte[] bytePayload; - if (payload instanceof byte[]) { - bytePayload = (byte[]) payload; - } else if (payload == null) { - bytePayload = new byte[0]; - } else { - bytePayload = String.valueOf(payload).getBytes(UTF_8); - } - - HttpURLConnection conn = createConnection(url, headers); - conn.setRequestMethod(method); - conn.setRequestProperty("Content-Type", contentType); - conn.setRequestProperty("Content-Length", String.valueOf(bytePayload.length)); - conn.setDoOutput(true); - - try { - conn.connect(); - try (OutputStream out = conn.getOutputStream()) { - out.write(bytePayload); - } - try (InputStream in = conn.getInputStream()) { - return asString(in); - } - } catch (IOException e) { - try (InputStream in = conn.getErrorStream()) { - return readErrorStream(in, e); - } - } finally { - conn.disconnect(); - } - } - - private String readErrorStream(InputStream in, IOException e) throws IOException { - if (in == null) - throw e; - - try { - return asString(in); - } catch (IOException e1) { - if (e != e1) - e1.addSuppressed(e); - - throw e1; - } - } - - private HttpURLConnection createConnection(String url, Map headers) throws IOException { - HttpURLConnection conn = createConnection(new URL(url)); - if (headers != null) - headers.forEach((key, value) -> conn.setRequestProperty(key, value)); - return conn; - } - - private HttpURLConnection createConnection(URL url) throws IOException { - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(timeout); - conn.setReadTimeout(timeout); - conn.setUseCaches(false); - return conn; - } - -} diff --git a/src/main/java/org/to2mbn/authlibinjector/util/IOUtils.java b/src/main/java/org/to2mbn/authlibinjector/util/IOUtils.java index 0fc787c..c21178b 100644 --- a/src/main/java/org/to2mbn/authlibinjector/util/IOUtils.java +++ b/src/main/java/org/to2mbn/authlibinjector/util/IOUtils.java @@ -6,11 +6,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import org.to2mbn.authlibinjector.internal.org.json.JSONException; -import org.to2mbn.authlibinjector.internal.org.json.JSONObject; +import java.net.URL; public final class IOUtils { + public static String readURL(String url) throws IOException { + try (InputStream in = new URL(url).openStream()) { + return asString(in); + } + } + public static String asString(InputStream in) throws IOException { CharArrayWriter w = new CharArrayWriter(); Reader reader = new InputStreamReader(in, UTF_8); @@ -22,10 +27,6 @@ public final class IOUtils { return new String(w.toCharArray()); } - public static JSONObject asJson(String data) throws JSONException { - return new JSONObject(data); - } - private IOUtils() {} }