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

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

There is a newer version: 1.91.8
Show newest version
/*
 * aoserv-master - Master server for the AOServ Platform.
 * Copyright (C) 2012, 2013, 2015, 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.account.Account;
import com.aoindustries.aoserv.client.master.User;
import com.aoindustries.aoserv.client.master.UserHost;
import com.aoindustries.aoserv.client.net.reputation.Host;
import com.aoindustries.aoserv.client.net.reputation.Network;
import com.aoindustries.aoserv.client.net.reputation.Set;
import com.aoindustries.aoserv.client.schema.Table;
import com.aoindustries.dbc.DatabaseConnection;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

/**
 * The IpReputationSetHandler handles all the accesses to the
 * net.reputation.Set tables.
 *
 * @author  AO Industries, Inc.
 */
final public class NetReputationSetHandler {

	/**
	 * Make no instances.
	 */
	private NetReputationSetHandler() {
	}

	public static Account.Name getAccountForIpReputationSet(DatabaseConnection conn, int ipReputationSet) throws IOException, SQLException {
		return conn.executeObjectQuery(ObjectFactories.accountNameFactory,
			"select accounting from \"net.reputation\".\"Set\" where id=?",
			ipReputationSet
		);
	}

	public static void checkAccessIpReputationSet(DatabaseConnection conn, RequestSource source, String action, int ipReputationSet) throws IOException, SQLException {
		User mu = MasterServer.getUser(conn, source.getCurrentAdministrator());
		if(mu!=null) {
			if(MasterServer.getUserHosts(conn, source.getCurrentAdministrator()).length!=0) {
				// Must be an admin or router to submit reputation
				String message=
					"currentAdministrator="
					+source.getCurrentAdministrator()
					+" is not allowed to access ip reputation set: action='"
					+action
					+", id="
					+ipReputationSet
				;
				throw new SQLException(message);
			}
		} else {
			AccountHandler.checkAccessAccount(conn,
				source,
				action,
				getAccountForIpReputationSet(conn, ipReputationSet)
			);
		}
	}

	/**
	 * Adds IP reputation with security checks.
	 */
	public static void addIpReputation(
		DatabaseConnection conn,
		RequestSource source,
		InvalidateList invalidateList,
		int ipReputationSet,
		Set.AddReputation[] addReputations
	) throws IOException, SQLException {
		checkAccessIpReputationSet(conn, source, "addIpReputation", ipReputationSet);

		addIpReputation(conn, invalidateList, ipReputationSet, addReputations);
	}

	/**
	 * Gets the network for a host and networkPrefix.
	 */
	private static int getNetwork(int host, short networkPrefix) {
		return
			host
			& (0xffffffff << (32-networkPrefix))
		;
	}

	/**
	 * Locks all IP reputation tables for updates.  Locks in the same order to avoid any potential deadlock.
	 */
	private static void lockForUpdate(
		DatabaseConnection conn
	) throws SQLException {
		conn.executeUpdate(
			"LOCK TABLE\n"
			+ "  \"net.reputation\".\"Set\",\n"
			+ "  \"net.reputation\".\"Host\",\n"
			+ "  \"net.reputation\".\"Network\"\n"
			+ "IN EXCLUSIVE MODE");
	}

