1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 package com.zabbix.gateway;
21 
22 import java.util.HashMap;
23 
24 import javax.management.MBeanAttributeInfo;
25 import javax.management.MBeanInfo;
26 import javax.management.MBeanServerConnection;
27 import javax.management.ObjectName;
28 import javax.management.openmbean.CompositeData;
29 import javax.management.openmbean.TabularDataSupport;
30 import javax.management.remote.JMXConnector;
31 import javax.management.remote.JMXServiceURL;
32 
33 import org.json.*;
34 
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 
38 class JMXItemChecker extends ItemChecker
39 {
40 	private static final Logger logger = LoggerFactory.getLogger(JMXItemChecker.class);
41 
42 	private JMXServiceURL url;
43 	private JMXConnector jmxc;
44 	private MBeanServerConnection mbsc;
45 
46 	private String username;
47 	private String password;
48 
JMXItemChecker(JSONObject request)49 	public JMXItemChecker(JSONObject request) throws ZabbixException
50 	{
51 		super(request);
52 
53 		try
54 		{
55 			String conn = request.getString(JSON_TAG_CONN);
56 			int port = request.getInt(JSON_TAG_PORT);
57 
58 			url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://[" + conn + "]:" + port + "/jmxrmi");
59 			jmxc = null;
60 			mbsc = null;
61 
62 			username = request.optString(JSON_TAG_USERNAME, null);
63 			password = request.optString(JSON_TAG_PASSWORD, null);
64 
65 			if (null != username && null == password || null == username && null != password)
66 				throw new IllegalArgumentException("invalid username and password nullness combination");
67 		}
68 		catch (Exception e)
69 		{
70 			throw new ZabbixException(e);
71 		}
72 	}
73 
74 	@Override
getValues()75 	public JSONArray getValues() throws ZabbixException
76 	{
77 		JSONArray values = new JSONArray();
78 
79 		try
80 		{
81 			HashMap<String, String[]> env = null;
82 
83 			if (null != username && null != password)
84 			{
85 				env = new HashMap<String, String[]>();
86 				env.put(JMXConnector.CREDENTIALS, new String[] {username, password});
87 			}
88 
89 			jmxc = ZabbixJMXConnectorFactory.connect(url, env);
90 			mbsc = jmxc.getMBeanServerConnection();
91 
92 			for (String key : keys)
93 				values.put(getJSONValue(key));
94 		}
95 		catch (Exception e)
96 		{
97 			throw new ZabbixException(e);
98 		}
99 		finally
100 		{
101 			try { if (null != jmxc) jmxc.close(); } catch (java.io.IOException exception) { }
102 
103 			jmxc = null;
104 			mbsc = null;
105 		}
106 
107 		return values;
108 	}
109 
110 	@Override
getStringValue(String key)111 	protected String getStringValue(String key) throws Exception
112 	{
113 		ZabbixItem item = new ZabbixItem(key);
114 
115 		if (item.getKeyId().equals("jmx"))
116 		{
117 			if (2 != item.getArgumentCount())
118 				throw new ZabbixException("required key format: jmx[<object name>,<attribute name>]");
119 
120 			ObjectName objectName = new ObjectName(item.getArgument(1));
121 			String attributeName = item.getArgument(2);
122 			String realAttributeName;
123 			String fieldNames = "";
124 
125 			// Attribute name and composite data field names are separated by dots. On the other hand the
126 			// name may contain a dot too. In this case user needs to escape it with a backslash. Also the
127 			// backslash symbols in the name must be escaped. So a real separator is unescaped dot and
128 			// separatorIndex() is used to locate it.
129 
130 			int sep = HelperFunctionChest.separatorIndex(attributeName);
131 
132 			if (-1 != sep)
133 			{
134 				logger.trace("'{}' contains composite data", attributeName);
135 
136 				realAttributeName = attributeName.substring(0, sep);
137 				fieldNames = attributeName.substring(sep + 1);
138 			}
139 			else
140 				realAttributeName = attributeName;
141 
142 			// unescape possible dots or backslashes that were escaped by user
143 			realAttributeName = HelperFunctionChest.unescapeUserInput(realAttributeName);
144 
145 			logger.trace("attributeName:'{}'", realAttributeName);
146 			logger.trace("fieldNames:'{}'", fieldNames);
147 
148 			return getPrimitiveAttributeValue(mbsc.getAttribute(objectName, realAttributeName), fieldNames);
149 		}
150 		else if (item.getKeyId().equals("jmx.discovery"))
151 		{
152 			if (0 != item.getArgumentCount())
153 				throw new ZabbixException("required key format: jmx.discovery");
154 
155 			JSONArray counters = new JSONArray();
156 
157 			for (ObjectName name : mbsc.queryNames(null, null))
158 			{
159 				logger.trace("discovered object '{}'", name);
160 
161 				for (MBeanAttributeInfo attrInfo : mbsc.getMBeanInfo(name).getAttributes())
162 				{
163 					logger.trace("discovered attribute '{}'", attrInfo.getName());
164 
165 					if (!attrInfo.isReadable())
166 					{
167 						logger.trace("attribute not readable, skipping");
168 						continue;
169 					}
170 
171 					try
172 					{
173 						logger.trace("looking for attributes of primitive types");
174 						String descr = (attrInfo.getName().equals(attrInfo.getDescription()) ? null : attrInfo.getDescription());
175 						findPrimitiveAttributes(counters, name, descr, attrInfo.getName(), mbsc.getAttribute(name, attrInfo.getName()));
176 					}
177 					catch (Exception e)
178 					{
179 						Object[] logInfo = {name, attrInfo.getName(), e};
180 						logger.trace("processing '{},{}' failed", logInfo);
181 					}
182 				}
183 			}
184 
185 			JSONObject mapping = new JSONObject();
186 			mapping.put(ItemChecker.JSON_TAG_DATA, counters);
187 			return mapping.toString();
188 		}
189 		else
190 			throw new ZabbixException("key ID '%s' is not supported", item.getKeyId());
191 	}
192 
getPrimitiveAttributeValue(Object dataObject, String fieldNames)193 	private String getPrimitiveAttributeValue(Object dataObject, String fieldNames) throws ZabbixException
194 	{
195 		logger.trace("drilling down with data object '{}' and field names '{}'", dataObject, fieldNames);
196 
197 		if (null == dataObject)
198 			throw new ZabbixException("data object is null");
199 
200 		if (fieldNames.equals(""))
201 		{
202 			if (isPrimitiveAttributeType(dataObject.getClass()))
203 				return dataObject.toString();
204 			else
205 				throw new ZabbixException("data object type is not primitive: %s", dataObject.getClass());
206 		}
207 
208 		if (dataObject instanceof CompositeData)
209 		{
210 			logger.trace("'{}' contains composite data", dataObject);
211 
212 			CompositeData comp = (CompositeData)dataObject;
213 
214 			String dataObjectName;
215 			String newFieldNames = "";
216 
217 			int sep = HelperFunctionChest.separatorIndex(fieldNames);
218 
219 			if (-1 != sep)
220 			{
221 				dataObjectName = fieldNames.substring(0, sep);
222 				newFieldNames = fieldNames.substring(sep + 1);
223 			}
224 			else
225 				dataObjectName = fieldNames;
226 
227 			// unescape possible dots or backslashes that were escaped by user
228 			dataObjectName = HelperFunctionChest.unescapeUserInput(dataObjectName);
229 
230 			return getPrimitiveAttributeValue(comp.get(dataObjectName), newFieldNames);
231 		}
232 		else
233 			throw new ZabbixException("unsupported data object type along the path: %s", dataObject.getClass());
234 	}
235 
findPrimitiveAttributes(JSONArray counters, ObjectName name, String descr, String attrPath, Object attribute)236 	private void findPrimitiveAttributes(JSONArray counters, ObjectName name, String descr, String attrPath, Object attribute) throws JSONException
237 	{
238 		logger.trace("drilling down with attribute path '{}'", attrPath);
239 
240 		if (isPrimitiveAttributeType(attribute.getClass()))
241 		{
242 			logger.trace("found attribute of a primitive type: {}", attribute.getClass());
243 
244 			JSONObject counter = new JSONObject();
245 
246 			counter.put("{#JMXDESC}", null == descr ? name + "," + attrPath : descr);
247 			counter.put("{#JMXOBJ}", name);
248 			counter.put("{#JMXATTR}", attrPath);
249 			counter.put("{#JMXTYPE}", attribute.getClass().getName());
250 			counter.put("{#JMXVALUE}", attribute.toString());
251 
252 			counters.put(counter);
253 		}
254 		else if (attribute instanceof CompositeData)
255 		{
256 			logger.trace("found attribute of a composite type: {}", attribute.getClass());
257 
258 			CompositeData comp = (CompositeData)attribute;
259 
260 			for (String key : comp.getCompositeType().keySet())
261 				findPrimitiveAttributes(counters, name, descr, attrPath + "." + key, comp.get(key));
262 		}
263 		else if (attribute instanceof TabularDataSupport || attribute.getClass().isArray())
264 		{
265 			logger.trace("found attribute of a known, unsupported type: {}", attribute.getClass());
266 		}
267 		else
268 			logger.trace("found attribute of an unknown, unsupported type: {}", attribute.getClass());
269 	}
270 
isPrimitiveAttributeType(Class<?> clazz)271 	private boolean isPrimitiveAttributeType(Class<?> clazz)
272 	{
273 		Class<?>[] clazzez = {Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class,
274 			Float.class, Double.class, String.class, java.math.BigDecimal.class, java.math.BigInteger.class,
275 			java.util.Date.class, javax.management.ObjectName.class};
276 
277 		return HelperFunctionChest.arrayContains(clazzez, clazz);
278 	}
279 }
280