001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v2.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.net; 015 016import java.io.IOException; 017import java.lang.reflect.Constructor; 018import java.net.InetAddress; 019import java.net.ServerSocket; 020import java.net.Socket; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.List; 024import java.util.concurrent.CountDownLatch; 025 026import javax.net.ServerSocketFactory; 027 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031import ch.qos.logback.classic.LoggerContext; 032import ch.qos.logback.classic.joran.JoranConfigurator; 033import ch.qos.logback.core.joran.spi.JoranException; 034import ch.qos.logback.core.util.IpAddressMatcher; 035 036/** 037 * A simple {@link SocketNode} based server. 038 * 039 * <pre> 040 * <b>Usage:</b> java ch.qos.logback.classic.net.SimpleSocketServer port configFile 041 * allowedAddress [allowedAddress ...] 042 * </pre> 043 * 044 * where <em>port</em> is a port number where the server listens, 045 * <em>configFile</em> is an XML configuration file fed to 046 * {@link JoranConfigurator}, and each <em>allowedAddress</em> is a client IP 047 * or CIDR range that is permitted to connect (e.g. {@code 192.168.1.10} or 048 * {@code 192.168.1.0/24}). At least one allowed address must be specified on 049 * the command line. 050 * 051 * <p> 052 * When embedding the server programmatically, allowed client addresses must be 053 * registered with {@link #addAllowedClientAddress(String)} or 054 * {@link #setAllowedClientAddresses(Collection)} before clients can connect. 055 * Supported forms are single IPs and CIDR network ranges. An empty whitelist 056 * means no clients are allowed. 057 * </p> 058 * 059 * @author Ceki Gülcü 060 * @author Sébastien Pennec 061 * 062 * @since 0.8.4 063 */ 064public class SimpleSocketServer extends Thread { 065 066 Logger logger = LoggerFactory.getLogger(SimpleSocketServer.class); 067 068 private final int port; 069 private final LoggerContext lc; 070 private boolean closed = false; 071 private ServerSocket serverSocket; 072 private List<SocketNode> socketNodeList = new ArrayList<SocketNode>(); 073 074 /** 075 * Only clients whose remote address matches one of these matchers are 076 * accepted. Empty means no clients are allowed. 077 */ 078 private final List<IpAddressMatcher> allowedClientAddresses = new ArrayList<IpAddressMatcher>(); 079 080 // used for testing purposes 081 private CountDownLatch latch; 082 083 public static void main(String argv[]) throws Exception { 084 doMain(SimpleSocketServer.class, argv); 085 } 086 087 protected static void doMain(Class<? extends SimpleSocketServer> serverClass, String argv[]) throws Exception { 088 if (argv.length < 3) { 089 if (argv.length == 2) { 090 usage("No allowed client IP addresses specified. Please explicitly whitelist client IPs" 091 + " or CIDR ranges on the command line (e.g. 192.168.1.0/24).", serverClass); 092 } else { 093 usage("Wrong number of arguments.", serverClass); 094 } 095 } 096 097 int port = parsePortNumber(argv[0]); 098 String configFile = argv[1]; 099 LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); 100 configureLC(lc, configFile); 101 102 SimpleSocketServer sss = createServer(serverClass, lc, port); 103 for (int i = 2; i < argv.length; i++) { 104 try { 105 sss.addAllowedClientAddress(argv[i]); 106 } catch (IllegalArgumentException e) { 107 usage("Invalid allowed client address [" + argv[i] + "]: " + e.getMessage(), serverClass); 108 } 109 } 110 111 112 113 // start the server in a separate thread 114 sss.start(); 115 } 116 117 private static SimpleSocketServer createServer(Class<? extends SimpleSocketServer> serverClass, LoggerContext lc, 118 int port) throws Exception { 119 Constructor<? extends SimpleSocketServer> constructor = serverClass.getConstructor(LoggerContext.class, 120 int.class); 121 return constructor.newInstance(lc, port); 122 } 123 124 public SimpleSocketServer(LoggerContext lc, int port) { 125 this.lc = lc; 126 this.port = port; 127 } 128 129 /** 130 * Authorize a single client IP address or a CIDR network range. 131 * <p> 132 * Only matching clients are accepted; others are closed immediately after 133 * {@code accept()}. When no allowed addresses are registered, no clients 134 * are accepted. 135 * </p> 136 * 137 * @param addressOrCidr a single IP (e.g. {@code 10.0.0.5}) or CIDR range 138 * (e.g. {@code 192.168.1.0/24}) 139 * @throws IllegalArgumentException if the specification is invalid 140 * @since 1.6.2 141 */ 142 public void addAllowedClientAddress(String addressOrCidr) { 143 allowedClientAddresses.add(new IpAddressMatcher(addressOrCidr)); 144 } 145 146 /** 147 * Replace the set of authorized client addresses with the given collection. 148 * Each entry must be a single IP or CIDR range. Passing an empty collection 149 * (or {@code null}) clears the whitelist so that no clients are allowed. 150 * 151 * @param addresses allowed addresses / CIDR ranges, or {@code null} 152 * @throws IllegalArgumentException if any specification is invalid 153 * @since 1.6.2 154 */ 155 public void setAllowedClientAddresses(Collection<String> addresses) { 156 allowedClientAddresses.clear(); 157 if (addresses == null) { 158 return; 159 } 160 for (String addressOrCidr : addresses) { 161 addAllowedClientAddress(addressOrCidr); 162 } 163 } 164 165 /** 166 * Returns {@code true} if the client is allowed to connect. 167 * <p> 168 * When no allowed addresses are configured, no clients are allowed. 169 * </p> 170 * 171 * @param clientAddress the remote address of the connecting client 172 * @return {@code true} if the connection should be accepted 173 * @since 1.6.2 174 */ 175 protected boolean isClientAllowed(InetAddress clientAddress) { 176 if (allowedClientAddresses.isEmpty() || clientAddress == null) { 177 return false; 178 } 179 for (IpAddressMatcher matcher : allowedClientAddresses) { 180 if (matcher.matches(clientAddress)) { 181 return true; 182 } 183 } 184 return false; 185 } 186 187 public void run() { 188 189 final String oldThreadName = Thread.currentThread().getName(); 190 191 try { 192 193 final String newThreadName = getServerThreadName(); 194 Thread.currentThread().setName(newThreadName); 195 196 logger.info("Listening on port " + port); 197 if (allowedClientAddresses.isEmpty()) { 198 logger.warn("No allowed client addresses configured; all incoming connections will be denied. " 199 + "Use addAllowedClientAddress() or pass allowed addresses on the command line."); 200 } else { 201 logger.info("Client IP whitelist in effect ({} allowed address pattern(s))", 202 allowedClientAddresses.size()); 203 } 204 serverSocket = getServerSocketFactory().createServerSocket(port); 205 while (!closed) { 206 logger.info("Waiting to accept a new client."); 207 signalAlmostReadiness(); 208 Socket socket = serverSocket.accept(); 209 InetAddress clientAddress = socket.getInetAddress(); 210 logger.info("Connected to client at " + clientAddress); 211 if (!isClientAllowed(clientAddress)) { 212 logger.warn("Denying connection from unauthorized client " + clientAddress); 213 closeSocketQuietly(socket); 214 continue; 215 } 216 logger.info("Starting new socket node."); 217 SocketNode newSocketNode = new SocketNode(this, socket, lc); 218 synchronized (socketNodeList) { 219 socketNodeList.add(newSocketNode); 220 } 221 final String clientThreadName = getClientThreadName(socket); 222 new Thread(newSocketNode, clientThreadName).start(); 223 } 224 } catch (Exception e) { 225 if (closed) { 226 logger.info("Exception in run method for a closed server. This is normal."); 227 } else { 228 logger.error("Unexpected failure in run method", e); 229 } 230 } 231 232 finally { 233 Thread.currentThread().setName(oldThreadName); 234 } 235 } 236 237 private void closeSocketQuietly(Socket socket) { 238 try { 239 socket.close(); 240 } catch (IOException e) { 241 logger.debug("Failed to close unauthorized client socket", e); 242 } 243 } 244 245 /** 246 * Returns the name given to the server thread. 247 */ 248 protected String getServerThreadName() { 249 return String.format("Logback %s (port %d)", getClass().getSimpleName(), port); 250 } 251 252 /** 253 * Returns a name to identify each client thread. 254 */ 255 protected String getClientThreadName(Socket socket) { 256 return String.format("Logback SocketNode (client: %s)", socket.getRemoteSocketAddress()); 257 } 258 259 /** 260 * Gets the platform default {@link ServerSocketFactory}. 261 * <p> 262 * Subclasses may override to provide a custom server socket factory. 263 */ 264 protected ServerSocketFactory getServerSocketFactory() { 265 return ServerSocketFactory.getDefault(); 266 } 267 268 /** 269 * Signal another thread that we have established a connection This is useful 270 * for testing purposes. 271 */ 272 void signalAlmostReadiness() { 273 if (latch != null && latch.getCount() != 0) { 274 // System.out.println("signalAlmostReadiness() with latch "+latch); 275 latch.countDown(); 276 } 277 } 278 279 /** 280 * Used for testing purposes 281 * 282 * @param latch 283 */ 284 void setLatch(CountDownLatch latch) { 285 this.latch = latch; 286 } 287 288 /** 289 * Used for testing purposes 290 */ 291 public CountDownLatch getLatch() { 292 return latch; 293 } 294 295 public boolean isClosed() { 296 return closed; 297 } 298 299 public void close() { 300 closed = true; 301 if (serverSocket != null) { 302 try { 303 serverSocket.close(); 304 } catch (IOException e) { 305 logger.error("Failed to close serverSocket", e); 306 } finally { 307 serverSocket = null; 308 } 309 } 310 311 logger.info("closing this server"); 312 synchronized (socketNodeList) { 313 for (SocketNode sn : socketNodeList) { 314 sn.close(); 315 } 316 } 317 if (socketNodeList.size() != 0) { 318 logger.warn("Was expecting a 0-sized socketNodeList after server shutdown"); 319 } 320 321 } 322 323 public void socketNodeClosing(SocketNode sn) { 324 logger.debug("Removing {}", sn); 325 326 // don't allow simultaneous access to the socketNodeList 327 // (e.g. removal whole iterating on the list causes 328 // java.util.ConcurrentModificationException) 329 synchronized (socketNodeList) { 330 socketNodeList.remove(sn); 331 } 332 } 333 334 static void usage(String msg) { 335 usage(msg, SimpleSocketServer.class); 336 } 337 338 static void usage(String msg, Class<? extends SimpleSocketServer> serverClass) { 339 System.err.println(msg); 340 System.err.println("Usage: java " + serverClass.getName() 341 + " port configFile allowedAddress [allowedAddress ...]"); 342 System.err.println( 343 " allowedAddress: a single IP (e.g. 192.168.1.10) or CIDR range (e.g. 192.168.1.0/24)"); 344 System.err.println(" At least one allowedAddress must be specified."); 345 System.exit(1); 346 } 347 348 static int parsePortNumber(String portStr) { 349 try { 350 return Integer.parseInt(portStr); 351 } catch (java.lang.NumberFormatException e) { 352 e.printStackTrace(); 353 usage("Could not interpret port number [" + portStr + "]."); 354 // we won't get here 355 return -1; 356 } 357 } 358 359 static public void configureLC(LoggerContext lc, String configFile) throws JoranException { 360 JoranConfigurator configurator = new JoranConfigurator(); 361 lc.reset(); 362 configurator.setContext(lc); 363 configurator.doConfigure(configFile); 364 } 365}