All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aoindustries.aoserv.master.SocketServerThread Maven / Gradle / Ivy

/*
 * aoserv-master - Master server for the AOServ Platform.
 * Copyright (C) 2001-2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020  AO Industries, Inc.
 *     [email protected]
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of aoserv-master.
 *
 * aoserv-master is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aoserv-master 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with aoserv-master.  If not, see .
 */
package com.aoindustries.aoserv.master;

import com.aoindustries.aoserv.client.master.UserHost;
import com.aoindustries.aoserv.client.schema.AoservProtocol;
import com.aoindustries.aoserv.master.master.Process;
import com.aoindustries.aoserv.master.master.Process_Manager;
import com.aoindustries.collections.IntArrayList;
import com.aoindustries.collections.IntList;
import com.aoindustries.dbc.DatabaseConnection;
import com.aoindustries.io.stream.StreamableInput;
import com.aoindustries.io.stream.StreamableOutput;
import com.aoindustries.lang.Strings;
import com.aoindustries.net.DomainName;
import com.aoindustries.net.InetAddress;
import com.aoindustries.security.Identifier;
import com.aoindustries.validation.ValidationException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;

/**
 * The AOServServerThread handles a connection once it is accepted.
 *
 * @author  AO Industries, Inc.
 */
final public class SocketServerThread extends Thread implements RequestSource {

	private static final Logger logger = Logger.getLogger(SocketServerThread.class.getName());

	/**
	 * The {@link TCPServer} that created this SocketServerThread.
	 */
	private final TCPServer server;

	/**
	 * The {@link Socket} that is connected.
	 */
	private final Socket socket;

	/**
	 * The {@link StreamableInput} that is being read from.
	 */
	private final StreamableInput in;

	/**
	 * The {@link StreamableOutput} that is being written to.
	 */
	private final StreamableOutput out;

	/**
	 * The version of the protocol the client is running.
	 */
	private AoservProtocol.Version protocolVersion;

	/**
	 * The server if this is a connection from a daemon.
	 */

	/**
	 * The master process.
	 */
	private final Process process;

	private boolean isClosed = true;

	/**
	 * Creates a new, running AOServServerThread.
	 */
	public SocketServerThread(TCPServer server, Socket socket) throws IOException, SQLException {
		try {
			this.server = server;
			this.socket = socket;
			this.in = new StreamableInput(new BufferedInputStream(socket.getInputStream()));
			this.out = new StreamableOutput(new BufferedOutputStream(socket.getOutputStream()));
			InetAddress host = InetAddress.valueOf(socket.getInetAddress().getHostAddress());
			process = Process_Manager.createProcess(
				host,
				server.getProtocol(),
				server.isSecure()
			);
			isClosed = false;
		} catch(ValidationException e) {
			throw new IOException(e.getLocalizedMessage(), e);
		}
	}

	private final LinkedList invalidateLists=new LinkedList<>();

	/**
	 * Invalidates the listed tables.  Also, if this connector represents a daemon,
	 * this invalidate is registered with ServerHandler for invalidation synchronization.
	 *
	 * IDEA: Could reduce signals under high load by combining entries that are not synchronous.
	 *       Could even combine synchronous ones as long as all sync entries were acknowledged in the proper order.
	 */
	@Override
	public void cachesInvalidated(IntList tableList) throws IOException {
		if(tableList!=null && tableList.size()>0) {
			synchronized(this) { // Must use "this" lock because wait is performed on this object externally
				// Register with ServerHandler for invalidation synchronization
				int daemonServer=getDaemonServer();
				IntList copy=new IntArrayList(tableList);
				InvalidateCacheEntry ice=new InvalidateCacheEntry(
					copy,
					daemonServer,
					daemonServer==-1?null:NetHostHandler.addInvalidateSyncEntry(daemonServer, this)
				);
				invalidateLists.addLast(ice);
				notify();
			}
		}
	}

	@Override
	public int getDaemonServer() {
		return process.getDaemonServer();
	}

	@Override
	public InvalidateCacheEntry getNextInvalidatedTables() {
		synchronized(this) {
			if(invalidateLists.isEmpty()) return null;
			return invalidateLists.removeFirst();
		}
	}

	@Override
	public Identifier getConnectorId() {
		return process.getConnectorId();
	}

	@Override
	public AoservProtocol.Version getProtocolVersion() {
		return protocolVersion;
	}

	/**
	 * Logs a security message to System.err.
	 * Also sends email messages to aoserv.server.
	 */
	@Override
	public String getSecurityMessageHeader() {
		return "IP="+socket.getInetAddress().getHostAddress()+" EffUsr="+process.getEffectiveAdministrator_username()+" AuthUsr="+process.getAuthenticatedAdministrator_username();
	}

