1 /*
2 * MIT License
3 *
4 * Copyright(c) 2019 Intel Corporation. All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * Authors:
25 * Kamil Wiatrowski <kamilx.wiatrowski@intel.com>
26 *
27 */
28
29 #include "collectd.h"
30
31 #include "plugin.h"
32 #include "utils/common/common.h"
33 #include "utils/dmi/dmi.h"
34
35 #include <microhttpd.h>
36 #if MHD_VERSION >= 0x00097002
37 #define MHD_RESULT enum MHD_Result
38 #else
39 #define MHD_RESULT int
40 #endif
41
42 #include <jansson.h>
43 #include <netdb.h>
44
45 #define CAP_PLUGIN "capabilities"
46 #define CONTENT_TYPE_JSON "application/json"
47 #define LISTEN_BACKLOG 16
48
49 typedef struct dmi_type_name_s {
50 dmi_type type;
51 const char *name;
52 } dmi_type_name_t;
53
54 static char *g_cap_json = NULL;
55 static char *httpd_host = NULL;
56 static unsigned short httpd_port = 9104;
57 static struct MHD_Daemon *httpd;
58
59 static dmi_type_name_t types_list[] = {
60 {BIOS, "BIOS"},
61 {SYSTEM, "SYSTEM"},
62 {BASEBOARD, "BASEBOARD"},
63 {PROCESSOR, "PROCESSORS"},
64 {CACHE, "CACHE"},
65 {PHYSICAL_MEMORY_ARRAY, "PHYSICAL MEMORY ARRAYS"},
66 {MEMORY_DEVICE, "MEMORY DEVICES"},
67 {IPMI_DEVICE, "IPMI DEVICE"},
68 {ONBOARD_DEVICES_EXTENDED_INFORMATION,
69 "ONBOARD DEVICES EXTENDED INFORMATION"}};
70
cap_get_dmi_variables(json_t * parent,const dmi_type type,const char * json_name)71 static int cap_get_dmi_variables(json_t *parent, const dmi_type type,
72 const char *json_name) {
73 DEBUG(CAP_PLUGIN ": cap_get_dmi_variables: %d/%s.", type, json_name);
74 dmi_reader_t reader;
75 if (dmi_reader_init(&reader, type) != DMI_OK) {
76 ERROR(CAP_PLUGIN ": dmi_reader_init failed.");
77 return -1;
78 }
79
80 json_t *section = NULL;
81 json_t *entries = NULL;
82 json_t *attributes = NULL;
83 json_t *arr = json_array();
84 if (arr == NULL) {
85 ERROR(CAP_PLUGIN ": Failed to allocate json array.");
86 dmi_reader_clean(&reader);
87 return -1;
88 }
89 if (json_object_set_new(parent, json_name, arr)) {
90 ERROR(CAP_PLUGIN ": Failed to set array to parent.");
91 dmi_reader_clean(&reader);
92 return -1;
93 }
94
95 while (reader.current_type != DMI_ENTRY_END) {
96 int status = dmi_read_next(&reader);
97 if (status != DMI_OK) {
98 ERROR(CAP_PLUGIN ": dmi_read_next failed.");
99 return -1;
100 }
101
102 switch (reader.current_type) {
103
104 case DMI_ENTRY_NAME:
105 DEBUG("%s", reader.name);
106 attributes = NULL;
107 section = json_object();
108 if (section == NULL) {
109 ERROR(CAP_PLUGIN ": Failed to allocate json object.");
110 dmi_reader_clean(&reader);
111 return -1;
112 }
113 if (json_array_append_new(arr, section)) {
114 ERROR(CAP_PLUGIN ": Failed to append json entry.");
115 dmi_reader_clean(&reader);
116 return -1;
117 }
118 entries = json_object();
119 if (entries == NULL) {
120 ERROR(CAP_PLUGIN ": Failed to allocate json object.");
121 dmi_reader_clean(&reader);
122 return -1;
123 }
124 if (json_object_set_new(section, reader.name, entries)) {
125 ERROR(CAP_PLUGIN ": Failed to set json entry.");
126 dmi_reader_clean(&reader);
127 return -1;
128 }
129 break;
130
131 case DMI_ENTRY_MAP:
132 DEBUG(" %s:%s", reader.name, reader.value);
133 attributes = NULL;
134 if (entries == NULL) {
135 ERROR(CAP_PLUGIN ": unexpected dmi output format.");
136 dmi_reader_clean(&reader);
137 return -1;
138 }
139 if (json_object_set_new(entries, reader.name,
140 json_string(reader.value))) {
141 ERROR(CAP_PLUGIN ": Failed to set json object for entries.");
142 dmi_reader_clean(&reader);
143 return -1;
144 }
145 break;
146
147 case DMI_ENTRY_LIST_NAME:
148 DEBUG(" %s:", reader.name);
149 if (entries == NULL) {
150 ERROR(CAP_PLUGIN ": unexpected dmi output format.");
151 dmi_reader_clean(&reader);
152 return -1;
153 }
154 attributes = json_array();
155 if (attributes == NULL) {
156 ERROR(CAP_PLUGIN ": Failed to allocate json array for attributes.");
157 dmi_reader_clean(&reader);
158 return -1;
159 }
160 if (json_object_set_new(entries, reader.name, attributes)) {
161 ERROR(CAP_PLUGIN ": Failed to set json object for entry %s.",
162 reader.name);
163 dmi_reader_clean(&reader);
164 return -1;
165 }
166 break;
167
168 case DMI_ENTRY_LIST_VALUE:
169 DEBUG(" %s", reader.value);
170 if (attributes == NULL) {
171 ERROR(CAP_PLUGIN ": unexpected dmi output format");
172 dmi_reader_clean(&reader);
173 return -1;
174 }
175 if (json_array_append_new(attributes, json_string(reader.value))) {
176 ERROR(CAP_PLUGIN ": Failed to append json attribute.");
177 dmi_reader_clean(&reader);
178 return -1;
179 }
180 break;
181
182 default:
183 section = NULL;
184 entries = NULL;
185 attributes = NULL;
186 break;
187 }
188 }
189
190 return 0;
191 }
192
193 /* http_handler is the callback called by the microhttpd library. It essentially
194 * handles all HTTP request aspects and creates an HTTP response. */
cap_http_handler(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** connection_state)195 static MHD_RESULT cap_http_handler(void *cls, struct MHD_Connection *connection,
196 const char *url, const char *method,
197 const char *version, const char *upload_data,
198 size_t *upload_data_size,
199 void **connection_state) {
200 if (strcmp(method, MHD_HTTP_METHOD_GET) != 0) {
201 return MHD_NO;
202 }
203
204 /* On the first call for each connection, return without anything further.
205 * The first time only the headers are valid, do not respond in the first
206 * round. The docs are not very specific on the issue. */
207 if (*connection_state == NULL) {
208 /* set to a random non-NULL pointer. */
209 *connection_state = &(int){44};
210 return MHD_YES;
211 }
212 DEBUG(CAP_PLUGIN ": formatted response: %s", g_cap_json);
213
214 #if defined(MHD_VERSION) && MHD_VERSION >= 0x00090500
215 struct MHD_Response *res = MHD_create_response_from_buffer(
216 strlen(g_cap_json), g_cap_json, MHD_RESPMEM_PERSISTENT);
217 #else
218 struct MHD_Response *res =
219 MHD_create_response_from_data(strlen(g_cap_json), g_cap_json, 0, 0);
220 #endif
221 if (res == NULL) {
222 ERROR(CAP_PLUGIN ": MHD create response failed.");
223 return MHD_NO;
224 }
225
226 MHD_add_response_header(res, MHD_HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON);
227 int status = MHD_queue_response(connection, MHD_HTTP_OK, res);
228
229 MHD_destroy_response(res);
230
231 return status;
232 }
233
cap_logger(void * arg,char const * fmt,va_list ap)234 static void cap_logger(__attribute__((unused)) void *arg, char const *fmt,
235 va_list ap) {
236 char errbuf[1024];
237 vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
238
239 ERROR(CAP_PLUGIN ": libmicrohttpd: %s", errbuf);
240 }
241
242 #if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000
cap_open_socket()243 static int cap_open_socket() {
244 char service[NI_MAXSERV];
245 snprintf(service, sizeof(service), "%hu", httpd_port);
246
247 struct addrinfo *res;
248 int status = getaddrinfo(httpd_host, service,
249 &(struct addrinfo){
250 .ai_flags = AI_PASSIVE | AI_ADDRCONFIG,
251 .ai_family = PF_INET,
252 .ai_socktype = SOCK_STREAM,
253 },
254 &res);
255 if (status != 0) {
256 return -1;
257 }
258
259 int fd = -1;
260 for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) {
261 int flags = ai->ai_socktype;
262 #ifdef SOCK_CLOEXEC
263 flags |= SOCK_CLOEXEC;
264 #endif
265
266 fd = socket(ai->ai_family, flags, 0);
267 if (fd == -1)
268 continue;
269
270 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) != 0) {
271 WARNING(CAP_PLUGIN ": setsockopt(SO_REUSEADDR) failed: %s", STRERRNO);
272 close(fd);
273 fd = -1;
274 continue;
275 }
276
277 if (bind(fd, ai->ai_addr, ai->ai_addrlen) != 0) {
278 INFO(CAP_PLUGIN ": bind failed: %s", STRERRNO);
279 close(fd);
280 fd = -1;
281 continue;
282 }
283
284 if (listen(fd, LISTEN_BACKLOG) != 0) {
285 INFO(CAP_PLUGIN ": listen failed: %s", STRERRNO);
286 close(fd);
287 fd = -1;
288 continue;
289 }
290
291 char str_node[NI_MAXHOST];
292 char str_service[NI_MAXSERV];
293
294 getnameinfo(ai->ai_addr, ai->ai_addrlen, str_node, sizeof(str_node),
295 str_service, sizeof(str_service),
296 NI_NUMERICHOST | NI_NUMERICSERV);
297
298 INFO(CAP_PLUGIN ": Listening on [%s]:%s.", str_node, str_service);
299 break;
300 }
301
302 freeaddrinfo(res);
303
304 return fd;
305 }
306 #endif
307
cap_start_daemon()308 static struct MHD_Daemon *cap_start_daemon() {
309 #if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000
310 int fd = cap_open_socket();
311 if (fd == -1) {
312 ERROR(CAP_PLUGIN ": Opening a listening socket for [%s]:%hu failed.",
313 (httpd_host != NULL) ? httpd_host : "0.0.0.0", httpd_port);
314 return NULL;
315 }
316 #endif
317 unsigned int flags = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG;
318 #if defined(MHD_VERSION) && MHD_VERSION >= 0x00095300
319 flags |= MHD_USE_POLL_INTERNAL_THREAD;
320 #endif
321
322 struct MHD_Daemon *d = MHD_start_daemon(
323 flags, httpd_port, NULL, NULL, cap_http_handler, NULL,
324 #if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000
325 MHD_OPTION_LISTEN_SOCKET, fd,
326 #endif
327 MHD_OPTION_EXTERNAL_LOGGER, cap_logger, NULL, MHD_OPTION_END);
328
329 if (d == NULL)
330 ERROR(CAP_PLUGIN ": MHD_start_daemon() failed.");
331
332 return d;
333 }
334
cap_config(oconfig_item_t * ci)335 static int cap_config(oconfig_item_t *ci) {
336 int status = 0;
337 for (int i = 0; i < ci->children_num; i++) {
338 oconfig_item_t *child = ci->children + i;
339
340 if (strcasecmp("Host", child->key) == 0) {
341 #if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000
342 status = cf_util_get_string(child, &httpd_host);
343 #else
344 ERROR(CAP_PLUGIN ": Option `Host' not supported. Please upgrade "
345 "libmicrohttpd to at least 0.9.0");
346 status = -1;
347 #endif
348 } else if (strcasecmp("Port", child->key) == 0) {
349 int port = cf_util_get_port_number(child);
350 if (port > 0)
351 httpd_port = (unsigned short)port;
352 else {
353 ERROR(CAP_PLUGIN ": Wrong port number, correct range is 1-65535.");
354 status = -1;
355 }
356 } else {
357 ERROR(CAP_PLUGIN ": Unknown configuration option \"%s\".", child->key);
358 status = -1;
359 }
360
361 if (status) {
362 ERROR(CAP_PLUGIN ": Invalid configuration parameter \"%s\".", child->key);
363 sfree(httpd_host);
364 break;
365 }
366 }
367
368 return status;
369 }
370
cap_shutdown()371 static int cap_shutdown() {
372 if (httpd != NULL) {
373 MHD_stop_daemon(httpd);
374 httpd = NULL;
375 }
376
377 sfree(httpd_host);
378 sfree(g_cap_json);
379 return 0;
380 }
381
cap_init(void)382 static int cap_init(void) {
383 json_t *root = json_object();
384 if (root == NULL) {
385 ERROR(CAP_PLUGIN ": Failed to allocate json root.");
386 cap_shutdown();
387 return -1;
388 }
389
390 for (int i = 0; i < STATIC_ARRAY_SIZE(types_list); i++)
391 if (cap_get_dmi_variables(root, types_list[i].type, types_list[i].name)) {
392 json_decref(root);
393 cap_shutdown();
394 return -1;
395 }
396
397 g_cap_json = json_dumps(root, JSON_COMPACT);
398 json_decref(root);
399
400 if (g_cap_json == NULL) {
401 ERROR(CAP_PLUGIN ": json_dumps() failed.");
402 cap_shutdown();
403 return -1;
404 }
405
406 httpd = cap_start_daemon();
407 if (httpd == NULL) {
408 cap_shutdown();
409 return -1;
410 }
411
412 return 0;
413 }
414
module_register(void)415 void module_register(void) {
416 plugin_register_complex_config(CAP_PLUGIN, cap_config);
417 plugin_register_init(CAP_PLUGIN, cap_init);
418 plugin_register_shutdown(CAP_PLUGIN, cap_shutdown);
419 }
420