blob: 6af2d2f72acd5c861ebd5eddfc0a6d3c1bdcb02a [file] [log] [blame] [raw]
/* Copyright 2015-2022 Rivoreo
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package rivoreo.minecraft.tsauth;
import org.bukkit.Server;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.util.logging.Logger;
import java.util.UUID;
public class MojangAuthenticatorWrapper {
public MojangAuthenticatorWrapper(Logger log, Server craftbukkit_server) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
this.log = log;
try {
Class<?> protocolsupport_minecraft_session_service_class =
Class.forName("protocolsupport.protocol.utils.authlib.MinecraftSessionService");
mojang_minecraft_session_service_has_joined_server_method =
protocolsupport_minecraft_session_service_class.getDeclaredMethod("hasJoinedServer", String.class, String.class);
} catch(Exception e) {
Class<?> craftbukkit_server_class = craftbukkit_server.getClass();
if(!craftbukkit_server_class.getSimpleName().equals("CraftServer")) {
throw new IllegalArgumentException("A CraftBukkit server class is required");
}
Method craftbukkit_server_get_server_method = craftbukkit_server_class.getDeclaredMethod("getServer");
Class<?> minecraft_server_class = craftbukkit_server_get_server_method.getReturnType();
Object minecraft_server = craftbukkit_server_get_server_method.invoke(craftbukkit_server);
for(Method m : minecraft_server_class.getDeclaredMethods()) {
Class<?> c = m.getReturnType();
String class_name = c.getName();
if(class_name.equals("com.mojang.authlib.minecraft.MinecraftSessionService") || class_name.equals("net.minecraft.util.com.mojang.authlib.minecraft.MinecraftSessionService")) {
mojang_minecraft_session_service = m.invoke(minecraft_server);
for(Method mm : c.getDeclaredMethods()) {
if(mm.getName().equals("hasJoinedServer")) {
Class<?>[] arg_classes = mm.getParameterTypes();
if((arg_classes.length == 2 || arg_classes.length == 3) && arg_classes[0].equals(mm.getReturnType()) && arg_classes[1].equals(String.class)) {
mojang_minecraft_session_service_has_joined_server_method = mm;
mojang_minecraft_session_service_has_joined_server_method_has_ip_address_argument = arg_classes.length > 2;
mojang_gameprofile_class = arg_classes[0];
mojang_gameprofile_class_constructor = mojang_gameprofile_class.getDeclaredConstructor(UUID.class, String.class);
AUTH_UNAVAIL_MAX_TRIES = 6;
return;
}
}
}
throw new NoSuchMethodException("Cannot find hasJoinedServer method from MinecraftSessionService");
}
}
throw new NoSuchMethodException("Cannot find method for MinecraftSessionService from MinecraftServer");
}
AUTH_UNAVAIL_MAX_TRIES = 4;
}
private final int AUTH_UNAVAIL_MAX_TRIES;
private Logger log;
private Method mojang_minecraft_session_service_has_joined_server_method;
private boolean mojang_minecraft_session_service_has_joined_server_method_has_ip_address_argument;
private Object mojang_minecraft_session_service;
private Class<?> mojang_gameprofile_class;
private Constructor<?> mojang_gameprofile_class_constructor;
private Field minecraft_server_login_handler_gameprofile_field;
private Object minecraft_server_login_handler;
public void set_minecraft_server_login_handler_from_network_manager(Object nm) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Class<?> nm_class = nm.getClass();
Method get_handler_method;
try {
get_handler_method = nm_class.getDeclaredMethod("func_150729_e");
} catch(NoSuchMethodException e) {
try {
get_handler_method = nm_class.getDeclaredMethod("getPacketListener");
} catch(NoSuchMethodException ee) {
get_handler_method = null;
for(Method m : nm_class.getDeclaredMethods()) {
if(m.getReturnType().getSimpleName().equals("PacketListener")) {
get_handler_method = m;
break;
}
}
if(get_handler_method == null) throw ee;
}
}
Object handler = get_handler_method.invoke(nm);
Class<?> handler_class = handler.getClass();
if(handler_class.getName().equals("protocolsupport.zplatform.impl.spigot.network.handler.SpigotLoginListenerPlay")) {
Class<?> abstract_login_listener = handler_class.getSuperclass();
if(!abstract_login_listener.getName().equals("protocolsupport.protocol.packet.handler.AbstractLoginListenerPlay")) {
throw new IllegalStateException("protocolsupport.protocol.packet.handler.AbstractLoginListenerPlay isn't the superclass of protocolsupport.zplatform.impl.spigot.network.handler.SpigotLoginListenerPlay, but instead " + abstract_login_listener.getName());
}
minecraft_server_login_handler = handler;
minecraft_server_login_handler_gameprofile_field = abstract_login_listener.getDeclaredField("profile");
minecraft_server_login_handler_gameprofile_field.setAccessible(true);
return;
}
String handler_class_simple_name = handler_class.getSimpleName();
if(!handler_class_simple_name.equals("NetHandlerLoginServer") && !handler_class_simple_name.equals("LoginListener")) {
throw new IllegalStateException("Minecraft network manager doesn't have login handler installed");
}
minecraft_server_login_handler = handler;
for(Field f : handler_class.getDeclaredFields()) {
if(f.getType().equals(mojang_gameprofile_class)) {
f.setAccessible(true);
minecraft_server_login_handler_gameprofile_field = f;
return;
}
}
throw new NoSuchFieldException("Cannot find field with type GameProfile from " + handler_class_simple_name);
}
public boolean authenticate(String user_name, String server_session_hash, InetAddress remote_address) throws IllegalAccessException, InstantiationException, InvocationTargetException {
int tries = 0;
Object game_profile;
Object[] args;
if(mojang_gameprofile_class == null) {
// ProtocolSupport implementation
args = new Object[] { user_name, server_session_hash };
} else {
game_profile = mojang_gameprofile_class_constructor.newInstance(null, user_name);
args = mojang_minecraft_session_service_has_joined_server_method_has_ip_address_argument ?
new Object[] { game_profile, server_session_hash, remote_address } :
new Object[] { game_profile, server_session_hash };
}
while(true) try {
game_profile = mojang_minecraft_session_service_has_joined_server_method.invoke(mojang_minecraft_session_service, args);
break;
} catch(InvocationTargetException e) {
Throwable cause = e.getCause();
if(cause == null) throw e;
if(!cause.getClass().getSimpleName().equals("AuthenticationUnavailableException")) throw e;
if(++tries > AUTH_UNAVAIL_MAX_TRIES) throw e;
Throwable deeper_cause = cause.getCause();
log.warning(String.format("Mojang authentication service temporarily unavailable: %s: %s; retrying (%d)",
cause.getMessage(), deeper_cause == null ? "Unknown error" : deeper_cause.getMessage(), tries));
}
if(game_profile != null && minecraft_server_login_handler_gameprofile_field != null) {
minecraft_server_login_handler_gameprofile_field.set(minecraft_server_login_handler, game_profile);
}
return game_profile != null;
}
}