View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2005, 2013
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Roberto Pineiro, IBM, roberto.pineiro@us.ibm.com
12   * @author : Chung-hao Tan, IBM, chungtan@us.ibm.com
13   * 
14   * 
15   * Change History
16   * Flag       Date        Prog         Description
17   *------------------------------------------------------------------------------- 
18   * 17931      2005-07-28  thschaef     Add InetAddress to CIM Event
19   * 1535756    2006-08-07  lupusalex    Make code warning free
20   * 1565892    2006-11-28  lupusalex    Make SBLIM client JSR48 compliant
21   * 1649779    2007-02-01  lupusalex    Indication listener threads freeze
22   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
23   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
24   * 2763216    2009-04-14  blaschke-oss Code cleanup: visible spelling/grammar errors
25   * 3185818    2011-02-18  blaschke-oss indicationOccured URL incorrect
26   * 3495662    2012-02-29  blaschke-oss Invalid HTML from HttpConnectionHandler.writeError
27   *    2635    2013-05-16  blaschke-oss Slowloris DoS attack for CIM indication listener port
28   *    2657    2013-08-20  blaschke-oss Potential null pointer exception in handleConnection
29   */
30  package org.metricshub.wbem.sblim.cimclient.internal.http;
31  
32  /*-
33   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
34   * WBEM Java Client
35   * ჻჻჻჻჻჻
36   * Copyright 2023 - 2025 MetricsHub
37   * ჻჻჻჻჻჻
38   * Licensed under the Apache License, Version 2.0 (the "License");
39   * you may not use this file except in compliance with the License.
40   * You may obtain a copy of the License at
41   *
42   *      http://www.apache.org/licenses/LICENSE-2.0
43   *
44   * Unless required by applicable law or agreed to in writing, software
45   * distributed under the License is distributed on an "AS IS" BASIS,
46   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
47   * See the License for the specific language governing permissions and
48   * limitations under the License.
49   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
50   */
51  
52  import java.io.IOException;
53  import java.io.InputStream;
54  import java.io.OutputStream;
55  import java.net.InetAddress;
56  import java.net.Socket;
57  import java.net.SocketTimeoutException;
58  import java.util.HashMap;
59  import java.util.Map;
60  import java.util.logging.Level;
61  import javax.net.ssl.SSLSocket;
62  import org.metricshub.wbem.sblim.cimclient.internal.http.io.ASCIIPrintStream;
63  import org.metricshub.wbem.sblim.cimclient.internal.logging.LogAndTraceBroker;
64  import org.metricshub.wbem.sblim.cimclient.internal.util.WBEMConfiguration;
65  import org.metricshub.wbem.sblim.cimclient.internal.util.WBEMConstants;
66  
67  /**
68   * Class HttpConnectionHandler is responsible for handling an incoming
69   * connection
70   *
71   */
72  public class HttpConnectionHandler {
73  	/**
74  	 * MAJOR_VERSION
75  	 */
76  	public static final int MAJOR_VERSION = 1;
77  
78  	/**
79  	 * MINOR_VERSION
80  	 */
81  	public static final int MINOR_VERSION = 1;
82  
83  	HttpContentHandler iHandler;
84  
85  	private int iHeaderTimeout;
86  
87  	private int iMaxAllowedTimeouts;
88  
89  	private Map<String, Integer> iSuspectIPs = new HashMap<String, Integer>();
90  
91  	private StringBuilder iBlockedIPs = null;
92  
93  	/**
94  	 * Ctor.
95  	 *
96  	 * @param pHandler
97  	 *            The content handler
98  	 * @param pProperties
99  	 *            The configuration properties
100 	 */
101 	public HttpConnectionHandler(HttpContentHandler pHandler, WBEMConfiguration pProperties) {
102 		this.iHandler = pHandler;
103 		this.iHeaderTimeout = pProperties.getListenerHttpHeaderTimeout();
104 		this.iMaxAllowedTimeouts = pProperties.getListenerMaxAllowedTimeouts();
105 	}
106 
107 	private synchronized void addSuspectIP(String pSuspectIP) {
108 		if (this.iMaxAllowedTimeouts == 0) return;
109 
110 		Integer times = this.iSuspectIPs.get(pSuspectIP);
111 		if (times == null) {
112 			this.iSuspectIPs.put(pSuspectIP, Integer.valueOf(1));
113 		} else {
114 			int timesVal = times.intValue() + 1;
115 			this.iSuspectIPs.put(pSuspectIP, Integer.valueOf(timesVal));
116 			if (timesVal >= this.iMaxAllowedTimeouts) {
117 				boolean newIP = false;
118 				if (this.iBlockedIPs == null) {
119 					this.iBlockedIPs = new StringBuilder(pSuspectIP);
120 					newIP = true;
121 				} else if (this.iBlockedIPs.indexOf(pSuspectIP) == -1) {
122 					this.iBlockedIPs.append(',');
123 					this.iBlockedIPs.append(pSuspectIP);
124 					newIP = true;
125 				}
126 				if (newIP) LogAndTraceBroker
127 					.getBroker()
128 					.trace(
129 						Level.WARNING,
130 						"Maximum allowable timeouts exceeded, all future connections ignored from " + pSuspectIP
131 					);
132 			}
133 		}
134 	}
135 
136 	private synchronized boolean isBlockedIP(Socket socket) {
137 		return (this.iBlockedIPs != null && this.iBlockedIPs.indexOf(socket.getInetAddress().getHostAddress()) != -1);
138 	}
139 
140 	/**
141 	 * Handles the incoming connection and forwards to the content handler
142 	 *
143 	 * @param socket
144 	 *            The socket of the connection
145 	 */
146 	public void handleConnection(Socket socket) {
147 		InputStream is = null;
148 		OutputStream os = null;
149 		if (isBlockedIP(socket)) {
150 			LogAndTraceBroker
151 				.getBroker()
152 				.trace(Level.FINEST, "Incoming connection ignored from blocked IP " + socket.getInetAddress().getHostAddress());
153 		} else try {
154 			is = socket.getInputStream();
155 			os = socket.getOutputStream();
156 			do { // handle persistent connections
157 				MessageReader reader = new MessageReader(is, this.iHeaderTimeout);
158 				boolean persistent = reader.isPersistentConnectionSupported();
159 				persistent = false;
160 				boolean chunk = reader.isChunkSupported();
161 
162 				HttpServerMethod readerMethod = reader.getMethod();
163 				if (readerMethod.getMethodName().equals("POST") || readerMethod.getMethodName().equals("M-POST")) {
164 					// TODO: validate authorization
165 
166 					MessageWriter writer = new MessageWriter(os, persistent, chunk);
167 					try {
168 						StringBuilder localURL = new StringBuilder(socket instanceof SSLSocket ? "https://" : "http://");
169 						InetAddress localAddress = socket.getLocalAddress();
170 						if (localAddress != null) {
171 							localURL.append(localAddress.getHostAddress());
172 							int port = socket.getLocalPort();
173 							if (port > 0) {
174 								localURL.append(":");
175 								localURL.append(port);
176 							}
177 						}
178 
179 						// 17931
180 						this.iHandler.handleContent(reader, writer, socket.getInetAddress(), localURL.toString());
181 						writer.setMethod(
182 							new HttpServerMethod(readerMethod.getMajorVersion(), readerMethod.getMinorVersion(), 200, "OK")
183 						);
184 					} catch (HttpException e) {
185 						writer.setMethod(
186 							new HttpServerMethod(
187 								readerMethod.getMajorVersion(),
188 								readerMethod.getMinorVersion(),
189 								e.getStatus(),
190 								e.getMessage()
191 							)
192 						);
193 					} catch (Throwable t) {
194 						writer.setMethod(
195 							new HttpServerMethod(
196 								readerMethod.getMajorVersion(),
197 								readerMethod.getMinorVersion(),
198 								501,
199 								"Not Implemented"
200 							)
201 						);
202 						// TODO: define the correct error description
203 						writer.reset();
204 						// TODO: report an specific error
205 						writeError(writer.getOutputStream(), "error", "error");
206 					} finally {
207 						try {
208 							writer.close();
209 						} catch (IOException e) {
210 							LogAndTraceBroker
211 								.getBroker()
212 								.trace(Level.FINER, "Exception while closing output stream from server socket", e);
213 						}
214 					}
215 				} else {
216 					persistent = false;
217 					MessageWriter writer = new MessageWriter(os, false, false);
218 					HttpHeader header = new HttpHeader();
219 					writer.setHeader(header);
220 					// header.addField("Connection", persistent?
221 					// "Keep-Alive","close");
222 					writer.setMethod(
223 						new HttpServerMethod(readerMethod.getMajorVersion(), readerMethod.getMinorVersion(), 501, "Not Implemented")
224 					);
225 					writeError(writer.getOutputStream(), "", "");
226 					try {
227 						writer.close();
228 					} catch (IOException e) {
229 						LogAndTraceBroker
230 							.getBroker()
231 							.trace(Level.FINER, "Exception while closing output stream from server socket", e);
232 					}
233 				}
234 
235 				if (!persistent) break;
236 				try {
237 					reader.close();
238 				} catch (IOException e) {
239 					LogAndTraceBroker
240 						.getBroker()
241 						.trace(Level.FINER, "Exception while closing input stream from server socket", e);
242 				}
243 			} while (true);
244 		} catch (IOException e) {
245 			LogAndTraceBroker.getBroker().trace(Level.FINER, "Exception while reading from server socket", e);
246 
247 			if (
248 				e instanceof SocketTimeoutException ||
249 				WBEMConstants.INDICATION_DOS_EXCEPTION_MESSAGE.equalsIgnoreCase(e.getMessage())
250 			) {
251 				addSuspectIP(socket.getInetAddress().getHostAddress());
252 			}
253 		}
254 		try {
255 			socket.close();
256 		} catch (IOException e) {
257 			LogAndTraceBroker.getBroker().trace(Level.FINER, "Exception while closing server socket", e);
258 		}
259 	}
260 
261 	/**
262 	 * Closes the handler. Will also close the content handler.
263 	 */
264 	public void close() {
265 		this.iHandler.close();
266 	}
267 
268 	private void writeError(ASCIIPrintStream dos, String title, String body) {
269 		dos.print("<HTML> <HEAD> <TITLE>" + title + "</TITLE></HEAD><BODY>" + body + "</BODY></HTML>");
270 	}
271 
272 	/**
273 	 * Get the IPs blocked by the listener associated with the specified port.
274 	 *
275 	 * @return The comma-separated list of blocked IPs.
276 	 */
277 	public synchronized String getBlockedIPs() {
278 		return (this.iBlockedIPs == null) ? null : this.iBlockedIPs.toString();
279 	}
280 
281 	/**
282 	 * Set the IPs to be blocked by the listener associated with the specified
283 	 * port.
284 	 *
285 	 * @param pIPs
286 	 *            The comma-separated list of blocked IPs.
287 	 */
288 	public synchronized void setBlockedIPs(String pIPs) {
289 		this.iBlockedIPs = (pIPs == null || pIPs.trim().length() == 0) ? null : new StringBuilder(pIPs);
290 	}
291 }