	@Override
	public com.aoindustries.aoserv.client.account.User.Name getCurrentAdministrator() {
		return process.getEffectiveAdministrator_username();
	}

	@Override
	public boolean isSecure() throws UnknownHostException {
		return server.isSecure();
	}

	@Override
	public void run() {
		try {
			try {
				this.protocolVersion=AoservProtocol.Version.getVersion(in.readUTF());
				process.setAOServProtocol(protocolVersion.getVersion());
				if(in.readBoolean()) {
					DomainName daemonServer;
					try {
						daemonServer = DomainName.valueOf(in.readUTF());
					} catch(ValidationException e) {
						out.writeBoolean(false);
						out.writeUTF(e.getLocalizedMessage());
						out.flush();
						return;
					}
					DatabaseConnection conn=MasterDatabase.getDatabase().createDatabaseConnection();
					try {
						process.setDeamonServer(NetHostHandler.getHostForLinuxServerHostname(conn, daemonServer));
					} catch(RuntimeException | IOException err) {
						conn.rollback();
						throw err;
					} catch(SQLException err) {
						conn.rollbackAndClose();
						throw err;
					} finally {
						conn.releaseConnection();
					}
				} else {
					process.setDeamonServer(-1);
				}
				try {
					process.setEffectiveUser(com.aoindustries.aoserv.client.account.User.Name.valueOf(in.readUTF()));
					process.setAuthenticatedUser(com.aoindustries.aoserv.client.account.User.Name.valueOf(in.readUTF()));
				} catch(ValidationException e) {
					out.writeBoolean(false);
					out.writeUTF(e.getLocalizedMessage());
					out.flush();
					return;
				}
				String host=socket.getInetAddress().getHostAddress();
				setName(
					"SocketServerThread"
					+ "?"
					+ host
					+ ":"
					+ socket.getPort()
					+ "->"
					+ socket.getLocalAddress().getHostAddress()
					+ ":"
					+ socket.getLocalPort()
					+ " as "
					+ process.getEffectiveAdministrator_username()
					+ " ("
					+ process.getAuthenticatedAdministrator_username()
					+ ")"
				);
				String password = in.readUTF();
				Identifier existingId;
				if(protocolVersion.compareTo(AoservProtocol.Version.VERSION_1_83_0) < 0) {
					long existingIdLong = in.readLong();
					existingId = existingIdLong == -1 ? null : new Identifier(0, existingIdLong);
				} else {
					existingId = in.readNullIdentifier();
				}

				switch(protocolVersion) {
					case VERSION_1_83_2 :
					case VERSION_1_83_1 :
					case VERSION_1_83_0 :
					case VERSION_1_82_1 :
					case VERSION_1_82_0 :
					case VERSION_1_81_22 :
					case VERSION_1_81_21 :
					case VERSION_1_81_20 :
					case VERSION_1_81_19 :
					case VERSION_1_81_18 :
					case VERSION_1_81_17 :
					case VERSION_1_81_16 :
					case VERSION_1_81_15 :
					case VERSION_1_81_14 :
					case VERSION_1_81_13 :
					case VERSION_1_81_12 :
					case VERSION_1_81_11 :
					case VERSION_1_81_10 :
					case VERSION_1_81_9 :
					case VERSION_1_81_8 :
					case VERSION_1_81_7 :
					case VERSION_1_81_6 :
					case VERSION_1_81_5 :
					case VERSION_1_81_4 :
					case VERSION_1_81_3 :
					case VERSION_1_81_2 :
					case VERSION_1_81_1 :
					case VERSION_1_81_0 :
					case VERSION_1_80_2 :
					case VERSION_1_80_1 :
					case VERSION_1_80_0 :
					case VERSION_1_80 :
					case VERSION_1_79 :
					case VERSION_1_78 :
					case VERSION_1_77 :
					case VERSION_1_76 :
					case VERSION_1_75 :
					case VERSION_1_74 :
					case VERSION_1_73 :
					case VERSION_1_72 :
					case VERSION_1_71 :
					case VERSION_1_70 :
					case VERSION_1_69 :
					case VERSION_1_68 :
					case VERSION_1_67 :
					case VERSION_1_66 :
					case VERSION_1_65 :
					case VERSION_1_64 :
					case VERSION_1_63 :
					case VERSION_1_62 :
					case VERSION_1_61 :
					case VERSION_1_60 :
					case VERSION_1_59 :
					case VERSION_1_58 :
					case VERSION_1_57 :
					case VERSION_1_56 :
					case VERSION_1_55 :
					case VERSION_1_54 :
					case VERSION_1_53 :
					case VERSION_1_52 :
					case VERSION_1_51 :
					case VERSION_1_50 :
					case VERSION_1_49 :
					case VERSION_1_48 :
					case VERSION_1_47 :
					case VERSION_1_46 :
					case VERSION_1_45 :
					case VERSION_1_44 :
					case VERSION_1_43 :
					case VERSION_1_42 :
					case VERSION_1_41 :
					case VERSION_1_40 :
					case VERSION_1_39 :
					case VERSION_1_38 :
					case VERSION_1_37 :
					case VERSION_1_36 :
					case VERSION_1_35 :
					case VERSION_1_34 :
					case VERSION_1_33 :
					case VERSION_1_32 :
					case VERSION_1_31 :
					case VERSION_1_30 :
					case VERSION_1_29 :
					case VERSION_1_28 :
					case VERSION_1_27 :
					case VERSION_1_26 :
					case VERSION_1_25 :
					case VERSION_1_24 :
					case VERSION_1_23 :
					case VERSION_1_22 :
					case VERSION_1_21 :
					case VERSION_1_20 :
					case VERSION_1_19 :
					case VERSION_1_18 :
					case VERSION_1_17 :
					case VERSION_1_16 :
					case VERSION_1_15 :
					case VERSION_1_14 :
					case VERSION_1_13 :
					case VERSION_1_12 :
					case VERSION_1_11 :
					case VERSION_1_10 :
					case VERSION_1_9 :
					case VERSION_1_8 :
					case VERSION_1_7 :
					case VERSION_1_6 :
					case VERSION_1_5 :
					case VERSION_1_4 :
					case VERSION_1_3 :
					case VERSION_1_2 :
					case VERSION_1_1 :
					case VERSION_1_0_A_130 :
					case VERSION_1_0_A_129 :
					case VERSION_1_0_A_128 :
					case VERSION_1_0_A_127 :
					case VERSION_1_0_A_126 :
					case VERSION_1_0_A_125 :
					case VERSION_1_0_A_124 :
					case VERSION_1_0_A_123 :
					case VERSION_1_0_A_122 :
					case VERSION_1_0_A_121 :
					case VERSION_1_0_A_120 :
					case VERSION_1_0_A_119 :
					case VERSION_1_0_A_118 :
					case VERSION_1_0_A_117 :
					case VERSION_1_0_A_116 :
					case VERSION_1_0_A_115 :
					case VERSION_1_0_A_114 :
					case VERSION_1_0_A_113 :
					case VERSION_1_0_A_112 :
					case VERSION_1_0_A_111 :
					case VERSION_1_0_A_110 :
					case VERSION_1_0_A_109 :
					case VERSION_1_0_A_108 :
					case VERSION_1_0_A_107 :
					case VERSION_1_0_A_106 :
					case VERSION_1_0_A_105 :
					case VERSION_1_0_A_104 :
					case VERSION_1_0_A_103 :
					case VERSION_1_0_A_102 :
					case VERSION_1_0_A_101 :
					case VERSION_1_0_A_100 :
					{
						String message;
						DatabaseConnection conn=MasterDatabase.getDatabase().createDatabaseConnection();
						try {
							try {
								message = MasterServer.authenticate(
									conn,
									socket.getInetAddress().getHostAddress(),
									process.getEffectiveAdministrator_username(),
									process.getAuthenticatedAdministrator_username(),
									password
								);
							} catch(RuntimeException | IOException err) {
								conn.rollback();
								throw err;
							} catch(SQLException err) {
								conn.rollbackAndClose();
								throw err;
							} finally {
								conn.releaseConnection();
							}

							if(message!=null) {
								//UserHost.reportSecurityMessage(this, message, process.getEffectiveUser().length()>0 && password.length()>0);
								out.writeBoolean(false);
								out.writeUTF(message);
								out.flush();
							} else {
								// Only master users may provide a daemon_server
								boolean isOK=true;
								int daemonServer=process.getDaemonServer();
								if(daemonServer!=-1) {
									try {
										if(MasterServer.getUser(conn, process.getEffectiveAdministrator_username())==null) {
											conn.releaseConnection();
											out.writeBoolean(false);
											out.writeUTF("Only master users may register a daemon server.");
											out.flush();
											isOK=false;
										} else {
											UserHost[] servers=MasterServer.getUserHosts(conn, process.getEffectiveAdministrator_username());
											conn.releaseConnection();
											if(servers.length!=0) {
												isOK=false;
												for (UserHost server1 : servers) {
													if (server1.getServerPKey() == daemonServer) {
														isOK=true;
														break;
													}
												}
												if(!isOK) {
													out.writeBoolean(false);
													out.writeUTF("Master user ("+process.getEffectiveAdministrator_username()+") not allowed to access server: "+daemonServer);
													out.flush();
												}
											}
										}
									} catch(RuntimeException | IOException err) {
										conn.rollback();
										throw err;
									} catch(SQLException err) {
										conn.rollbackAndClose();
										throw err;
									} finally {
										conn.releaseConnection();
									}
								}
								if(isOK) {
									out.writeBoolean(true);
									if(existingId == null) {
										Identifier connectorId = MasterServer.getNextConnectorId(protocolVersion);
										process.setConnectorId(connectorId);
										if(protocolVersion.compareTo(AoservProtocol.Version.VERSION_1_83_0) < 0) {
											assert connectorId.getHi() == 0;
											assert connectorId.getLo() != -1;
											out.writeLong(connectorId.getLo());
										} else {
											out.writeIdentifier(connectorId);
										}
									} else {
										process.setConnectorId(existingId);
									}
									// Command sequence starts at a random value
									final long startSeq;
									if(protocolVersion.compareTo(AoservProtocol.Version.VERSION_1_80_0) >= 0) {
										startSeq = MasterServer.getSecureRandom().nextLong();
										out.writeLong(startSeq);
									} else {
										startSeq = 0;
									}
									out.flush();

									try {
										MasterServer.updateAOServProtocolLastUsed(conn, protocolVersion);
									} catch(RuntimeException | IOException err) {
										conn.rollback();
										throw err;
									} catch(SQLException err) {
										conn.rollbackAndClose();
										throw err;
									} finally {
										conn.releaseConnection();
									}

									long seq = startSeq;
									while(server.handleRequest(this, seq++, in, out, process)) {
										// Do nothing in loop
									}
								}
							}
						} catch(RuntimeException | IOException err) {
							conn.rollback();
							throw err;
						} catch(SQLException err) {
							conn.rollbackAndClose();
							throw err;
						} finally {
							conn.releaseConnection();
						}
						break;
					}
					default :
					{
						out.writeBoolean(false);
						out.writeUTF(
							"Client ("+socket.getInetAddress().getHostAddress()+":"+socket.getPort()+") requesting AOServ Protocol version "
							+protocolVersion
							+", server ("+socket.getLocalAddress().getHostAddress()+":"+socket.getLocalPort()+") supporting versions "
							+Strings.join(AoservProtocol.Version.values(), ", ")
							+".  Please upgrade the client code to match the server."
						);
						out.flush();
					}
				}
			} catch(EOFException err) {
				// Normal when disconnecting
			} catch(SSLHandshakeException err) {
				String message = err.getMessage();
				if(
					message == null
					|| (
						!message.equals("Remote host closed connection during handshake")
						&& !message.equals("no cipher suites in common")
					)
				) {
					logger.log(Level.SEVERE, null, err);
				} else {
					logger.log(Level.FINE, null, err);
				}
			} catch(SSLException err) {
				String message = err.getMessage();
				if(
					message == null
					|| (
						!message.equals("Connection has been shutdown: javax.net.ssl.SSLException: java.net.SocketException: Connection reset")
						&& !message.equals("Unrecognized SSL message, plaintext connection?")
					)
				) {
					logger.log(Level.SEVERE, null, err);
				} else {
					logger.log(Level.FINE, null, err);
				}
			} catch(SocketException err) {
				String message = err.getMessage();
				if(
					message == null
					|| (
						// Connection reset common for abnormal client disconnects
						!message.startsWith("Broken pipe")
						&& !message.equals("Connection reset")
						&& !message.startsWith("Connection timed out")
					)
				) {
					logger.log(Level.SEVERE, null, err);
				} else {
					logger.log(Level.FINE, null, err);
				}
			} catch(IOException err) {
				String message = err.getMessage();
				if(
					message == null
					// Broken pipe common for abnormal client disconnects
					|| !message.startsWith("Broken pipe")
				) {
					logger.log(Level.SEVERE, null, err);
				} else {
					logger.log(Level.FINE, null, err);
				}
			} catch(ThreadDeath TD) {
				throw TD;
			} catch(RuntimeException | SQLException T) {
				logger.log(Level.SEVERE, null, T);
			} finally {
				// Close the socket
				try {
					isClosed = true;
					socket.close();
				} catch (IOException err) {
					logger.log(Level.SEVERE, null, err);
				}
			}
		} finally {
			Process_Manager.removeProcess(process);
		}
	}

	@Override
	public boolean isClosed() {
		return isClosed;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy