1 package org.sentrysoftware.ipmi.core.connection;
2
3 /*-
4 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5 * IPMI Java Client
6 * ჻჻჻჻჻჻
7 * Copyright 2023 Verax Systems, Sentry Software
8 * ჻჻჻჻჻჻
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation, either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Lesser Public License for more details.
18 *
19 * You should have received a copy of the GNU General Lesser Public
20 * License along with this program. If not, see
21 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23 */
24
25 import org.sentrysoftware.ipmi.core.coding.commands.PrivilegeLevel;
26 import org.sentrysoftware.ipmi.core.coding.commands.session.GetChannelAuthenticationCapabilitiesResponseData;
27 import org.sentrysoftware.ipmi.core.coding.security.CipherSuite;
28 import org.sentrysoftware.ipmi.core.common.PropertiesManager;
29 import org.sentrysoftware.ipmi.core.transport.Messenger;
30 import org.sentrysoftware.ipmi.core.transport.UdpListener;
31 import org.sentrysoftware.ipmi.core.transport.UdpMessenger;
32
33 import java.io.IOException;
34 import java.net.InetAddress;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.concurrent.atomic.AtomicInteger;
38
39 /**
40 * Manages multiple {@link Connection}s
41 */
42 public class ConnectionManager {
43 private Messenger messenger;
44 private List<Connection> connections;
45
46 private static final AtomicInteger sessionlessTag = new AtomicInteger(0);
47 private static List<Integer> reservedTags = new ArrayList<Integer>();
48
49 /**
50 * Frequency of the no-op commands that will be sent to keep up the session
51 */
52 private long pingPeriod = -1;
53
54 /**
55 * Initiates the connection manager. Wildcard IP address will be used.
56 *
57 * @param port the port at which {@link UdpListener} will work
58 * @param pingPeriod frequency of the no-op commands that will be sent to keep
59 * up the session. 0 to disable ping requests.
60 * @throws IOException If UdpMessenger encountered an error
61 */
62 public ConnectionManager(int port, long pingPeriod) throws IOException {
63 this(port);
64 this.pingPeriod = pingPeriod;
65 }
66
67 /**
68 * Initiates the connection manager. Wildcard IP address will be used.
69 *
70 * @param port
71 * - the port at which {@link UdpListener} will work
72 * @throws IOException If UdpMessenger encountered an error
73 */
74 public ConnectionManager(int port) throws IOException {
75 messenger = new UdpMessenger(port);
76 initialize();
77 }
78
79 /**
80 * Initiates the connection manager.
81 *
82 * @param port
83 * - the port at which {@link UdpListener} will work
84 * @param address
85 * - the IP interface {@link UdpListener} will bind to
86 * @throws IOException If UdpMessenger encountered an error
87 */
88 public ConnectionManager(int port, InetAddress address) throws IOException {
89 messenger = new UdpMessenger(port, address);
90 initialize();
91 }
92
93 /**
94 * Initiates the connection manager.
95 *
96 * @param messenger
97 * - {@link Messenger} to be used in communication
98 */
99 public ConnectionManager(Messenger messenger) {
100 this.messenger = messenger;
101 initialize();
102 }
103
104 private void initialize() {
105 connections = new ArrayList<Connection>();
106 reservedTags = new ArrayList<Integer>();
107 if (pingPeriod == -1) {
108 pingPeriod = Long.parseLong(PropertiesManager.getInstance().getProperty("pingPeriod"));
109 }
110 }
111
112 /**
113 * Closes all open connections and disconnects {@link UdpListener}.
114 */
115 public void close() {
116 synchronized (connections) {
117 for (Connection connection : connections) {
118 if (connection != null && connection.isActive()) {
119 connection.disconnect();
120 }
121 }
122 }
123 messenger.closeConnection();
124 }
125
126 /**
127 * The tag for messages sent outside the session generated by the
128 * {@link ConnectionManager}. Auto-incremented.
129 */
130 public static int generateSessionlessTag() {
131 synchronized (sessionlessTag) {
132 boolean wait = true;
133 while (wait) {
134 sessionlessTag.incrementAndGet();
135 sessionlessTag.set(sessionlessTag.get() % 60);
136 synchronized (reservedTags) {
137 if (!reservedTags.contains(sessionlessTag.get())) {
138 wait = false;
139 }
140 }
141 if (wait) {
142 try {
143 sessionlessTag.wait(1);
144 } catch (InterruptedException e) {
145 // TODO log
146 }
147 }
148 }
149 synchronized (reservedTags) {
150 reservedTags.add(sessionlessTag.get());
151 }
152 return sessionlessTag.get();
153 }
154 }
155
156 /**
157 * Frees the sessionless tag for further use
158 *
159 * @param tag
160 * - tag to free
161 */
162 public static void freeTag(int tag) {
163 synchronized (reservedTags) {
164 reservedTags.remove((Integer) tag);
165 }
166 }
167
168 /**
169 * Returns {@link Connection} identified by index.
170 *
171 * @param index
172 * - index of the connection to return
173 */
174 public Connection getConnection(int index) {
175 return connections.get(index);
176 }
177
178 /**
179 * Closes the connection with the given index.
180 */
181 public void closeConnection(int index) {
182 connections.get(index).disconnect();
183 }
184
185 /**
186 * Returns first {@link Connection} associated with the address
187 *
188 * @param address
189 * - {@link InetAddress} of the remote host to get connection
190 * with.
191 * @return First {@link Connection} to the address or null if none found
192 */
193 public Connection getConnection(InetAddress address, int port) {
194 synchronized (connections) {
195 for (Connection connection : connections) {
196 if (connection != null && connection.isActive()
197 && connection.getRemoteMachineAddress() == address
198 && connection.getRemoteMachinePort() == port) {
199 return connection;
200 }
201 }
202 }
203 return null;
204 }
205
206 /**
207 * Creates and initiates {@link Connection} to the remote host.
208 * @param address
209 * - {@link InetAddress} of the remote host
210 * @param pingPeriod
211 * - frequency of the no-op commands that will be sent to keep up the session
212 * @param skipCiphers
213 * - determines if the getAvailableCipherSuites and getChannelAuthenticationCapabilities phases should be skipped
214 * @return index of the connection
215 * @throws IOException
216 * - when properties file was not found
217 */
218 public int createConnection(InetAddress address, int port, int pingPeriod, boolean skipCiphers) throws IOException {
219 Connection connection = new Connection(messenger, 0);
220 connection.connect(address, port, pingPeriod, skipCiphers);
221
222 synchronized (connections) {
223 connections.add(connection);
224 return connections.size() - 1;
225 }
226 }
227
228 /**
229 * Creates and initiates {@link Connection} to the remote host.
230 *
231 * @param address
232 * - {@link InetAddress} of the remote host
233 * @param pingPeriod
234 * - frequency of the no-op commands that will be sent to keep up
235 * the session
236 * @return index of the connection
237 * @throws IOException
238 * - when properties file was not found
239 */
240 public int createConnection(InetAddress address, int port, int pingPeriod) throws IOException {
241 Connection connection = new Connection(messenger, 0);
242 connection.connect(address, port, pingPeriod);
243
244 synchronized (connections) {
245 connections.add(connection);
246 return connections.size() - 1;
247 }
248 }
249
250 /**
251 * Creates and initiates {@link Connection} to the remote host with the
252 * default ping frequency.
253 *
254 * @param address
255 * - {@link InetAddress} of the remote host
256 * @return index of the connection
257 * @throws IOException
258 * when properties file was not found
259 */
260 public int createConnection(InetAddress address, int port) throws IOException {
261
262 synchronized (connections) {
263 Connection connection = new Connection(messenger,
264 connections.size());
265 connection.connect(address, port, pingPeriod);
266 connections.add(connection);
267 return connections.size() - 1;
268 }
269 }
270
271 /**
272 * Creates and initiates {@link Connection} to the remote host with the default ping frequency.
273 * @param address
274 * - {@link InetAddress} of the remote host
275 * @param skipCiphers
276 * - determines if the getAvailableCipherSuites and getChannelAuthenticationCapabilities phases should be skipped
277 * @return index of the connection
278 * @throws IOException
279 * when properties file was not found
280 */
281 public int createConnection(InetAddress address, int port, boolean skipCiphers) throws IOException {
282 synchronized (connections) {
283 Connection connection = new Connection(messenger, connections.size());
284 connection.connect(address, port, pingPeriod, skipCiphers);
285 connections.add(connection);
286 return connections.size() - 1;
287 }
288 }
289
290 /**
291 * Gets from the managed system supported {@link CipherSuite}s. Should be
292 * performed only immediately after {@link #createConnection}.
293 *
294 * @param connection
295 * - index of the connection to get available Cipher Suites from
296 *
297 * @return list of the {@link CipherSuite}s supported by the managed system.
298 * @throws ConnectionException
299 * when connection is in the state that does not allow to
300 * perform this operation.
301 * @throws Exception
302 * when sending message to the managed system fails
303 */
304 public List<CipherSuite> getAvailableCipherSuites(int connection)
305 throws Exception {
306 int tag = generateSessionlessTag();
307 List<CipherSuite> suites;
308 try {
309 suites = connections.get(connection).getAvailableCipherSuites(tag);
310 } catch (Exception e) {
311 freeTag(tag);
312 throw e;
313 }
314 freeTag(tag);
315 return suites;
316 }
317
318 /**
319 * Queries the managed system for the details of the authentification
320 * process. Must be performed after {@link #getAvailableCipherSuites(int)}
321 *
322 * @param connection
323 * - index of the connection to get Channel Authentication
324 * Capabilities from
325 * @param cipherSuite
326 * - {@link CipherSuite} requested for the session
327 * @param requestedPrivilegeLevel
328 * - {@link PrivilegeLevel} requested for the session
329 * @return {@link GetChannelAuthenticationCapabilitiesResponseData}
330 * @throws ConnectionException
331 * when connection is in the state that does not allow to
332 * perform this operation.
333 * @throws Exception
334 * when sending message to the managed system fails
335 */
336 public GetChannelAuthenticationCapabilitiesResponseData getChannelAuthenticationCapabilities(
337 int connection, CipherSuite cipherSuite,
338 PrivilegeLevel requestedPrivilegeLevel) throws Exception {
339 int tag = generateSessionlessTag();
340 GetChannelAuthenticationCapabilitiesResponseData responseData;
341 try {
342 responseData = connections.get(connection)
343 .getChannelAuthenticationCapabilities(tag, cipherSuite,
344 requestedPrivilegeLevel);
345 } catch (Exception e) {
346 freeTag(tag);
347 throw e;
348 }
349 freeTag(tag);
350 return responseData;
351 }
352
353 /**
354 * Initiates the session with the managed system. Must be performed after
355 * {@link #getChannelAuthenticationCapabilities(int, CipherSuite, PrivilegeLevel)}
356 *
357 * @param connection
358 * - index of the connection that starts the session
359 * @param cipherSuite
360 * - {@link CipherSuite} that will be used during the session
361 * @param privilegeLevel
362 * - requested {@link PrivilegeLevel} - most of the time it will
363 * be {@link PrivilegeLevel#User}
364 * @param username
365 * - the username
366 * @param password
367 * - the password matching the username
368 * @param bmcKey
369 * - the key that should be provided if the two-key
370 * authentication is enabled, null otherwise.
371 * @throws ConnectionException
372 * when connection is in the state that does not allow to
373 * perform this operation.
374 * @throws Exception
375 * when sending message to the managed system or initializing
376 * one of the cipherSuite's algorithms fails
377 */
378 public int startSession(int connection, CipherSuite cipherSuite,
379 PrivilegeLevel privilegeLevel, String username, String password,
380 byte[] bmcKey) throws Exception {
381 int sessionId;
382 int tag = generateSessionlessTag();
383 try {
384 sessionId = connections.get(connection).startSession(tag, cipherSuite,
385 privilegeLevel, username, password, bmcKey);
386 } catch (Exception e) {
387 freeTag(tag);
388 throw e;
389 }
390 freeTag(tag);
391
392 return sessionId;
393 }
394
395 /**
396 * Registers the listener so it will receive notifications from connection
397 *
398 * @param connection
399 * - index of the {@link Connection} to listen to
400 * @param listener
401 * - {@link ConnectionListener} to processResponse
402 */
403 public void registerListener(int connection, ConnectionListener listener) {
404 connections.get(connection).registerListener(listener);
405 }
406 }