2 * collectd - src/netcmd.c
3 * Copyright (C) 2007-2011 Florian octo Forster
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
24 * Florian octo Forster <octo at collectd.org>
30 #include "configfile.h"
32 #include "utils_cmd_flush.h"
33 #include "utils_cmd_getval.h"
34 #include "utils_cmd_listval.h"
35 #include "utils_cmd_putval.h"
36 #include "utils_cmd_putnotif.h"
38 /* Folks without pthread will need to disable this plugin. */
41 #include <sys/socket.h>
49 #include <gnutls/gnutls.h>
51 #define NC_DEFAULT_SERVICE "25826"
52 #define NC_TLS_DH_BITS 1024
55 * Private data structures
68 _Bool tls_verify_peer;
70 gnutls_certificate_credentials_t tls_credentials;
71 gnutls_dh_params_t tls_dh_params;
72 gnutls_priority_t tls_priority;
75 typedef struct nc_peer_s nc_peer_t;
77 struct nc_connection_s
81 gnutls_session_t tls_session;
82 _Bool have_tls_session;
84 typedef struct nc_connection_s nc_connection_t;
90 /* socket configuration */
91 static nc_peer_t *peers = NULL;
92 static size_t peers_num;
94 static struct pollfd *pollfd = NULL;
95 static size_t pollfd_num;
97 static int listen_thread_loop = 0;
98 static int listen_thread_running = 0;
99 static pthread_t listen_thread;
104 static nc_peer_t *nc_fd_to_peer (int fd) /* {{{ */
108 for (i = 0; i < peers_num; i++)
112 for (j = 0; j < peers[i].fds_num; j++)
113 if (peers[i].fds[j] == fd)
118 } /* }}} nc_peer_t *nc_fd_to_peer */
120 static int nc_register_fd (nc_peer_t *peer, int fd) /* {{{ */
122 struct pollfd *poll_ptr;
125 poll_ptr = realloc (pollfd, (pollfd_num + 1) * sizeof (*pollfd));
126 if (poll_ptr == NULL)
128 ERROR ("netcmd plugin: realloc failed.");
133 memset (&pollfd[pollfd_num], 0, sizeof (pollfd[pollfd_num]));
134 pollfd[pollfd_num].fd = fd;
135 pollfd[pollfd_num].events = POLLIN | POLLPRI;
136 pollfd[pollfd_num].revents = 0;
142 fd_ptr = realloc (peer->fds, (peer->fds_num + 1) * sizeof (*peer->fds));
145 ERROR ("netcmd plugin: realloc failed.");
149 peer->fds[peer->fds_num] = fd;
153 } /* }}} int nc_register_fd */
155 static int nc_tls_init (nc_peer_t *peer) /* {{{ */
160 if ((peer->tls_cert_file == NULL)
161 || (peer->tls_key_file == NULL))
164 /* Initialize the structure holding our certificate information. */
165 gnutls_certificate_allocate_credentials (&peer->tls_credentials);
167 /* Set up the configured certificates. */
168 if (peer->tls_ca_file != NULL)
169 gnutls_certificate_set_x509_trust_file (peer->tls_credentials,
170 peer->tls_ca_file, GNUTLS_X509_FMT_PEM);
171 if (peer->tls_crl_file != NULL)
172 gnutls_certificate_set_x509_crl_file (peer->tls_credentials,
173 peer->tls_crl_file, GNUTLS_X509_FMT_PEM);
174 gnutls_certificate_set_x509_key_file (peer->tls_credentials,
175 peer->tls_cert_file, peer->tls_key_file, GNUTLS_X509_FMT_PEM);
177 /* Initialize Diffie-Hellman parameters. */
178 gnutls_dh_params_init (&peer->tls_dh_params);
179 gnutls_dh_params_generate2 (peer->tls_dh_params, NC_TLS_DH_BITS);
180 gnutls_certificate_set_dh_params (peer->tls_credentials,
181 peer->tls_dh_params);
183 /* Initialize a "priority cache". This will tell GNUTLS which algorithms to
184 * use and which to avoid. We use the "NORMAL" method for now. */
185 gnutls_priority_init (&peer->tls_priority,
186 /* priority = */ "NORMAL", /* errpos = */ NULL);
189 } /* }}} int nc_tls_init */
191 static gnutls_session_t nc_tls_get_session (nc_peer_t *peer) /* {{{ */
193 gnutls_session_t session;
195 if (peer->tls_credentials == NULL)
198 /* Initialize new session. */
199 gnutls_init (&session, GNUTLS_SERVER);
201 /* Set cipher priority and credentials based on the information stored with
203 gnutls_priority_set (session, peer->tls_priority);
204 gnutls_credentials_set (session,
205 GNUTLS_CRD_CERTIFICATE, peer->tls_credentials);
207 /* Request the client certificate. */
208 gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUEST);
211 } /* }}} gnutls_session_t nc_tls_get_session */
213 static int nc_open_socket (nc_peer_t *peer) /* {{{ */
215 struct addrinfo ai_hints;
216 struct addrinfo *ai_list;
217 struct addrinfo *ai_ptr;
220 const char *node = NULL;
221 const char *service = NULL;
226 service = peer->service;
230 service = NC_DEFAULT_SERVICE;
232 memset (&ai_hints, 0, sizeof (ai_hints));
234 ai_hints.ai_flags |= AI_PASSIVE;
237 ai_hints.ai_flags |= AI_ADDRCONFIG;
239 ai_hints.ai_family = AF_UNSPEC;
240 ai_hints.ai_socktype = SOCK_STREAM;
245 service = NC_DEFAULT_SERVICE;
247 status = getaddrinfo (node, service, &ai_hints, &ai_list);
250 ERROR ("netcmd plugin: getaddrinfo failed: %s",
251 gai_strerror (status));
255 for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
260 fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
261 ai_ptr->ai_protocol);
264 ERROR ("netcmd plugin: socket(2) failed: %s",
265 sstrerror (errno, errbuf, sizeof (errbuf)));
269 status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
273 ERROR ("netcmd plugin: bind(2) failed: %s",
274 sstrerror (errno, errbuf, sizeof (errbuf)));
278 status = listen (fd, /* backlog = */ 8);
282 ERROR ("netcmd plugin: listen(2) failed: %s",
283 sstrerror (errno, errbuf, sizeof (errbuf)));
287 status = nc_register_fd (peer, fd);
293 } /* for (ai_next) */
295 freeaddrinfo (ai_list);
297 return (nc_tls_init (peer));
298 } /* }}} int nc_open_socket */
300 static void nc_connection_close (nc_connection_t *conn) /* {{{ */
311 if (conn->have_tls_session)
313 gnutls_deinit (conn->tls_session);
314 conn->have_tls_session = 0;
318 } /* }}} void nc_connection_close */
320 static void *nc_handle_client (void *arg) /* {{{ */
322 nc_connection_t *conn;
328 DEBUG ("netcmd plugin: nc_handle_client: Reading from fd #%i", conn->fd);
330 fhin = fdopen (conn->fd, "r");
333 ERROR ("netcmd plugin: fdopen failed: %s",
334 sstrerror (errno, errbuf, sizeof (errbuf)));
335 nc_connection_close (conn);
336 pthread_exit ((void *) 1);
339 /* FIXME: dup conn->fd before calling fdopen! */
340 fhout = fdopen (conn->fd, "w");
341 /* Prevent nc_connection_close from calling close(2) on this fd. */
345 ERROR ("netcmd plugin: fdopen failed: %s",
346 sstrerror (errno, errbuf, sizeof (errbuf)));
347 fclose (fhin); /* this closes fd as well */
348 nc_connection_close (conn);
349 pthread_exit ((void *) 1);
352 /* change output buffer to line buffered mode */
353 if (setvbuf (fhout, NULL, _IOLBF, 0) != 0)
355 ERROR ("netcmd plugin: setvbuf failed: %s",
356 sstrerror (errno, errbuf, sizeof (errbuf)));
357 nc_connection_close (conn);
358 pthread_exit ((void *) 1);
364 char buffer_copy[1024];
370 if (fgets (buffer, sizeof (buffer), fhin) == NULL)
374 WARNING ("netcmd plugin: failed to read from socket #%i: %s",
376 sstrerror (errno, errbuf, sizeof (errbuf)));
381 len = strlen (buffer);
383 && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
384 buffer[--len] = '\0';
389 sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
391 fields_num = strsplit (buffer_copy, fields,
392 sizeof (fields) / sizeof (fields[0]));
396 nc_connection_close (conn);
400 if (strcasecmp (fields[0], "getval") == 0)
402 handle_getval (fhout, buffer);
404 else if (strcasecmp (fields[0], "putval") == 0)
406 handle_putval (fhout, buffer);
408 else if (strcasecmp (fields[0], "listval") == 0)
410 handle_listval (fhout, buffer);
412 else if (strcasecmp (fields[0], "putnotif") == 0)
414 handle_putnotif (fhout, buffer);
416 else if (strcasecmp (fields[0], "flush") == 0)
418 handle_flush (fhout, buffer);
422 if (fprintf (fhout, "-1 Unknown command: %s\n", fields[0]) < 0)
424 WARNING ("netcmd plugin: failed to write to socket #%i: %s",
426 sstrerror (errno, errbuf, sizeof (errbuf)));
430 } /* while (fgets) */
432 DEBUG ("netcmd plugin: nc_handle_client: Exiting..");
433 /* XXX: Is this calling close on the same FD twice? */
436 nc_connection_close (conn);
438 pthread_exit ((void *) 0);
440 } /* }}} void *nc_handle_client */
442 static void *nc_server_thread (void __attribute__((unused)) *arg) /* {{{ */
446 pthread_attr_t th_attr;
450 for (i = 0; i < peers_num; i++)
451 nc_open_socket (peers + i);
454 nc_open_socket (NULL);
458 ERROR ("netcmd plugin: No sockets could be opened.");
459 pthread_exit ((void *) -1);
462 while (listen_thread_loop != 0)
464 status = poll (pollfd, (nfds_t) pollfd_num, /* timeout = */ -1);
467 if ((errno == EINTR) || (errno == EAGAIN))
470 ERROR ("netcmd plugin: poll(2) failed: %s",
471 sstrerror (errno, errbuf, sizeof (errbuf)));
472 listen_thread_loop = 0;
476 for (i = 0; i < pollfd_num; i++)
479 nc_connection_t *conn;
481 if (pollfd[i].revents == 0)
485 else if ((pollfd[i].revents & (POLLERR | POLLHUP | POLLNVAL))
488 WARNING ("netcmd plugin: File descriptor %i failed.",
490 close (pollfd[i].fd);
492 pollfd[i].events = 0;
493 pollfd[i].revents = 0;
496 pollfd[i].revents = 0;
498 peer = nc_fd_to_peer (pollfd[i].fd);
501 ERROR ("netcmd plugin: Unable to find peer structure for file "
502 "descriptor #%i.", pollfd[i].fd);
506 status = accept (pollfd[i].fd,
507 /* sockaddr = */ NULL,
508 /* sockaddr_len = */ NULL);
512 ERROR ("netcmd plugin: accept failed: %s",
513 sstrerror (errno, errbuf, sizeof (errbuf)));
517 conn = malloc (sizeof (*conn));
520 ERROR ("netcmd plugin: malloc failed.");
524 memset (conn, 0, sizeof (*conn));
528 && (peer->tls_cert_file != NULL))
530 DEBUG ("netcmd plugin: Starting TLS session on [%s]:%s",
531 (peer->node != NULL) ? peer->node : "any",
532 (peer->service != NULL) ? peer->service : NC_DEFAULT_SERVICE);
533 conn->tls_session = nc_tls_get_session (peer);
534 conn->have_tls_session = 1;
537 DEBUG ("Spawning child to handle connection on fd %i", conn->fd);
539 pthread_attr_init (&th_attr);
540 pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
542 status = pthread_create (&th, &th_attr, nc_handle_client,
546 WARNING ("netcmd plugin: pthread_create failed: %s",
547 sstrerror (errno, errbuf, sizeof (errbuf)));
548 nc_connection_close (conn);
552 } /* while (listen_thread_loop) */
554 for (i = 0; i < pollfd_num; i++)
556 if (pollfd[i].fd < 0)
559 close (pollfd[i].fd);
561 pollfd[i].events = 0;
562 pollfd[i].revents = 0;
569 } /* }}} void *nc_server_thread */
576 * TLSCertFile "/path/to/cert"
577 * TLSKeyFile "/path/to/key"
578 * TLSCAFile "/path/to/ca"
579 * TLSCRLFile "/path/to/crl"
580 * TLSVerifyPeer yes|no
584 static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
589 p = realloc (peers, sizeof (*peers) * (peers_num + 1));
592 ERROR ("netcmd plugin: realloc failed.");
596 p = peers + peers_num;
597 memset (p, 0, sizeof (*p));
600 p->tls_cert_file = NULL;
601 p->tls_key_file = NULL;
602 p->tls_ca_file = NULL;
603 p->tls_crl_file = NULL;
604 p->tls_verify_peer = 1;
606 for (i = 0; i < ci->children_num; i++)
608 oconfig_item_t *child = ci->children + i;
610 if (strcasecmp ("Address", child->key) == 0)
611 cf_util_get_string (child, &p->node);
612 else if (strcasecmp ("Port", child->key) == 0)
613 cf_util_get_string (child, &p->service);
614 else if (strcasecmp ("TLSCertFile", child->key) == 0)
615 cf_util_get_string (child, &p->tls_cert_file);
616 else if (strcasecmp ("TLSKeyFile", child->key) == 0)
617 cf_util_get_string (child, &p->tls_key_file);
618 else if (strcasecmp ("TLSCAFile", child->key) == 0)
619 cf_util_get_string (child, &p->tls_ca_file);
620 else if (strcasecmp ("TLSCRLFile", child->key) == 0)
621 cf_util_get_string (child, &p->tls_crl_file);
623 WARNING ("netcmd plugin: The option \"%s\" is not recognized within "
624 "a \"%s\" block.", child->key, ci->key);
627 DEBUG ("netcmd plugin: node = \"%s\"; service = \"%s\";", p->node, p->service);
632 } /* }}} int nc_config_peer */
634 static int nc_config (oconfig_item_t *ci)
638 for (i = 0; i < ci->children_num; i++)
640 oconfig_item_t *child = ci->children + i;
642 if (strcasecmp ("Listen", child->key) == 0)
643 nc_config_peer (child);
645 WARNING ("netcmd plugin: The option \"%s\" is not recognized.",
650 } /* int nc_config */
652 static int nc_init (void)
654 static int have_init = 0;
658 /* Initialize only once. */
663 listen_thread_loop = 1;
665 status = pthread_create (&listen_thread, NULL, nc_server_thread, NULL);
669 listen_thread_loop = 0;
670 listen_thread_running = 0;
671 ERROR ("netcmd plugin: pthread_create failed: %s",
672 sstrerror (errno, errbuf, sizeof (errbuf)));
676 listen_thread_running = 1;
680 static int nc_shutdown (void)
684 listen_thread_loop = 0;
686 if (listen_thread != (pthread_t) 0)
688 pthread_kill (listen_thread, SIGTERM);
689 pthread_join (listen_thread, &ret);
690 listen_thread = (pthread_t) 0;
693 plugin_unregister_init ("netcmd");
694 plugin_unregister_shutdown ("netcmd");
697 } /* int nc_shutdown */
699 void module_register (void)
701 plugin_register_complex_config ("netcmd", nc_config);
702 plugin_register_init ("netcmd", nc_init);
703 plugin_register_shutdown ("netcmd", nc_shutdown);
704 } /* void module_register (void) */
706 /* vim: set sw=2 sts=2 tw=78 et fdm=marker : */