bc052ed99c947170a43822b0f57a42822398cbfa
[collectd.git] / src / netcmd.c
1 /**
2  * collectd - src/netcmd.c
3  * Copyright (C) 2007-2013  Florian octo Forster
4  *
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:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
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.
22  *
23  * Author:
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30 #include "configfile.h"
31
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"
37
38 /* Folks without pthread will need to disable this plugin. */
39 #include <pthread.h>
40
41 #include <sys/socket.h>
42 #include <sys/poll.h>
43 #include <netdb.h>
44 #include <sys/stat.h>
45 #include <sys/un.h>
46
47 #include <grp.h>
48
49 #include <gnutls/gnutls.h>
50
51 #define NC_DEFAULT_SERVICE "25826"
52 #define NC_TLS_DH_BITS 1024
53
54 /*
55  * Private data structures
56  */
57 struct nc_peer_s /* {{{ */
58 {
59   char *node;
60   char *service;
61   int *fds;
62   size_t fds_num;
63
64   char *tls_cert_file;
65   char *tls_key_file;
66   char *tls_ca_file;
67   char *tls_crl_file;
68   _Bool tls_verify_peer;
69
70   gnutls_certificate_credentials_t tls_credentials;
71   gnutls_dh_params_t tls_dh_params;
72   gnutls_priority_t tls_priority;
73 }; /* }}} */
74 typedef struct nc_peer_s nc_peer_t;
75
76 #if defined(PAGESIZE)
77 # define NC_READ_BUFFER_SIZE PAGESIZE
78 #elif defined(PAGE_SIZE)
79 # define NC_READ_BUFFER_SIZE PAGE_SIZE
80 #else
81 # define NC_READ_BUFFER_SIZE 4096
82 #endif
83
84 struct nc_connection_s /* {{{ */
85 {
86   /* TLS fields */
87   int fd;
88   char *read_buffer;
89   size_t read_buffer_fill;
90
91   /* non-TLS fields */
92   FILE *fh_in;
93   FILE *fh_out;
94
95   gnutls_session_t tls_session;
96   _Bool have_tls_session;
97   _Bool tls_verify_peer;
98 }; /* }}} */
99 typedef struct nc_connection_s nc_connection_t;
100
101 struct nc_proxy_s
102 {
103   int pipe_rx;
104   int pipe_tx;
105
106   gnutls_session_t tls_session;
107 };
108 typedef struct nc_proxy_s nc_proxy_t;
109
110 /*
111  * Private variables
112  */
113
114 /* socket configuration */
115 static nc_peer_t *peers = NULL;
116 static size_t     peers_num;
117
118 static struct pollfd  *pollfd = NULL;
119 static size_t          pollfd_num;
120
121 static _Bool     listen_thread_loop = 0;
122 static _Bool     listen_thread_running = 0;
123 static pthread_t listen_thread;
124
125 /*
126  * Functions
127  */
128 static const char *nc_verify_status_to_string (gnutls_certificate_status_t status)
129 {
130   if (status == 0)
131     return ("Valid");
132   else if (status & GNUTLS_CERT_INVALID)
133     return ("Invalid");
134   else if (status & GNUTLS_CERT_REVOKED)
135     return ("Revoked");
136   else if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
137     return ("Signer not found");
138   else if (status & GNUTLS_CERT_SIGNER_NOT_CA)
139     return ("Signer not a CA");
140   else if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
141     return ("Insecure algorithm");
142 #if GNUTLS_VERSION_NUMBER >= 0x020708
143   else if (status & GNUTLS_CERT_NOT_ACTIVATED)
144     return ("Not activated");
145   else if (status & GNUTLS_CERT_EXPIRED)
146     return ("Expired");
147 #endif
148   else
149     return (NULL);
150 } /* }}} const char *nc_verify_status_to_string */
151
152 static void *nc_proxy_thread (void *args) /* {{{ */
153 {
154   nc_proxy_t *data = args;
155   struct pollfd fds[2];
156   int gtls_fd;
157   long pagesize;
158
159   gtls_fd = (int) gnutls_transport_get_ptr (data->tls_session);
160   DEBUG ("netcmd plugin: nc_proxy_thread: pipe_rx = %i; pipe_tx = %i; gtls_fd = %i;",
161       data->pipe_rx, data->pipe_tx, gtls_fd);
162
163   memset (fds, 0, sizeof (fds));
164   fds[0].fd = data->pipe_rx;
165   fds[0].events = POLLIN | POLLPRI;
166   fds[1].fd = gtls_fd;
167   fds[1].events = POLLIN | POLLPRI;
168
169   pagesize = sysconf (_SC_PAGESIZE);
170
171   while (42)
172   {
173     char errbuf[1024];
174     char buffer[pagesize];
175     int status;
176
177     status = poll (fds, STATIC_ARRAY_SIZE(fds), /* timeout = */ -1);
178     if (status < 0)
179     {
180       if ((errno == EINTR) || (errno == EAGAIN))
181         continue;
182       ERROR ("netcmd plugin: poll(2) failed: %s",
183           sstrerror (errno, errbuf, sizeof (errbuf)));
184       break;
185     }
186
187     /* pipe -> TLS */
188     if (fds[0].revents != 0) /* {{{ */
189     {
190       ssize_t iostatus;
191       size_t buffer_size;
192       char *buffer_ptr;
193
194       DEBUG ("netcmd plugin: nc_proxy_thread: Something's up on the pipe.");
195
196       /* Check for hangup, error, ... */
197       if ((fds[0].revents & (POLLIN | POLLPRI)) == 0)
198         break;
199
200       iostatus = read (fds[0].fd, buffer, sizeof (buffer));
201       DEBUG ("netcmd plugin: nc_proxy_thread: Received %zi bytes from pipe.",
202           iostatus);
203       if (iostatus < 0)
204       {
205         if ((errno == EINTR) || (errno == EAGAIN))
206           continue;
207         ERROR ("netcmd plugin: read(2) failed: %s",
208             sstrerror (errno, errbuf, sizeof (errbuf)));
209         break;
210       }
211       else if (iostatus == 0)
212       {
213         break;
214       }
215
216       buffer_ptr = buffer;
217       buffer_size = (size_t) iostatus;
218       while (buffer_size > 0)
219       {
220         iostatus = gnutls_record_send (data->tls_session,
221             buffer, buffer_size);
222         DEBUG ("netcmd plugin: nc_proxy_thread: Wrote %zi bytes to GNU-TLS.",
223             iostatus);
224         if (iostatus < 0)
225         {
226           ERROR ("netcmd plugin: gnutls_record_send failed: %s",
227               gnutls_strerror ((int) iostatus));
228           break;
229         }
230
231         assert (iostatus <= buffer_size);
232         buffer_ptr += iostatus;
233         buffer_size -= iostatus;
234       } /* while (buffer_size > 0) */
235
236       if (buffer_size != 0)
237         break;
238
239       fds[0].revents = 0;
240     } /* }}} if (fds[0].revents != 0) */
241
242     /* TLS -> pipe */
243     if (fds[1].revents != 0) /* {{{ */
244     {
245       ssize_t iostatus;
246       size_t buffer_size;
247
248       DEBUG ("netcmd plugin: nc_proxy_thread: Something's up on the TLS socket.");
249
250       /* Check for hangup, error, ... */
251       if ((fds[1].revents & (POLLIN | POLLPRI)) == 0)
252         break;
253
254       iostatus = gnutls_record_recv (data->tls_session, buffer, sizeof (buffer));
255       DEBUG ("netcmd plugin: nc_proxy_thread: Received %zi bytes from GNU-TLS.",
256           iostatus);
257       if (iostatus < 0)
258       {
259         if ((iostatus == GNUTLS_E_INTERRUPTED)
260             || (iostatus == GNUTLS_E_AGAIN))
261           continue;
262         ERROR ("netcmd plugin: gnutls_record_recv failed: %s",
263             gnutls_strerror ((int) iostatus));
264         break;
265       }
266       else if (iostatus == 0)
267       {
268         break;
269       }
270
271       buffer_size = (size_t) iostatus;
272       iostatus = swrite (data->pipe_tx, buffer, buffer_size);
273       DEBUG ("netcmd plugin: nc_proxy_thread:  Wrote %zi bytes to pipe.",
274           iostatus);
275
276       fds[1].revents = 0;
277     } /* }}} if (fds[1].revents != 0) */
278   } /* while (42) */
279
280   DEBUG ("netcmd plugin: nc_proxy_thread: Shutting down.");
281   return (NULL);
282 } /* }}} void *nc_proxy_thread */
283
284 /* Creates two pipes and a separate thread to pass data between two FILE* and
285  * the GNUTLS back and forth. This is required because the handle_<cmd>
286  * functions expect to be able to write to a FILE*. */
287 static int nc_start_tls_file_handles (nc_connection_t *conn) /* {{{ */
288 {
289 #define BAIL_OUT(status) do { \
290   DEBUG ("netcmd plugin: nc_start_tls_file_handles: Bailing out with status %i.", (status)); \
291   if (proxy_config->pipe_rx >= 0) { close (proxy_config->pipe_rx); }         \
292   if (proxy_config->pipe_tx >= 0) { close (proxy_config->pipe_tx); }         \
293   if (conn->fh_in != NULL) { fclose (conn->fh_in); conn->fh_in = NULL; }     \
294   if (conn->fh_out != NULL) { fclose (conn->fh_out); conn->fh_out = NULL; }  \
295   free (proxy_config);                                                       \
296   return (status);                                                           \
297 } while (0)
298
299   nc_proxy_t *proxy_config;
300   int pipe_fd[2];
301   int status;
302
303   pthread_attr_t attr;
304   pthread_t thread;
305
306   if ((conn->fh_in != NULL) || (conn->fh_out != NULL))
307   {
308     ERROR ("netcmd plugin: nc_start_tls_file_handles: Connection already connected.");
309     return (EEXIST);
310   }
311
312   proxy_config = malloc (sizeof (*proxy_config));
313   if (proxy_config == NULL)
314   {
315     ERROR ("netcmd plugin: malloc failed.");
316     return (ENOMEM);
317   }
318   memset (proxy_config, 0, sizeof (*proxy_config));
319   proxy_config->pipe_rx = -1;
320   proxy_config->pipe_tx = -1;
321   proxy_config->tls_session = conn->tls_session;
322
323   pipe_fd[0] = pipe_fd[1] = -1;
324   status = pipe (pipe_fd);
325   if (status != 0)
326   {
327     char errmsg[1024];
328     ERROR ("netcmd plugin: pipe(2) failed: %s",
329         sstrerror (errno, errmsg, sizeof (errmsg)));
330     BAIL_OUT (-1);
331   }
332   proxy_config->pipe_rx = pipe_fd[0];
333   conn->fh_out = fdopen (pipe_fd[1], "w");
334   if (conn->fh_out == NULL)
335   {
336     char errmsg[1024];
337     ERROR ("netcmd plugin: fdopen(2) failed: %s",
338         sstrerror (errno, errmsg, sizeof (errmsg)));
339     close (pipe_fd[1]);
340     BAIL_OUT (-1);
341   }
342
343   pipe_fd[0] = pipe_fd[1] = -1;
344   status = pipe (pipe_fd);
345   if (status != 0)
346   {
347     char errmsg[1024];
348     ERROR ("netcmd plugin: pipe(2) failed: %s",
349         sstrerror (errno, errmsg, sizeof (errmsg)));
350     BAIL_OUT (-1);
351   }
352   proxy_config->pipe_tx = pipe_fd[1];
353   conn->fh_in = fdopen (pipe_fd[0], "r");
354   if (conn->fh_in == NULL)
355   {
356     char errmsg[1024];
357     ERROR ("netcmd plugin: fdopen(2) failed: %s",
358         sstrerror (errno, errmsg, sizeof (errmsg)));
359     close (pipe_fd[0]);
360     BAIL_OUT (-1);
361   }
362
363   pthread_attr_init (&attr);
364   pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
365
366   status = pthread_create (&thread, &attr, nc_proxy_thread, proxy_config);
367   pthread_attr_destroy (&attr);
368   if (status != 0)
369   {
370     char errmsg[1024];
371     ERROR ("netcmd plugin: pthread_create(2) failed: %s",
372         sstrerror (errno, errmsg, sizeof (errmsg)));
373     BAIL_OUT (-1);
374   }
375
376   DEBUG ("netcmd plugin: nc_start_tls_file_handles: Successfully started proxy thread.");
377   return (0);
378 } /* }}} int nc_start_tls_file_handles */
379
380 static nc_peer_t *nc_fd_to_peer (int fd) /* {{{ */
381 {
382   size_t i;
383
384   for (i = 0; i < peers_num; i++)
385   {
386     size_t j;
387
388     for (j = 0; j < peers[i].fds_num; j++)
389       if (peers[i].fds[j] == fd)
390         return (peers + i);
391   }
392
393   return (NULL);
394 } /* }}} nc_peer_t *nc_fd_to_peer */
395
396 static void nc_free_peer (nc_peer_t *p) /* {{{ */
397 {
398   size_t i;
399   if (p == NULL)
400     return;
401
402   sfree (p->node);
403   sfree (p->service);
404
405   for (i = 0; i < p->fds_num; i++)
406   {
407     if (p->fds[i] >= 0)
408       close (p->fds[i]);
409     p->fds[i] = -1;
410   }
411   p->fds_num = 0;
412   sfree (p->fds);
413
414   sfree (p->tls_cert_file);
415   sfree (p->tls_key_file);
416   sfree (p->tls_ca_file);
417   sfree (p->tls_crl_file);
418
419   gnutls_certificate_free_credentials (p->tls_credentials);
420   gnutls_dh_params_deinit (p->tls_dh_params);
421   gnutls_priority_deinit (p->tls_priority);
422 } /* }}} void nc_free_peer */
423
424 static int nc_register_fd (nc_peer_t *peer, int fd) /* {{{ */
425 {
426   struct pollfd *poll_ptr;
427   int *fd_ptr;
428
429   poll_ptr = realloc (pollfd, (pollfd_num + 1) * sizeof (*pollfd));
430   if (poll_ptr == NULL)
431   {
432     ERROR ("netcmd plugin: realloc failed.");
433     return (-1);
434   }
435   pollfd = poll_ptr;
436
437   memset (&pollfd[pollfd_num], 0, sizeof (pollfd[pollfd_num]));
438   pollfd[pollfd_num].fd = fd;
439   pollfd[pollfd_num].events = POLLIN | POLLPRI;
440   pollfd[pollfd_num].revents = 0;
441   pollfd_num++;
442
443   if (peer == NULL)
444     return (0);
445
446   fd_ptr = realloc (peer->fds, (peer->fds_num + 1) * sizeof (*peer->fds));
447   if (fd_ptr == NULL)
448   {
449     ERROR ("netcmd plugin: realloc failed.");
450     return (-1);
451   }
452   peer->fds = fd_ptr;
453   peer->fds[peer->fds_num] = fd;
454   peer->fds_num++;
455
456   return (0);
457 } /* }}} int nc_register_fd */
458
459 static int nc_tls_init (nc_peer_t *peer) /* {{{ */
460 {
461   int status;
462
463   if (peer == NULL)
464     return (EINVAL);
465
466   if (peer->tls_key_file == NULL)
467   {
468     DEBUG ("netcmd plugin: Not setting up TLS environment for peer.");
469     return (0);
470   }
471
472   DEBUG ("netcmd plugin: Setting up TLS environment for peer.");
473
474   /* Initialize the structure holding our certificate information. */
475   status = gnutls_certificate_allocate_credentials (&peer->tls_credentials);
476   if (status != GNUTLS_E_SUCCESS)
477   {
478     ERROR ("netcmd plugin: gnutls_certificate_allocate_credentials failed: %s",
479         gnutls_strerror (status));
480     return (status);
481   }
482
483   /* Set up the configured certificates. */
484   if (peer->tls_ca_file != NULL)
485   {
486     status = gnutls_certificate_set_x509_trust_file (peer->tls_credentials,
487         peer->tls_ca_file, GNUTLS_X509_FMT_PEM);
488     if (status < 0)
489     {
490       ERROR ("netcmd plugin: gnutls_certificate_set_x509_trust_file (%s) "
491           "failed: %s",
492           peer->tls_ca_file, gnutls_strerror (status));
493       return (status);
494     }
495     else
496     {
497       DEBUG ("netcmd plugin: Successfully loaded %i CA(s).", status);
498     }
499   }
500
501   if (peer->tls_crl_file != NULL)
502   {
503     status = gnutls_certificate_set_x509_crl_file (peer->tls_credentials,
504         peer->tls_crl_file, GNUTLS_X509_FMT_PEM);
505     if (status < 0)
506     {
507       ERROR ("netcmd plugin: gnutls_certificate_set_x509_crl_file (%s) "
508           "failed: %s",
509           peer->tls_crl_file, gnutls_strerror (status));
510       return (status);
511     }
512     else
513     {
514       DEBUG ("netcmd plugin: Successfully loaded %i CRL(s).", status);
515     }
516   }
517
518   status = gnutls_certificate_set_x509_key_file (peer->tls_credentials,
519       peer->tls_cert_file, peer->tls_key_file, GNUTLS_X509_FMT_PEM);
520   if (status != GNUTLS_E_SUCCESS)
521   {
522     ERROR ("netcmd plugin: gnutls_certificate_set_x509_key_file failed: %s",
523         gnutls_strerror (status));
524     return (status);
525   }
526
527   /* Initialize Diffie-Hellman parameters. */
528   gnutls_dh_params_init (&peer->tls_dh_params);
529   gnutls_dh_params_generate2 (peer->tls_dh_params, NC_TLS_DH_BITS);
530   gnutls_certificate_set_dh_params (peer->tls_credentials,
531       peer->tls_dh_params);
532
533   /* Initialize a "priority cache". This will tell GNUTLS which algorithms to
534    * use and which to avoid. We use the "NORMAL" method for now. */
535   gnutls_priority_init (&peer->tls_priority,
536      /* priority = */ "NORMAL", /* errpos = */ NULL);
537
538   return (0);
539 } /* }}} int nc_tls_init */
540
541 static gnutls_session_t nc_tls_get_session (nc_peer_t *peer) /* {{{ */
542 {
543   gnutls_session_t session;
544   int status;
545
546   if (peer->tls_credentials == NULL)
547     return (NULL);
548
549   DEBUG ("netcmd plugin: nc_tls_get_session (%s)", peer->node);
550
551   /* Initialize new session. */
552   gnutls_init (&session, GNUTLS_SERVER);
553
554   /* Set cipher priority and credentials based on the information stored with
555    * the peer. */
556   status = gnutls_priority_set (session, peer->tls_priority);
557   if (status != GNUTLS_E_SUCCESS)
558   {
559     ERROR ("netcmd plugin: gnutls_priority_set failed: %s",
560         gnutls_strerror (status));
561     gnutls_deinit (session);
562     return (NULL);
563   }
564
565   status = gnutls_credentials_set (session,
566       GNUTLS_CRD_CERTIFICATE, peer->tls_credentials);
567   if (status != GNUTLS_E_SUCCESS)
568   {
569     ERROR ("netcmd plugin: gnutls_credentials_set failed: %s",
570         gnutls_strerror (status));
571     gnutls_deinit (session);
572     return (NULL);
573   }
574
575   /* Request the client certificate. If TLSVerifyPeer is set to true,
576    * *require* a client certificate. */
577   gnutls_certificate_server_set_request (session,
578       peer->tls_verify_peer ? GNUTLS_CERT_REQUIRE : GNUTLS_CERT_REQUEST);
579
580   return (session);
581 } /* }}} gnutls_session_t nc_tls_get_session */
582
583 static int nc_open_socket (nc_peer_t *peer) /* {{{ */
584 {
585   struct addrinfo ai_hints;
586   struct addrinfo *ai_list;
587   struct addrinfo *ai_ptr;
588   int status;
589
590   const char *node = NULL;
591   const char *service = NULL;
592
593   if (peer != NULL)
594   {
595     node = peer->node;
596     service = peer->service;
597   }
598
599   if (service == NULL)
600     service = NC_DEFAULT_SERVICE;
601
602   memset (&ai_hints, 0, sizeof (ai_hints));
603 #ifdef AI_PASSIVE
604   ai_hints.ai_flags |= AI_PASSIVE;
605 #endif
606 #ifdef AI_ADDRCONFIG
607   ai_hints.ai_flags |= AI_ADDRCONFIG;
608 #endif
609   ai_hints.ai_family = AF_UNSPEC;
610   ai_hints.ai_socktype = SOCK_STREAM;
611
612   ai_list = NULL;
613
614   if (service == NULL)
615     service = NC_DEFAULT_SERVICE;
616
617   status = getaddrinfo (node, service, &ai_hints, &ai_list);
618   if (status != 0)
619   {
620     ERROR ("netcmd plugin: getaddrinfo failed: %s",
621         gai_strerror (status));
622     return (-1);
623   }
624
625   for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
626   {
627     char errbuf[1024];
628     int fd;
629
630     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
631         ai_ptr->ai_protocol);
632     if (fd < 0)
633     {
634       ERROR ("netcmd plugin: socket(2) failed: %s",
635           sstrerror (errno, errbuf, sizeof (errbuf)));
636       continue;
637     }
638
639     status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
640     if (status != 0)
641     {
642       close (fd);
643       ERROR ("netcmd plugin: bind(2) failed: %s",
644           sstrerror (errno, errbuf, sizeof (errbuf)));
645       continue;
646     }
647
648     status = listen (fd, /* backlog = */ 8);
649     if (status != 0)
650     {
651       close (fd);
652       ERROR ("netcmd plugin: listen(2) failed: %s",
653           sstrerror (errno, errbuf, sizeof (errbuf)));
654       continue;
655     }
656
657     status = nc_register_fd (peer, fd);
658     if (status != 0)
659     {
660       close (fd);
661       continue;
662     }
663   } /* for (ai_next) */
664
665   freeaddrinfo (ai_list);
666
667   return (nc_tls_init (peer));
668 } /* }}} int nc_open_socket */
669
670 static void nc_connection_close (nc_connection_t *conn) /* {{{ */
671 {
672   if (conn == NULL)
673     return;
674
675   if (conn->fd >= 0)
676   {
677     close (conn->fd);
678     conn->fd = -1;
679   }
680
681   if (conn->fh_in != NULL)
682   {
683     fclose (conn->fh_in);
684     conn->fh_in = NULL;
685   }
686
687   if (conn->fh_out != NULL)
688   {
689     fclose (conn->fh_out);
690     conn->fh_out = NULL;
691   }
692
693   if (conn->have_tls_session)
694   {
695     gnutls_deinit (conn->tls_session);
696     conn->have_tls_session = 0;
697   }
698
699   sfree (conn);
700 } /* }}} void nc_connection_close */
701
702 static int nc_connection_init_tls (nc_connection_t *conn) /* {{{ */
703 {
704   int status;
705   intptr_t fd;
706
707   conn->read_buffer = malloc (NC_READ_BUFFER_SIZE);
708   if (conn->read_buffer == NULL)
709     return (ENOMEM);
710   memset (conn->read_buffer, 0, NC_READ_BUFFER_SIZE);
711
712   /* Make (relatively) sure that 'fd' and 'void*' have the same size to make
713    * GCC happy. */
714   fd = (intptr_t) conn->fd;
715   gnutls_transport_set_ptr (conn->tls_session,
716       (gnutls_transport_ptr_t) fd);
717
718   while (42)
719   {
720     status = gnutls_handshake (conn->tls_session);
721     if (status == GNUTLS_E_SUCCESS)
722       break;
723     else if ((status == GNUTLS_E_AGAIN) || (status == GNUTLS_E_INTERRUPTED))
724       continue;
725     else
726     {
727       ERROR ("netcmd plugin: gnutls_handshake failed: %s",
728           gnutls_strerror (status));
729       return (status);
730     }
731   }
732
733   if (conn->tls_verify_peer)
734   {
735     unsigned int verify_status = 0;
736
737     status = gnutls_certificate_verify_peers2 (conn->tls_session,
738         &verify_status);
739     if (status != GNUTLS_E_SUCCESS)
740     {
741       ERROR ("netcmd plugin: gnutls_certificate_verify_peers2 failed: %s",
742           gnutls_strerror (status));
743       return (status);
744     }
745
746     if (verify_status != 0)
747     {
748       const char *reason;
749
750       reason = nc_verify_status_to_string (verify_status);
751       if (reason == NULL)
752         ERROR ("netcmd plugin: Verification of peer failed with "
753             "status %i (%#x)", verify_status, verify_status);
754       else
755         ERROR ("netcmd plugin: Verification of peer failed with "
756             "status %i (%s)", verify_status, reason);
757
758       return (-1);
759     }
760   } /* if (conn->tls_verify_peer) */
761
762   status = nc_start_tls_file_handles (conn);
763   if (status != 0)
764   {
765     nc_connection_close (conn);
766     return (-1);
767   }
768
769   return (0);
770 } /* }}} int nc_connection_init_tls */
771
772 static int nc_connection_init (nc_connection_t *conn) /* {{{ */
773 {
774   int fd_copy;
775   char errbuf[1024];
776
777   if (conn->have_tls_session)
778     return (nc_connection_init_tls (conn));
779
780   /* Duplicate the file descriptor. We need two file descriptors, because we
781    * create two FILE* objects. If they pointed to the same FD and we called
782    * fclose() on each, that would call close() twice on the same FD. If
783    * another file is opened in between those two calls, it could get assigned
784    * that FD and weird stuff would happen. */
785   fd_copy = dup (conn->fd);
786   if (fd_copy < 0)
787   {
788     ERROR ("netcmd plugin: dup(2) failed: %s",
789         sstrerror (errno, errbuf, sizeof (errbuf)));
790     return (-1);
791   }
792
793   conn->fh_in  = fdopen (conn->fd, "r");
794   if (conn->fh_in == NULL)
795   {
796     ERROR ("netcmd plugin: fdopen failed: %s",
797         sstrerror (errno, errbuf, sizeof (errbuf)));
798     return (-1);
799   }
800   /* Prevent other code from using the FD directly. */
801   conn->fd = -1;
802
803   conn->fh_out = fdopen (fd_copy, "w");
804   /* Prevent nc_connection_close from calling close(2) on this fd. */
805   if (conn->fh_out == NULL)
806   {
807     ERROR ("netcmd plugin: fdopen failed: %s",
808         sstrerror (errno, errbuf, sizeof (errbuf)));
809     return (-1);
810   }
811
812   /* change output buffer to line buffered mode */
813   if (setvbuf (conn->fh_out, NULL, _IOLBF, 0) != 0)
814   {
815     ERROR ("netcmd plugin: setvbuf failed: %s",
816         sstrerror (errno, errbuf, sizeof (errbuf)));
817     nc_connection_close (conn);
818     return (-1);
819   }
820
821   return (0);
822 } /* }}} int nc_connection_init */
823
824 static char *nc_connection_gets (nc_connection_t *conn, /* {{{ */
825     char *buffer, size_t buffer_size)
826 {
827   ssize_t status;
828   char *orig_buffer = buffer;
829
830   if (conn == NULL)
831   {
832     errno = EINVAL;
833     return (NULL);
834   }
835
836   if (!conn->have_tls_session)
837     return (fgets (buffer, (int) buffer_size, conn->fh_in));
838
839   if ((buffer == NULL) || (buffer_size < 2))
840   {
841     errno = EINVAL;
842     return (NULL);
843   }
844
845   /* ensure null termination */
846   memset (buffer, 0, buffer_size);
847   buffer_size--;
848
849   while (42)
850   {
851     size_t max_copy_bytes;
852     size_t newline_pos;
853     _Bool found_newline;
854     size_t i;
855
856     /* If there's no more data in the read buffer, read another chunk from the
857      * socket. */
858     if (conn->read_buffer_fill < 1)
859     {
860       status = gnutls_record_recv (conn->tls_session,
861           conn->read_buffer, NC_READ_BUFFER_SIZE);
862       if (status < 0) /* error */
863       {
864         ERROR ("netcmd plugin: Error while reading from TLS stream.");
865         return (NULL);
866       }
867       else if (status == 0) /* we reached end of file */
868       {
869         if (orig_buffer == buffer) /* nothing has been written to the buffer yet */
870           return (NULL); /* end of file */
871         else
872           return (orig_buffer);
873       }
874       else
875       {
876         conn->read_buffer_fill = (size_t) status;
877       }
878     }
879     assert (conn->read_buffer_fill > 0);
880
881     /* Determine where the first newline character is in the buffer. We're not
882      * using strcspn(3) here, becaus the buffer is possibly not
883      * null-terminated. */
884     newline_pos = conn->read_buffer_fill;
885     found_newline = 0;
886     for (i = 0; i < conn->read_buffer_fill; i++)
887     {
888       if (conn->read_buffer[i] == '\n')
889       {
890         newline_pos = i;
891         found_newline = 1;
892         break;
893       }
894     }
895
896     /* Determine how many bytes to copy at most. This is MIN(buffer available,
897      * read buffer size, characters to newline). */
898     max_copy_bytes = buffer_size;
899     if (max_copy_bytes > conn->read_buffer_fill)
900       max_copy_bytes = conn->read_buffer_fill;
901     if (max_copy_bytes > (newline_pos + 1))
902       max_copy_bytes = newline_pos + 1;
903     assert (max_copy_bytes > 0);
904
905     /* Copy bytes to the output buffer. */
906     memcpy (buffer, conn->read_buffer, max_copy_bytes);
907     buffer += max_copy_bytes;
908     assert (buffer_size >= max_copy_bytes);
909     buffer_size -= max_copy_bytes;
910
911     /* If there is data left in the read buffer, move it to the front of the
912      * buffer. */
913     if (max_copy_bytes < conn->read_buffer_fill)
914     {
915       size_t data_left_size = conn->read_buffer_fill - max_copy_bytes;
916       memmove (conn->read_buffer, conn->read_buffer + max_copy_bytes,
917           data_left_size);
918       conn->read_buffer_fill -= max_copy_bytes;
919     }
920     else
921     {
922       assert (max_copy_bytes == conn->read_buffer_fill);
923       conn->read_buffer_fill = 0;
924     }
925
926     if (found_newline)
927       break;
928
929     if (buffer_size == 0) /* no more space in the output buffer */
930       break;
931   }
932
933   return (orig_buffer);
934 } /* }}} char *nc_connection_gets */
935
936 static void *nc_handle_client (void *arg) /* {{{ */
937 {
938   nc_connection_t *conn;
939   char errbuf[1024];
940   int status;
941
942   conn = arg;
943
944   DEBUG ("netcmd plugin: nc_handle_client: Reading from fd #%i", conn->fd);
945
946   status = nc_connection_init (conn);
947   if (status != 0)
948   {
949     nc_connection_close (conn);
950     pthread_exit ((void *) 1);
951   }
952
953   while (42)
954   {
955     char buffer[1024];
956     char buffer_copy[1024];
957     char *fields[128];
958     int   fields_num;
959     int   len;
960
961     errno = 0;
962     if (nc_connection_gets (conn, buffer, sizeof (buffer)) == NULL)
963     {
964       if (errno != 0)
965       {
966         WARNING ("netcmd plugin: failed to read from socket #%i: %s",
967             fileno (conn->fh_in),
968             sstrerror (errno, errbuf, sizeof (errbuf)));
969       }
970       break;
971     }
972
973     len = strlen (buffer);
974     while ((len > 0)
975         && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
976       buffer[--len] = '\0';
977
978     if (len == 0)
979       continue;
980
981     sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
982
983     fields_num = strsplit (buffer_copy, fields,
984         sizeof (fields) / sizeof (fields[0]));
985
986     if (fields_num < 1)
987     {
988       nc_connection_close (conn);
989       break;
990     }
991
992     if (strcasecmp (fields[0], "getval") == 0)
993     {
994       handle_getval (conn->fh_out, buffer);
995     }
996     else if (strcasecmp (fields[0], "putval") == 0)
997     {
998       handle_putval (conn->fh_out, buffer);
999     }
1000     else if (strcasecmp (fields[0], "listval") == 0)
1001     {
1002       handle_listval (conn->fh_out, buffer);
1003     }
1004     else if (strcasecmp (fields[0], "putnotif") == 0)
1005     {
1006       handle_putnotif (conn->fh_out, buffer);
1007     }
1008     else if (strcasecmp (fields[0], "flush") == 0)
1009     {
1010       handle_flush (conn->fh_out, buffer);
1011     }
1012     else
1013     {
1014       if (fprintf (conn->fh_out, "-1 Unknown command: %s\n", fields[0]) < 0)
1015       {
1016         WARNING ("netcmd plugin: failed to write to socket #%i: %s",
1017             fileno (conn->fh_out),
1018             sstrerror (errno, errbuf, sizeof (errbuf)));
1019         break;
1020       }
1021     }
1022   } /* while (fgets) */
1023
1024   DEBUG ("netcmd plugin: nc_handle_client: Exiting..");
1025   nc_connection_close (conn);
1026
1027   pthread_exit ((void *) 0);
1028   return ((void *) 0);
1029 } /* }}} void *nc_handle_client */
1030
1031 static void *nc_server_thread (void __attribute__((unused)) *arg) /* {{{ */
1032 {
1033   int  status;
1034   pthread_t th;
1035   pthread_attr_t th_attr;
1036   char errbuf[1024];
1037   size_t i;
1038
1039   for (i = 0; i < peers_num; i++)
1040     nc_open_socket (peers + i);
1041
1042   if (peers_num == 0)
1043     nc_open_socket (NULL);
1044
1045   if (pollfd_num == 0)
1046   {
1047     ERROR ("netcmd plugin: No sockets could be opened.");
1048     pthread_exit ((void *) -1);
1049   }
1050
1051   while (listen_thread_loop)
1052   {
1053     status = poll (pollfd, (nfds_t) pollfd_num, /* timeout = */ -1);
1054     if (status < 0)
1055     {
1056       if ((errno == EINTR) || (errno == EAGAIN))
1057         continue;
1058
1059       ERROR ("netcmd plugin: poll(2) failed: %s",
1060           sstrerror (errno, errbuf, sizeof (errbuf)));
1061       listen_thread_loop = 0;
1062       continue;
1063     }
1064
1065     for (i = 0; i < pollfd_num; i++)
1066     {
1067       nc_peer_t *peer;
1068       nc_connection_t *conn;
1069
1070       if (pollfd[i].revents == 0)
1071       {
1072         continue;
1073       }
1074       else if ((pollfd[i].revents & (POLLERR | POLLHUP | POLLNVAL))
1075           != 0)
1076       {
1077         WARNING ("netcmd plugin: File descriptor %i failed.",
1078             pollfd[i].fd);
1079         close (pollfd[i].fd);
1080         pollfd[i].fd = -1;
1081         pollfd[i].events = 0;
1082         pollfd[i].revents = 0;
1083         continue;
1084       }
1085       pollfd[i].revents = 0;
1086
1087       peer = nc_fd_to_peer (pollfd[i].fd);
1088       if (peer == NULL)
1089       {
1090         ERROR ("netcmd plugin: Unable to find peer structure for file "
1091             "descriptor #%i.", pollfd[i].fd);
1092         continue;
1093       }
1094
1095       status = accept (pollfd[i].fd,
1096           /* sockaddr = */ NULL,
1097           /* sockaddr_len = */ NULL);
1098       if (status < 0)
1099       {
1100         if (errno != EINTR)
1101           ERROR ("netcmd plugin: accept failed: %s",
1102               sstrerror (errno, errbuf, sizeof (errbuf)));
1103         continue;
1104       }
1105
1106       conn = malloc (sizeof (*conn));
1107       if (conn == NULL)
1108       {
1109         ERROR ("netcmd plugin: malloc failed.");
1110         close (status);
1111         continue;
1112       }
1113       memset (conn, 0, sizeof (*conn));
1114       conn->fh_in = NULL;
1115       conn->fh_out = NULL;
1116
1117       conn->fd = status;
1118
1119       /* Start up the TLS session if the required configuration options have
1120        * been given. */
1121       if ((peer != NULL)
1122           && (peer->tls_key_file != NULL))
1123       {
1124         DEBUG ("netcmd plugin: Starting TLS session on a connection "
1125             "via [%s]:%s",
1126             (peer->node != NULL) ? peer->node : "any",
1127             (peer->service != NULL) ? peer->service : NC_DEFAULT_SERVICE);
1128         conn->tls_session = nc_tls_get_session (peer);
1129         if (conn->tls_session == NULL)
1130         {
1131           ERROR ("netcmd plugin: Creating TLS session on a connection via "
1132               "[%s]:%s failed. For security reasons this connection will be "
1133               "terminated.",
1134               (peer->node != NULL) ? peer->node : "any",
1135               (peer->service != NULL) ? peer->service : NC_DEFAULT_SERVICE);
1136           nc_connection_close (conn);
1137           continue;
1138         }
1139         conn->have_tls_session = 1;
1140         conn->tls_verify_peer = peer->tls_verify_peer;
1141       }
1142
1143       DEBUG ("netcmd plugin: Spawning child to handle connection on fd #%i",
1144           conn->fd);
1145
1146       pthread_attr_init (&th_attr);
1147       pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
1148
1149       status = pthread_create (&th, &th_attr, nc_handle_client, conn);
1150       pthread_attr_destroy (&th_attr);
1151       if (status != 0)
1152       {
1153         WARNING ("netcmd plugin: pthread_create failed: %s",
1154             sstrerror (errno, errbuf, sizeof (errbuf)));
1155         nc_connection_close (conn);
1156         continue;
1157       }
1158     }
1159   } /* while (listen_thread_loop) */
1160
1161   for (i = 0; i < pollfd_num; i++)
1162   {
1163     if (pollfd[i].fd < 0)
1164       continue;
1165
1166     close (pollfd[i].fd);
1167     pollfd[i].fd = -1;
1168     pollfd[i].events = 0;
1169     pollfd[i].revents = 0;
1170   }
1171
1172   sfree (pollfd);
1173   pollfd_num = 0;
1174
1175   return ((void *) 0);
1176 } /* }}} void *nc_server_thread */
1177
1178 /*
1179  * <Plugin netcmd>
1180  *   <Listen>
1181  *     Address "::1"
1182  *     Port "1234"
1183  *     TLSCertFile "/path/to/cert"
1184  *     TLSKeyFile  "/path/to/key"
1185  *     TLSCAFile   "/path/to/ca"
1186  *     TLSCRLFile  "/path/to/crl"
1187  *     TLSVerifyPeer yes|no
1188  *   </Listen>
1189  * </Plugin>
1190  */
1191 static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
1192 {
1193   nc_peer_t *p;
1194   _Bool success;
1195   int i;
1196
1197   p = realloc (peers, sizeof (*peers) * (peers_num + 1));
1198   if (p == NULL)
1199   {
1200     ERROR ("netcmd plugin: realloc failed.");
1201     return (ENOMEM);
1202   }
1203   peers = p;
1204   p = peers + peers_num;
1205   memset (p, 0, sizeof (*p));
1206   p->node = NULL;
1207   p->service = NULL;
1208   p->tls_cert_file = NULL;
1209   p->tls_key_file = NULL;
1210   p->tls_ca_file = NULL;
1211   p->tls_crl_file = NULL;
1212   p->tls_verify_peer = 0;
1213
1214   for (i = 0; i < ci->children_num; i++)
1215   {
1216     oconfig_item_t *child = ci->children + i;
1217
1218     if (strcasecmp ("Address", child->key) == 0)
1219       cf_util_get_string (child, &p->node);
1220     else if (strcasecmp ("Port", child->key) == 0)
1221       cf_util_get_service (child, &p->service);
1222     else if (strcasecmp ("TLSCertFile", child->key) == 0)
1223       cf_util_get_string (child, &p->tls_cert_file);
1224     else if (strcasecmp ("TLSKeyFile", child->key) == 0)
1225       cf_util_get_string (child, &p->tls_key_file);
1226     else if (strcasecmp ("TLSCAFile", child->key) == 0)
1227       cf_util_get_string (child, &p->tls_ca_file);
1228     else if (strcasecmp ("TLSCRLFile", child->key) == 0)
1229       cf_util_get_string (child, &p->tls_crl_file);
1230     else if (strcasecmp ("TLSVerifyPeer", child->key) == 0)
1231       cf_util_get_boolean (child, &p->tls_verify_peer);
1232     else
1233       WARNING ("netcmd plugin: The option \"%s\" is not recognized within "
1234           "a \"%s\" block.", child->key, ci->key);
1235   }
1236
1237   /* TLS is confusing for many people. Be verbose on mis-configurations to
1238    * help people set up encryption correctly. */
1239   success = 1;
1240   if (p->tls_key_file == NULL)
1241   {
1242     if (p->tls_cert_file != NULL)
1243     {
1244       WARNING ("netcmd plugin: The \"TLSCertFile\" option is only valid in "
1245           "combination with the \"TLSKeyFile\" option.");
1246       success = 0;
1247     }
1248     if (p->tls_ca_file != NULL)
1249     {
1250       WARNING ("netcmd plugin: The \"TLSCAFile\" option is only valid when "
1251           "the \"TLSKeyFile\" option has been specified.");
1252       success = 0;
1253     }
1254     if (p->tls_crl_file != NULL)
1255     {
1256       WARNING ("netcmd plugin: The \"TLSCRLFile\" option is only valid when "
1257           "the \"TLSKeyFile\" option has been specified.");
1258       success = 0;
1259     }
1260   }
1261   else if (p->tls_cert_file == NULL)
1262   {
1263     WARNING ("netcmd plugin: The \"TLSKeyFile\" option is only valid in "
1264         "combination with the \"TLSCertFile\" option.");
1265     success = 0;
1266   }
1267
1268   if (!success)
1269   {
1270     ERROR ("netcmd plugin: Problems in the security settings have been "
1271         "detected in the <Listen /> block for [%s]:%s. The entire block "
1272         "will be ignored to prevent unauthorized access.",
1273         (p->node == NULL) ? "::0" : p->node,
1274         (p->service == NULL) ? NC_DEFAULT_SERVICE : p->service);
1275     nc_free_peer (p);
1276     return (-1);
1277   }
1278
1279   DEBUG ("netcmd plugin: node = \"%s\"; service = \"%s\";", p->node, p->service);
1280
1281   peers_num++;
1282
1283   return (0);
1284 } /* }}} int nc_config_peer */
1285
1286 static int nc_config (oconfig_item_t *ci) /* {{{ */
1287 {
1288   int i;
1289
1290   for (i = 0; i < ci->children_num; i++)
1291   {
1292     oconfig_item_t *child = ci->children + i;
1293
1294     if (strcasecmp ("Listen", child->key) == 0)
1295       nc_config_peer (child);
1296     else
1297       WARNING ("netcmd plugin: The option \"%s\" is not recognized.",
1298           child->key);
1299   }
1300
1301   return (0);
1302 } /* }}} int nc_config */
1303
1304 static int nc_init (void) /* {{{ */
1305 {
1306   static int have_init = 0;
1307
1308   int status;
1309
1310   /* Initialize only once. */
1311   if (have_init != 0)
1312     return (0);
1313   have_init = 1;
1314
1315   gnutls_global_init ();
1316
1317   listen_thread_loop = 1;
1318
1319   status = pthread_create (&listen_thread, NULL, nc_server_thread, NULL);
1320   if (status != 0)
1321   {
1322     char errbuf[1024];
1323     listen_thread_loop = 0;
1324     listen_thread_running = 0;
1325     ERROR ("netcmd plugin: pthread_create failed: %s",
1326         sstrerror (errno, errbuf, sizeof (errbuf)));
1327     return (-1);
1328   }
1329
1330   listen_thread_running = 1;
1331   return (0);
1332 } /* }}} int nc_init */
1333
1334 static int nc_shutdown (void) /* {{{ */
1335 {
1336   size_t i;
1337
1338   listen_thread_loop = 0;
1339
1340   if (listen_thread != (pthread_t) 0)
1341   {
1342     void *ret;
1343
1344     pthread_kill (listen_thread, SIGTERM);
1345     pthread_join (listen_thread, &ret);
1346     listen_thread = (pthread_t) 0;
1347   }
1348
1349   plugin_unregister_init ("netcmd");
1350   plugin_unregister_shutdown ("netcmd");
1351
1352   for (i = 0; i < peers_num; i++)
1353     nc_free_peer (peers + i);
1354   peers_num = 0;
1355   sfree (peers);
1356
1357   return (0);
1358 } /* }}} int nc_shutdown */
1359
1360 void module_register (void) /* {{{ */
1361 {
1362   plugin_register_complex_config ("netcmd", nc_config);
1363   plugin_register_init ("netcmd", nc_init);
1364   plugin_register_shutdown ("netcmd", nc_shutdown);
1365 } /* }}} void module_register (void) */
1366
1367 /* vim: set sw=2 sts=2 tw=78 et fdm=marker : */