Skip to content

Commit 69db23e

Browse files
committed
AI PoC: implement the remainder of NUT Networked protocol (as of NUT v2.8.5) for Java binding [networkupstools/nut#1350]
Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
1 parent 67d7fb1 commit 69db23e

6 files changed

Lines changed: 236 additions & 6 deletions

File tree

jNut/src/main/java/org/networkupstools/jnut/Client.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ public class Client {
7878
private StringLineSocket socket = null;
7979

8080

81+
/**
82+
* SSL configuration
83+
*/
84+
private SSLConfig sslConfig = null;
85+
8186
/**
8287
* Get the host name or address to which client is (or will be) connected.
8388
* @return Host name or address.
@@ -144,6 +149,14 @@ public void setPort(int port) {
144149

145150

146151

152+
public SSLConfig getSslConfig() {
153+
return sslConfig;
154+
}
155+
156+
public void setSslConfig(SSLConfig sslConfig) {
157+
this.sslConfig = sslConfig;
158+
}
159+
147160
/**
148161
* Default constructor.
149162
*/
@@ -206,6 +219,15 @@ public void connect() throws IOException, UnknownHostException, NutException
206219
disconnect();
207220

208221
socket = new StringLineSocket(host, port);
222+
223+
if (sslConfig != null) {
224+
String res = query("STARTTLS");
225+
if (res.startsWith("OK STARTTLS")) {
226+
socket.startTLS(sslConfig.createContext(), host, port);
227+
} else if (sslConfig.isForceSSL()) {
228+
throw new NutException("STARTTLS-FAILED", "Server does not support SSL but it is required");
229+
}
230+
}
209231

210232
authenticate();
211233
}
@@ -573,6 +595,14 @@ protected String[] list(String subcmd, String [] params) throws IOException, Nut
573595
}
574596

575597