	/* TODO: Do in batches
	private static void createTempTable(DatabaseConnection conn, String suffix) throws SQLException {
		conn.executeUpdate(
			"CREATE TEMPORARY TABLE add_reputation_" + suffix + " (\n"
			+ "  host INTEGER NOT NULL,\n"
			+ "  network INTEGER NOT NULL,\n"
			+ "  confidence CHAR(1) NOT NULL,\n"
			+ "  score SMALLINT NOT NULL\n"
			+ ")"
		);
	}

	private static void addTempRows(
		DatabaseConnection conn,
		short networkPrefix,
		Set.AddReputation[] addReputations,
		Set.ReputationType reputationType,
		String suffix
	) throws SQLException {
		PreparedStatement pstmt = conn.getConnection(
			Connection.TRANSACTION_READ_COMMITTED,
			false
		).prepareStatement("INSERT INTO add_reputation_" + suffix + " VALUES (?,?,?,?)");
		try {
			boolean hasRow = false;
			for(Set.AddReputation addRep : addReputations) {
				if(addRep.getReputationType()==reputationType) {
					hasRow = true;
					int host = addRep.getHost();
					pstmt.setInt   (1, host);
					pstmt.setInt   (2, getNetwork(host, networkPrefix));
					pstmt.setString(3, Character.toString(addRep.getConfidence().toChar()));
					pstmt.setShort (4, addRep.getScore());
					pstmt.addBatch();
				}
			}
			if(hasRow) pstmt.executeBatch();
		} catch(SQLException e) {
			throw new WrappedSQLException(e, pstmt);
		} finally {
			pstmt.close();
			pstmt = null;
		}
	}
	 */

	/**
	 * Updates the host reputations for any that exist.
	 * Adds the new hosts that did not exist.
	 *
	 * @return  true if any updated
	 */
	/* TODO: Do in batches
	private static boolean updateHosts(
		DatabaseConnection conn,
		String suffix,
		short maxUncertainReputation,
		short maxDefiniteReputation
	) throws SQLException {
		throw new SQLException("TODO: Implement method");
	}
	 */

	/**
	 * Updates the network reputations for any that exist.
	 * Adds the new networks that did not exist.
	 *
	 * @return  true if any updated
	 */
	/* TODO: Do in batches
	private static boolean updateNetworks(
		DatabaseConnection conn,
		String suffix,
		short networkPrefix,
		short maxNetworkReputation
	) throws SQLException {
		// Determine the maximum score that will result in maxNetworkReputation when divided by network size
		final int maxScore = ((maxNetworkReputation + 1) << (32 - networkPrefix)) - 1;
		conn.execute
		throw new SQLException("TODO: Implement method");
	}
	 */

	private static short constrainReputation(int newReputation, Set.ConfidenceType confidence, short maxUncertainReputation, short maxDefiniteReputation) {
		if(confidence==Set.ConfidenceType.UNCERTAIN) {
			return newReputation>maxUncertainReputation ? maxUncertainReputation : (short)newReputation;
		} else if(confidence==Set.ConfidenceType.DEFINITE) {
			return newReputation>maxDefiniteReputation ? maxDefiniteReputation : (short)newReputation;
		} else {
			throw new AssertionError("Unexpected value for confidence: " + confidence);
		}
	}

