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