598+
public String getTrackingResult(String id) throws IOException, NutException {
599+
String res = get("TRACKING", id);
600+
if (res == null) return null;
601+
// TRACKING <id> <status>
602+
String[] parts = res.split(" ");
603+
return parts.length >= 2 ? parts[1] : null;
604+
}
605+
576606
/**
577607
* Returns the list of available devices from the NUT server.
578608
* @return List of devices, empty if nothing,

jNut/src/main/java/org/networkupstools/jnut/Device.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,35 @@ public void login() throws IOException, NutException {
107107

108108
/**
109109
* This function doesn't do much by itself. It is used by <i>upsmon</i> to make
110-
* sure that master-level functions like FSD are available if necessary
110+
* sure that master-level functions like FSD are available if necessary.
111+
* <p>
112+
* NOTE: API changed since NUT 2.8.0 to replace MASTER with PRIMARY
113+
* (and backwards-compatible alias handling)
111114
* @throws IOException
112115
* @throws NutException
113116
*/
117+
public void primary() throws IOException, NutException {
118+
if(client!=null)
119+
{
120+
try {
121+
String res = client.query("PRIMARY", name);
122+
if(!res.startsWith("OK"))
123+
{
124+
throw new NutException(NutException.UnknownResponse, "Unknown response in Device.primary : " + res);
125+
}
126+
} catch (NutException ex) {
127+
// Retry with MASTER if PRIMARY failed
128+
master();
129+
}
130+
}
131+
}
132+
133+
/**
134+
* @deprecated Use primary() instead
135+
* @throws IOException
136+
* @throws NutException
137+
*/
138+
@Deprecated
114139
public void master() throws IOException, NutException {
115140
if(client!=null)
116141
{
@@ -163,10 +188,36 @@ public int getNumLogin() throws IOException, NutException {
163188
if(client!=null)
164189
{
165190
String res = client.get("NUMLOGINS", name);
166-
return res!=null?Integer.parseInt(res):-1;
191+
// NUMLOGINS <ups> <value>
192+
String[] parts = res.split(" ");
193+
return parts.length >= 1 ? Integer.parseInt(parts[0]) : -1;
167194
}
168195
return -1;
169196
}
197+
198+
/**
199+
* Return the list of clients which have done LOGIN for this UPS.
200+
* @return List of client hostnames.
201+
* @throws IOException
202+
* @throws NutException
203+
*/
204+
public String[] getClients() throws IOException, NutException {
205+
if(client!=null)
206+
{
207+
String[] res = client.list("CLIENT", name);
208+
if(res==null) return new String[0];
209+
ArrayList/*<String>*/ list = new ArrayList/*<String>*/();
210+
for(int i=0; i<res.length; i++)
211+
{
212+
// CLIENT <ups> <host>
213+
String[] parts = res[i].split(" ");
214+
if(parts.length >= 2)
215+
list.add(parts[1]);
216+
}
217+
return (String[])list.toArray(new String[list.size()]);
218+
}
219+
return null;
220+
}
170221

171222

172223
/**
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.networkupstools.jnut;
2+
3+
import javax.net.ssl.SSLContext;
4+
import java.security.NoSuchAlgorithmException;
5+
import java.security.KeyManagementException;
6+
7+
/**
8+
* Base class of SSL configuration for NUT connections.
9+
*/
10+
public abstract class SSLConfig {
11+
protected boolean forceSSL;
12+
protected boolean certVerify;
13+
14+
public SSLConfig(boolean forceSSL, boolean certVerify) {
15+
this.forceSSL = forceSSL;
16+
this.certVerify = certVerify;
17+
}
18+
19+
public boolean isForceSSL() {
20+
return forceSSL;
21+
}
22+
23+
public boolean isCertVerify() {
24+
return certVerify;
25+
}
26+
27+
public abstract SSLContext createContext() throws NutException;
28+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.networkupstools.jnut;
2+
3+
import javax.net.ssl.KeyManagerFactory;
4+
import javax.net.ssl.SSLContext;
5+
import javax.net.ssl.TrustManagerFactory;
6+
import java.io.FileInputStream;
7+
import java.security.KeyStore;
8+
9+
/**
10+
* SSL configuration with added options specific for Java KeyStore (JKS/PKCS12).
11+
*/
12+
public class SSLConfig_JKS extends SSLConfig {
13+
private String trustStorePath;
14+
private String trustStorePassword;
15+
private String keyStorePath;
16+
private String keyStorePassword;
17+
18+
public SSLConfig_JKS(boolean forceSSL, boolean certVerify,
19+
String trustStorePath, String trustStorePassword,
20+
String keyStorePath, String keyStorePassword) {
21+
super(forceSSL, certVerify);
22+
this.trustStorePath = trustStorePath;
23+
this.trustStorePassword = trustStorePassword;
24+
this.keyStorePath = keyStorePath;
25+
this.keyStorePassword = keyStorePassword;
26+
}
27+
28+
@Override
29+
public SSLContext createContext() throws NutException {
30+
try {
31+
SSLContext ctx = SSLContext.getInstance("TLS");
32+
TrustManagerFactory tmf = null;
33+
if (trustStorePath != null) {
34+
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
35+
ts.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray());
36+
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
37+
tmf.init(ts);
38+
}
39+
40+
KeyManagerFactory kmf = null;
41+
if (keyStorePath != null) {
42+
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
43+
ks.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray());
44+
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
45+
kmf.init(ks, keyStorePassword.toCharArray());
46+
}
47+
48+
ctx.init(kmf != null ? kmf.getKeyManagers() : null,
49+
tmf != null ? tmf.getTrustManagers() : null,
50+
null);
51+
return ctx;
52+
} catch (Exception e) {
53+
throw new NutException("Failed to create SSLContext", e.getMessage());
54+
}
55+
}
56+
}

jNut/src/main/java/org/networkupstools/jnut/StringLineSocket.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import java.io.OutputStreamWriter;
2525
import java.net.Socket;
2626
import java.net.UnknownHostException;
27+
import javax.net.ssl.SSLContext;
28+
import javax.net.ssl.SSLSocket;
29+
import javax.net.ssl.SSLSocketFactory;
2730

2831
/**
2932
* Class representing a socket, internally used to communicate with UPSD.
@@ -97,8 +100,16 @@ public void close() throws IOException{
97100
}
98101
}
99102

100-
/**
101-
* Test if the soecket is connected.
103+
public void startTLS(SSLContext sslContext, String host, int port) throws IOException {
104+
if (isConnected()) {
105+
SSLSocketFactory factory = sslContext.getSocketFactory();
106+
SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, host, port, true);
107+
sslSocket.startHandshake();
108+
socket = sslSocket;
109+
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
110+
writer = new OutputStreamWriter(socket.getOutputStream());
111+
}
112+
}
102113
* @return True if connected.
103114
*/
104115
public boolean isConnected() {

jNut/src/main/java/org/networkupstools/jnut/Variable.java

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,60 @@ public void setValue(String value) throws IOException, NutException {
115115
}
116116
}
117117
}
118-
119-
// TODO Add query for type, enum and range values
118+
119+
/**
120+
* Retrieve the variable type from UPSD.
121+
* @return Variable type string.
122+
* @throws IOException
123+
* @throws NutException
124+
*/
125+
public String getType() throws IOException, NutException {
126+
if(device!=null && device.getClient()!=null)
127+
{
128+
String[] params = {device.getName(), name};
129+
String res = device.getClient().get("TYPE", params);
130+
// TYPE <ups> <var> <type>
131+
return res;
132+
}
133+
return null;
134+
}
135+
136+
/**
137+
* Retrieve the list of possible values for an ENUM variable.
138+
* @return List of values.
139+
* @throws IOException
140+
* @throws NutException
141+
*/
142+
public String[] getEnumList() throws IOException, NutException {
143+
if(device!=null && device.getClient()!=null)
144+
{
145+
String[] params = {device.getName(), name};
146+
String[] res = device.getClient().list("ENUM", params);
147+
if(res == null) return new String[0];
148+
String[] list = new String[res.length];
149+
for(int i=0; i<res.length; i++) {
150+
// ENUM <ups> <var> "<value>"
151+
list[i] = Client.extractDoublequotedValue(res[i]);
152+
}
153+
return list;
154+
}
155+
return null;
156+
}
157+
158+
/**
159+
* Retrieve the list of possible ranges for a RANGE variable.
160+
* @return List of range strings or structured data.
161+
* @throws IOException
162+
* @throws NutException
163+
*/
164+
public String[] getRangeList() throws IOException, NutException {
165+
if(device!=null && device.getClient()!=null)
166+
{
167+
String[] params = {device.getName(), name};
168+
String[] res = device.getClient().list("RANGE", params);
169+
if(res == null) return new String[0];
170+
return res; // RANGE <ups> <var> "<min>" "<max>"
171+
}
172+
return null;
173+
}
120174
}

0 commit comments

Comments
 (0)