	/**
	 * Adds IP reputation with no security checks.
	 */
	public static void addIpReputation(
		DatabaseConnection conn,
		InvalidateList invalidateList,
		int ipReputationSet,
		Set.AddReputation[] addReputations
	) throws IOException, SQLException {
		// Can't add reputation to a disabled business
		Account.Name account = getAccountForIpReputationSet(conn, ipReputationSet);
		if(AccountHandler.isAccountDisabled(conn, account)) throw new SQLException("Unable to add IP reputation, business disabled: "+account);

		if(addReputations.length>0) {
			// Get the settings
			final short maxUncertainReputation = conn.executeShortQuery("SELECT max_uncertain_reputation FROM \"net.reputation\".\"Set\" WHERE id=?", ipReputationSet);
			final short maxDefiniteReputation  = conn.executeShortQuery("SELECT max_definite_reputation  FROM \"net.reputation\".\"Set\" WHERE id=?", ipReputationSet);
			final short networkPrefix          = conn.executeShortQuery("SELECT network_prefix           FROM \"net.reputation\".\"Set\" WHERE id=?", ipReputationSet);
			final short maxNetworkReputation   = conn.executeShortQuery("SELECT max_network_reputation   FROM \"net.reputation\".\"Set\" WHERE id=?", ipReputationSet);
			final int   maxNetworkCounter        = ((maxNetworkReputation + 1) << (32 - networkPrefix)) - 1;

			// Will only send signals when changed
			boolean hostsUpdated = false;
			boolean networksUpdated = false;

			// 

			// Lock for update
			lockForUpdate(conn);

			// Flag as rep added
			conn.executeUpdate("UPDATE \"net.reputation\".\"Set\" SET last_reputation_added=now() WHERE id=?", ipReputationSet);

			for(Set.AddReputation addRep : addReputations) {
				int host = addRep.getHost();
				Set.ConfidenceType confidence = addRep.getConfidence();
				Set.ReputationType reputationType = addRep.getReputationType();
				short score = addRep.getScore();
				Host dbHost = conn.executeObjectQuery(
					Connection.TRANSACTION_READ_COMMITTED,
					true,
					false,
					(ResultSet result) -> {
						Host obj = new Host();
						obj.init(result);
						return obj;
					},
					"select * from \"net.reputation\".\"Host\" where \"set\"=? and host=?",
					ipReputationSet,
					host
				);
				int positiveChange = 0;
				if(dbHost==null) {
					// Add new
					short goodReputation = 0;
					short badReputation = 0;
					// Constraint score by confidence
					short constrainedReputation = constrainReputation(score, confidence, maxUncertainReputation, maxDefiniteReputation);
					// Resolve starting reputation
					if(reputationType==Set.ReputationType.GOOD) {
						goodReputation = constrainedReputation;
						// Update positiveChange for network reputation
						positiveChange = goodReputation;
					} else if(reputationType==Set.ReputationType.BAD) {
						badReputation = constrainedReputation;
					} else {
						throw new AssertionError("Unexpected value for reputationType: " + reputationType);
					}
					if(goodReputation!=0 || badReputation!=0) {
						int rowCount = conn.executeUpdate(
							"INSERT INTO \"net.reputation\".\"Host\" (\"set\", host, good_reputation, bad_reputation) VALUES (?,?,?,?)",
							ipReputationSet,
							host,
							goodReputation,
							badReputation
						);
						if(rowCount!=1) throw new SQLException("Wrong number of rows updated: " + rowCount);
						hostsUpdated = true;
					}
				} else {
					if(reputationType==Set.ReputationType.GOOD) {
						short oldGoodReputation = dbHost.getGoodReputation();
						short newGoodReputation = constrainReputation(
							(int)oldGoodReputation + (int)score,
							confidence,
							maxUncertainReputation,
							maxDefiniteReputation
						);
						if(newGoodReputation!=oldGoodReputation) {
							int rowCount = conn.executeUpdate(
								"UPDATE \"net.reputation\".\"Host\" SET good_reputation=? WHERE \"set\"=? AND host=?",
								newGoodReputation,
								ipReputationSet,
								host
							);
							if(rowCount!=1) throw new SQLException("Wrong number of rows updated: " + rowCount);
							hostsUpdated = true;
							// Update positiveChange for network reputation
							positiveChange = newGoodReputation - oldGoodReputation;
						}
					} else if(reputationType==Set.ReputationType.BAD) {
						short oldBadReputation = dbHost.getBadReputation();
						short newBadReputation = constrainReputation(
							(int)oldBadReputation + (int)score,
							confidence,
							maxUncertainReputation,
							maxDefiniteReputation
						);
						if(newBadReputation!=oldBadReputation) {
							int rowCount = conn.executeUpdate(
								"UPDATE \"net.reputation\".\"Host\" SET bad_reputation=? WHERE \"set\"=? AND host=?",
								newBadReputation,
								ipReputationSet,
								host
							);
							if(rowCount!=1) throw new SQLException("Wrong number of rows updated: " + rowCount);
							hostsUpdated = true;
						}
					} else {
						throw new AssertionError("Unexpected value for reputationType: " + reputationType);
					}
				}
				if(positiveChange>0) {
					// Update network when positive change applied
					int network = getNetwork(host, networkPrefix);
					Network dbNetwork = conn.executeObjectQuery(
						Connection.TRANSACTION_READ_COMMITTED,
						true,
						false,
						(ResultSet result) -> {
							Network obj = new Network();
							obj.init(result);
							return obj;
						},
						"select * from \"net.reputation\".\"Network\" where \"set\"=? and network=?",
						ipReputationSet,
						network
					);
					if(dbNetwork==null) {
						// Add new
						int networkCounter = positiveChange;
						if(networkCounter>maxNetworkCounter) networkCounter = maxNetworkCounter;
						int rowCount = conn.executeUpdate(
							"INSERT INTO \"net.reputation\".\"Network\" (\"set\", network, counter) VALUES (?,?,?)",
							ipReputationSet,
							network,
							networkCounter
						);
						if(rowCount!=1) throw new SQLException("Wrong number of rows updated: " + rowCount);
						networksUpdated = true;
					} else {
						// Update existing
						int oldCounter = dbNetwork.getCounter();
						long newCounterLong = (long)oldCounter + (long)positiveChange;
						int newCounter = newCounterLong <= maxNetworkCounter ? (int)newCounterLong : maxNetworkCounter;
						if(newCounter!=oldCounter) {
							int rowCount = conn.executeUpdate(
								"UPDATE \"net.reputation\".\"Network\" SET counter=? WHERE \"set\"=? AND network=?",
								newCounter,
								ipReputationSet,
								network
							);
							if(rowCount!=1) throw new SQLException("Wrong number of rows updated: " + rowCount);
							networksUpdated = true;
						}
					}
				}
			}
			// 

			// 
			/* TODO: Do in batches
			try {
				// Populate temporary tables
				createTempTable(conn, "good");
				createTempTable(conn, "bad");

				// Add all rows in big batches
				addTempRows(conn, networkPrefix, addReputations, Set.ReputationType.GOOD, "good");
				addTempRows(conn, networkPrefix, addReputations, Set.ReputationType.BAD,  "bad");

				// Lock for update
				lockForUpdate(conn);

				// Update hosts
				if(updateHosts(conn, "good", maxUncertainReputation, maxDefiniteReputation)) hostsUpdated = true;
				if(updateHosts(conn, "bad" , maxUncertainReputation, maxDefiniteReputation)) hostsUpdated = true;

				// Update networks (only good)
				if(updateNetworks(conn, "good", networkPrefix, maxNetworkReputation)) networksUpdated = true;
				//if(updateNetworks(conn, "bad" , networkPrefix, maxNetworkReputation)) networksUpdated = true;
			} finally {
				conn.executeUpdate("DROP TABLE IF EXISTS add_reputation_good");
				conn.executeUpdate("DROP TABLE IF EXISTS add_reputation_bad");
			}
			 */
			// 

			// Notify all clients of the update
			if(hostsUpdated) {
				invalidateList.addTable(
					conn,
					Table.TableID.IP_REPUTATION_SET_HOSTS,
					account,
					AccountHandler.getHostsForAccount(conn, account),
					false
				);
			}
			if(networksUpdated) {
				invalidateList.addTable(
					conn,
					Table.TableID.IP_REPUTATION_SET_NETWORKS,
					account,
					AccountHandler.getHostsForAccount(conn, account),
					false
				);
			}
			// Also notify routers
			for(Map.Entry entry : MasterServer.getUsers(conn).entrySet()) {
				com.aoindustries.aoserv.client.account.User.Name user = entry.getKey();
				User mu = entry.getValue();
				if(mu.isRouter()) {
					// TODO: Filter isRouter users by server_farm
					for(UserHost ms : MasterServer.getUserHosts(conn, user)) {
						if(hostsUpdated) {
							invalidateList.addTable(conn,
								Table.TableID.IP_REPUTATION_SET_HOSTS,
								InvalidateList.allAccounts,
								ms.getServerPKey(),
								false
							);
						}
						if(networksUpdated) {
							invalidateList.addTable(conn,
								Table.TableID.IP_REPUTATION_SET_NETWORKS,
								InvalidateList.allAccounts,
								ms.getServerPKey(),
								false
							);
						}
					}
				}
			}
		}
	}

	// TODO: CronJob that decays and cleans reputation
	// TODO: Enforce max_hosts here instead of when each batch of hosts is updated?
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy