90aef1a2ab5a055cc2377d4153f1831e81aa12dd
[collectd.git] / src / netcmd.c
1 /**
2  * collectd - src/netcmd.c
3  * Copyright (C) 2007-2012  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 };
75 typedef struct nc_peer_s nc_peer_t;
76
77 #if defined(PAGESIZE)
78 # define NC_READ_BUFFER_SIZE PAGESIZE
79 #elif defined(PAGE_SIZE)
80 # define NC_READ_BUFFER_SIZE PAGE_SIZE
81 #else
82 # define NC_READ_BUFFER_SIZE 4096
83 #endif
84
85 struct nc_connection_s
86 {
87   /* TLS fields */
88   int fd;
89   char *read_buffer;
90   size_t read_buffer_fill;
91
92   /* non-TLS fields */
93   FILE *fh_in;
94   FILE *fh_out;
95
96   gnutls_session_t tls_session;
97   _Bool have_tls_session;
98 };
99 typedef struct nc_connection_s nc_connection_t;
100
101 /*
102  * Private variables
103  */
104
105 /* socket configuration */
106 static nc_peer_t *peers = NULL;
107 static size_t     peers_num;
108
109 static struct pollfd  *pollfd = NULL;
110 static size_t          pollfd_num;
111
112 static int       listen_thread_loop = 0;
113 static int       listen_thread_running = 0;
114 static pthread_t listen_thread;
115
116 /*
117  * Functions
118  */
119 static nc_peer_t *nc_fd_to_peer (int fd) /* {{{ */
120 {
121   size_t i;
122
123   for (i = 0; i < peers_num; i++)
124   {
125     size_t j;
126
127     for (j = 0; j < peers[i].fds_num; j++)
128       if (peers[i].fds[j] == fd)
129         return (peers + i);
130   }
131
132   return (NULL);
133 } /* }}} nc_peer_t *nc_fd_to_peer */
134
135 static void nc_free_peer (nc_peer_t *p) /* {{{ */
136 {
137   size_t i;
138   if (p == NULL)
139     return;
140
141   sfree (p->node);
142   sfree (p->service);
143
144   for (i = 0; i < p->fds_num; i++)
145   {
146     if (p->fds[i] >= 0)
147       close (p->fds[i]);
148     p->fds[i] = -1;
149   }
150   p->fds_num = 0;
151   sfree (p->fds);
152
153   sfree (p->tls_cert_file);
154   sfree (p->tls_key_file);
155   sfree (p->tls_ca_file);
156   sfree (p->tls_crl_file);
157
158   gnutls_certificate_free_credentials (p->tls_credentials);
159   gnutls_dh_params_deinit (p->tls_dh_params);
160   gnutls_priority_deinit (p->tls_priority);
161 } /* }}} void nc_free_peer */
162
163 static int nc_register_fd (nc_peer_t *peer, int fd) /* {{{ */
164 {
165   struct pollfd *poll_ptr;
166   int *fd_ptr;
167
168   poll_ptr = realloc (pollfd, (pollfd_num + 1) * sizeof (*pollfd));
169   if (poll_ptr == NULL)
170   {
171     ERROR ("netcmd plugin: realloc failed.");
172     return (-1);
173   }
174   pollfd = poll_ptr;
175
176   memset (&pollfd[pollfd_num], 0, sizeof (pollfd[pollfd_num]));
177   pollfd[pollfd_num].fd = fd;
178   pollfd[pollfd_num].events = POLLIN | POLLPRI;
179   pollfd[pollfd_num].revents = 0;
180   pollfd_num++;
181
182   if (peer == NULL)
183     return (0);
184
185   fd_ptr = realloc (peer->fds, (peer->fds_num + 1) * sizeof (*peer->fds));
186   if (fd_ptr == NULL)
187   {
188     ERROR ("netcmd plugin: realloc failed.");
189     return (-1);
190   }
191   peer->fds = fd_ptr;
192   peer->fds[peer->fds_num] = fd;
193   peer->fds_num++;
194
195   return (0);
196 } /* }}} int nc_register_fd */
197
198 static int nc_tls_init (nc_peer_t *peer) /* {{{ */
199 {
200   int status;
201
202   if (peer == NULL)
203     return (EINVAL);
204
205   if ((peer->tls_cert_file == NULL)
206       || (peer->tls_key_file == NULL))
207   {
208     DEBUG ("netcmd plugin: Not setting up TLS environment for peer.");
209     return (0);
210   }
211
212   DEBUG ("netcmd plugin: Setting up TLS environment for peer.");
213
214   /* Initialize the structure holding our certificate information. */
215   status = gnutls_certificate_allocate_credentials (&peer->tls_credentials);
216   if (status != GNUTLS_E_SUCCESS)
217   {
218     ERROR ("netcmd plugin: gnutls_certificate_allocate_credentials failed: %s",
219         gnutls_strerror (status));
220     return (status);
221   }
222
223   /* Set up the configured certificates. */
224   if (peer->tls_ca_file != NULL)
225   {
226     status = gnutls_certificate_set_x509_trust_file (peer->tls_credentials,
227         peer->tls_ca_file, GNUTLS_X509_FMT_PEM);
228     if (status < 0)
229     {
230       ERROR ("netcmd plugin: gnutls_certificate_set_x509_trust_file (%s) "
231           "failed: %s",
232           peer->tls_ca_file, gnutls_strerror (status));
233       return (status);
234     }
235     else
236     {
237       DEBUG ("netcmd plugin: Successfully loaded %i CA(s).", status);
238     }
239   }
240
241   if (peer->tls_crl_file != NULL)
242   {
243     status = gnutls_certificate_set_x509_crl_file (peer->tls_credentials,
244         peer->tls_crl_file, GNUTLS_X509_FMT_PEM);
245     if (status < 0)
246     {
247       ERROR ("netcmd plugin: gnutls_certificate_set_x509_crl_file (%s) "
248           "failed: %s",
249           peer->tls_crl_file, gnutls_strerror (status));
250       return (status);
251     }
252     else
253     {
254       DEBUG ("netcmd plugin: Successfully loaded %i CRL(s).", status);
255     }
256   }
257
258   status = gnutls_certificate_set_x509_key_file (peer->tls_credentials,
259       peer->tls_cert_file, peer->tls_key_file, GNUTLS_X509_FMT_PEM);
260   if (status != GNUTLS_E_SUCCESS)
261   {
262     ERROR ("netcmd plugin: gnutls_certificate_set_x509_key_file failed: %s",
263         gnutls_strerror (status));
264     return (status);
265   }
266
267   /* Initialize Diffie-Hellman parameters. */
268   gnutls_dh_params_init (&peer->tls_dh_params);
269   gnutls_dh_params_generate2 (peer->tls_dh_params, NC_TLS_DH_BITS);
270   gnutls_certificate_set_dh_params (peer->tls_credentials,
271       peer->tls_dh_params);
272
273   /* Initialize a "priority cache". This will tell GNUTLS which algorithms to
274    * use and which to avoid. We use the "NORMAL" method for now. */
275   gnutls_priority_init (&peer->tls_priority,
276      /* priority = */ "NORMAL", /* errpos = */ NULL);
277
278   return (0);
279 } /* }}} int nc_tls_init */
280
281 static gnutls_session_t nc_tls_get_session (nc_peer_t *peer) /* {{{ */
282 {
283   gnutls_session_t session;
284   int status;
285
286   if (peer->tls_credentials == NULL)
287     return (NULL);
288
289   DEBUG ("netcmd plugin: nc_tls_get_session (%s)", peer->node);
290
291   /* Initialize new session. */
292   gnutls_init (&session, GNUTLS_SERVER);
293
294   /* Set cipher priority and credentials based on the information stored with
295    * the peer. */
296   status = gnutls_priority_set (session, peer->tls_priority);
297   if (status != GNUTLS_E_SUCCESS)
298   {
299     ERROR ("netcmd plugin: gnutls_priority_set failed: %s",
300         gnutls_strerror (status));
301     gnutls_deinit (session);
302     return (NULL);
303   }
304
305   status = gnutls_credentials_set (session,
306       GNUTLS_CRD_CERTIFICATE, peer->tls_credentials);
307   if (status != GNUTLS_E_SUCCESS)
308   {
309     ERROR ("netcmd plugin: gnutls_credentials_set failed: %s",
310         gnutls_strerror (status));
311     gnutls_deinit (session);
312     return (NULL);
313   }
314
315   /* Request the client certificate. If TLSVerifyPeer is set to true,
316    * *require* a client certificate. */
317   gnutls_certificate_server_set_request (session,
318       peer->tls_verify_peer ? GNUTLS_CERT_REQUIRE : GNUTLS_CERT_REQUEST);
319
320   return (session);
321 } /* }}} gnutls_session_t nc_tls_get_session */
322
323 static int nc_open_socket (nc_peer_t *peer) /* {{{ */
324 {
325   struct addrinfo ai_hints;
326   struct addrinfo *ai_list;
327   struct addrinfo *ai_ptr;
328   int status;
329
330   const char *node = NULL;
331   const char *service = NULL;
332
333   if (peer != NULL)
334   {
335     node = peer->node;
336     service = peer->service;
337   }
338
339   if (service == NULL)
340     service = NC_DEFAULT_SERVICE;
341
342   memset (&ai_hints, 0, sizeof (ai_hints));
343 #ifdef AI_PASSIVE
344   ai_hints.ai_flags |= AI_PASSIVE;
345 #endif
346 #ifdef AI_ADDRCONFIG
347   ai_hints.ai_flags |= AI_ADDRCONFIG;
348 #endif
349   ai_hints.ai_family = AF_UNSPEC;
350   ai_hints.ai_socktype = SOCK_STREAM;
351
352   ai_list = NULL;
353
354   if (service == NULL)
355     service = NC_DEFAULT_SERVICE;
356
357   status = getaddrinfo (node, service, &ai_hints, &ai_list);
358   if (status != 0)
359   {
360     ERROR ("netcmd plugin: getaddrinfo failed: %s",
361         gai_strerror (status));
362     return (-1);
363   }
364
365   for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
366   {
367     char errbuf[1024];
368     int fd;
369
370     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
371         ai_ptr->ai_protocol);
372     if (fd < 0)
373     {
374       ERROR ("netcmd plugin: socket(2) failed: %s",
375           sstrerror (errno, errbuf, sizeof (errbuf)));
376       continue;
377     }
378
379     status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
380     if (status != 0)
381     {
382       close (fd);
383       ERROR ("netcmd plugin: bind(2) failed: %s",
384           sstrerror (errno, errbuf, sizeof (errbuf)));
385       continue;
386     }
387
388     status = listen (fd, /* backlog = */ 8);
389     if (status != 0)
390     {
391       close (fd);
392       ERROR ("netcmd plugin: listen(2) failed: %s",
393           sstrerror (errno, errbuf, sizeof (errbuf)));
394       continue;
395     }
396
397     status = nc_register_fd (peer, fd);
398     if (status != 0)
399     {
400       close (fd);
401       continue;
402     }
403   } /* for (ai_next) */
404
405   freeaddrinfo (ai_list);
406
407   return (nc_tls_init (peer));
408 } /* }}} int nc_open_socket */
409
410 static void nc_connection_close (nc_connection_t *conn) /* {{{ */
411 {
412   if (conn == NULL)
413     return;
414
415   if (conn->fd >= 0)
416   {
417     close (conn->fd);
418     conn->fd = -1;
419   }
420
421   if (conn->fh_in != NULL)
422   {
423     fclose (conn->fh_in);
424     conn->fh_in = NULL;
425   }
426
427   if (conn->fh_out != NULL)
428   {
429     fclose (conn->fh_out);
430     conn->fh_out = NULL;
431   }
432
433   if (conn->have_tls_session)
434   {
435     gnutls_deinit (conn->tls_session);
436     conn->have_tls_session = 0;
437   }
438
439   sfree (conn);
440 } /* }}} void nc_connection_close */
441
442 static int nc_connection_init (nc_connection_t *conn) /* {{{ */
443 {
444   int fd_copy;
445   char errbuf[1024];
446
447   DEBUG ("netcmd plugin: nc_connection_init();");
448
449   if (conn->have_tls_session)
450   {
451     int status;
452     intptr_t fd;
453
454     conn->read_buffer = malloc (NC_READ_BUFFER_SIZE);
455     if (conn->read_buffer == NULL)
456       return (ENOMEM);
457     memset (conn->read_buffer, 0, NC_READ_BUFFER_SIZE);
458
459     /* Make (relatively) sure that 'fd' and 'void*' have the same size to make
460      * GCC happy. */
461     fd = (intptr_t) conn->fd;
462     gnutls_transport_set_ptr (conn->tls_session,
463         (gnutls_transport_ptr_t) fd);
464
465     while (42)
466     {
467       status = gnutls_handshake (conn->tls_session);
468       if (status == GNUTLS_E_SUCCESS)
469         break;
470       else if ((status == GNUTLS_E_AGAIN) || (status == GNUTLS_E_INTERRUPTED))
471         continue;
472       else
473       {
474         ERROR ("netcmd plugin: gnutls_handshake failed: %s",
475             gnutls_strerror (status));
476         return (-1);
477       }
478     }
479
480     return (0);
481   }
482
483   /* Duplicate the file descriptor. We need two file descriptors, because we
484    * create two FILE* objects. If they pointed to the same FD and we called
485    * fclose() on each, that would call close() twice on the same FD. If
486    * another file is opened in between those two calls, it could get assigned
487    * that FD and weird stuff would happen. */
488   fd_copy = dup (conn->fd);
489   if (fd_copy < 0)
490   {
491     ERROR ("netcmd plugin: dup(2) failed: %s",
492         sstrerror (errno, errbuf, sizeof (errbuf)));
493     return (-1);
494   }
495
496   conn->fh_in  = fdopen (conn->fd, "r");
497   if (conn->fh_in == NULL)
498   {
499     ERROR ("netcmd plugin: fdopen failed: %s",
500         sstrerror (errno, errbuf, sizeof (errbuf)));
501     return (-1);
502   }
503   /* Prevent other code from using the FD directly. */
504   conn->fd = -1;
505
506   conn->fh_out = fdopen (fd_copy, "w");
507   /* Prevent nc_connection_close from calling close(2) on this fd. */
508   if (conn->fh_out == NULL)
509   {
510     ERROR ("netcmd plugin: fdopen failed: %s",
511         sstrerror (errno, errbuf, sizeof (errbuf)));
512     return (-1);
513   }
514
515   /* change output buffer to line buffered mode */
516   if (setvbuf (conn->fh_out, NULL, _IOLBF, 0) != 0)
517   {
518     ERROR ("netcmd plugin: setvbuf failed: %s",
519         sstrerror (errno, errbuf, sizeof (errbuf)));
520     nc_connection_close (conn);
521     return (-1);
522   }
523
524   return (0);
525 } /* }}} int nc_connection_init */
526
527 static char *nc_connection_gets (nc_connection_t *conn, /* {{{ */
528     char *buffer, size_t buffer_size)
529 {
530   ssize_t status;
531   char *orig_buffer = buffer;
532
533   if (conn == NULL)
534   {
535     errno = EINVAL;
536     return (NULL);
537   }
538
539   if (!conn->have_tls_session)
540     return (fgets (buffer, (int) buffer_size, conn->fh_in));
541
542   if ((buffer == NULL) || (buffer_size < 2))
543   {
544     errno = EINVAL;
545     return (NULL);
546   }
547
548   /* ensure null termination */
549   memset (buffer, 0, buffer_size);
550   buffer_size--;
551
552   while (42)
553   {
554     size_t max_copy_bytes;
555     size_t newline_pos;
556     _Bool found_newline;
557     size_t i;
558
559     /* If there's no more data in the read buffer, read another chunk from the
560      * socket. */
561     if (conn->read_buffer_fill < 1)
562     {
563       status = gnutls_record_recv (conn->tls_session,
564           conn->read_buffer, NC_READ_BUFFER_SIZE);
565       if (status < 0) /* error */
566       {
567         ERROR ("netcmd plugin: Error while reading from TLS stream.");
568         return (NULL);
569       }
570       else if (status == 0) /* we reached end of file */
571       {
572         if (orig_buffer == buffer) /* nothing has been written to the buffer yet */
573           return (NULL); /* end of file */
574         else
575           return (orig_buffer);
576       }
577       else
578       {
579         conn->read_buffer_fill = (size_t) status;
580       }
581     }
582     assert (conn->read_buffer_fill > 0);
583
584     /* Determine where the first newline character is in the buffer. We're not
585      * using strcspn(3) here, becaus the buffer is possibly not
586      * null-terminated. */
587     newline_pos = conn->read_buffer_fill;
588     found_newline = 0;
589     for (i = 0; i < conn->read_buffer_fill; i++)
590     {
591       if (conn->read_buffer[i] == '\n')
592       {
593         newline_pos = i;
594         found_newline = 1;
595         break;
596       }
597     }
598
599     /* Determine how many bytes to copy at most. This is MIN(buffer available,
600      * read buffer size, characters to newline). */
601     max_copy_bytes = buffer_size;
602     if (max_copy_bytes > conn->read_buffer_fill)
603       max_copy_bytes = conn->read_buffer_fill;
604     if (max_copy_bytes > (newline_pos + 1))
605       max_copy_bytes = newline_pos + 1;
606     assert (max_copy_bytes > 0);
607
608     /* Copy bytes to the output buffer. */
609     memcpy (buffer, conn->read_buffer, max_copy_bytes);
610     buffer += max_copy_bytes;
611     assert (buffer_size >= max_copy_bytes);
612     buffer_size -= max_copy_bytes;
613
614     /* If there is data left in the read buffer, move it to the front of the
615      * buffer. */
616     if (max_copy_bytes < conn->read_buffer_fill)
617     {
618       size_t data_left_size = conn->read_buffer_fill - max_copy_bytes;
619       memmove (conn->read_buffer, conn->read_buffer + max_copy_bytes,
620           data_left_size);
621       conn->read_buffer_fill -= max_copy_bytes;
622     }
623     else
624     {
625       assert (max_copy_bytes == conn->read_buffer_fill);
626       conn->read_buffer_fill = 0;
627     }
628
629     if (found_newline)
630       break;
631
632     if (buffer_size == 0) /* no more space in the output buffer */
633       break;
634   }
635
636   return (orig_buffer);
637 } /* }}} char *nc_connection_gets */
638
639 static void *nc_handle_client (void *arg) /* {{{ */
640 {
641   nc_connection_t *conn;
642   char errbuf[1024];
643   int status;
644
645   conn = arg;
646
647   DEBUG ("netcmd plugin: nc_handle_client: Reading from fd #%i", conn->fd);
648
649   status = nc_connection_init (conn);
650   if (status != 0)
651   {
652     nc_connection_close (conn);
653     pthread_exit ((void *) 1);
654   }
655
656   while (42)
657   {
658     char buffer[1024];
659     char buffer_copy[1024];
660     char *fields[128];
661     int   fields_num;
662     int   len;
663
664     errno = 0;
665     if (nc_connection_gets (conn, buffer, sizeof (buffer)) == NULL)
666     {
667       if (errno != 0)
668       {
669         WARNING ("netcmd plugin: failed to read from socket #%i: %s",
670             fileno (conn->fh_in),
671             sstrerror (errno, errbuf, sizeof (errbuf)));
672       }
673       break;
674     }
675
676     len = strlen (buffer);
677     while ((len > 0)
678         && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
679       buffer[--len] = '\0';
680
681     if (len == 0)
682       continue;
683
684     sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
685
686     fields_num = strsplit (buffer_copy, fields,
687         sizeof (fields) / sizeof (fields[0]));
688
689     if (fields_num < 1)
690     {
691       nc_connection_close (conn);
692       break;
693     }
694
695     if (strcasecmp (fields[0], "getval") == 0)
696     {
697       handle_getval (conn->fh_out, buffer);
698     }
699     else if (strcasecmp (fields[0], "putval") == 0)
700     {
701       handle_putval (conn->fh_out, buffer);
702     }
703     else if (strcasecmp (fields[0], "listval") == 0)
704     {
705       handle_listval (conn->fh_out, buffer);
706     }
707     else if (strcasecmp (fields[0], "putnotif") == 0)
708     {
709       handle_putnotif (conn->fh_out, buffer);
710     }
711     else if (strcasecmp (fields[0], "flush") == 0)
712     {
713       handle_flush (conn->fh_out, buffer);
714     }
715     else
716     {
717       if (fprintf (conn->fh_out, "-1 Unknown command: %s\n", fields[0]) < 0)
718       {
719         WARNING ("netcmd plugin: failed to write to socket #%i: %s",
720             fileno (conn->fh_out),
721             sstrerror (errno, errbuf, sizeof (errbuf)));
722         break;
723       }
724     }
725   } /* while (fgets) */
726
727   DEBUG ("netcmd plugin: nc_handle_client: Exiting..");
728   nc_connection_close (conn);
729
730   pthread_exit ((void *) 0);
731   return ((void *) 0);
732 } /* }}} void *nc_handle_client */
733
734 static void *nc_server_thread (void __attribute__((unused)) *arg) /* {{{ */
735 {
736   int  status;
737   pthread_t th;
738   pthread_attr_t th_attr;
739   char errbuf[1024];
740   size_t i;
741
742   for (i = 0; i < peers_num; i++)
743     nc_open_socket (peers + i);
744
745   if (peers_num == 0)
746     nc_open_socket (NULL);
747
748   if (pollfd_num == 0)
749   {
750     ERROR ("netcmd plugin: No sockets could be opened.");
751     pthread_exit ((void *) -1);
752   }
753
754   while (listen_thread_loop != 0)
755   {
756     status = poll (pollfd, (nfds_t) pollfd_num, /* timeout = */ -1);
757     if (status < 0)
758     {
759       if ((errno == EINTR) || (errno == EAGAIN))
760         continue;
761
762       ERROR ("netcmd plugin: poll(2) failed: %s",
763           sstrerror (errno, errbuf, sizeof (errbuf)));
764       listen_thread_loop = 0;
765       continue;
766     }
767
768     for (i = 0; i < pollfd_num; i++)
769     {
770       nc_peer_t *peer;
771       nc_connection_t *conn;
772
773       if (pollfd[i].revents == 0)
774       {
775         continue;
776       }
777       else if ((pollfd[i].revents & (POLLERR | POLLHUP | POLLNVAL))
778           != 0)
779       {
780         WARNING ("netcmd plugin: File descriptor %i failed.",
781             pollfd[i].fd);
782         close (pollfd[i].fd);
783         pollfd[i].fd = -1;
784         pollfd[i].events = 0;
785         pollfd[i].revents = 0;
786         continue;
787       }
788       pollfd[i].revents = 0;
789
790       peer = nc_fd_to_peer (pollfd[i].fd);
791       if (peer == NULL)
792       {
793         ERROR ("netcmd plugin: Unable to find peer structure for file "
794             "descriptor #%i.", pollfd[i].fd);
795         continue;
796       }
797
798       status = accept (pollfd[i].fd,
799           /* sockaddr = */ NULL,
800           /* sockaddr_len = */ NULL);
801       if (status < 0)
802       {
803         if (errno != EINTR)
804           ERROR ("netcmd plugin: accept failed: %s",
805               sstrerror (errno, errbuf, sizeof (errbuf)));
806         continue;
807       }
808
809       conn = malloc (sizeof (*conn));
810       if (conn == NULL)
811       {
812         ERROR ("netcmd plugin: malloc failed.");
813         close (status);
814         continue;
815       }
816       memset (conn, 0, sizeof (*conn));
817       conn->fh_in = NULL;
818       conn->fh_out = NULL;
819
820       conn->fd = status;
821       if ((peer != NULL)
822           && (peer->tls_cert_file != NULL))
823       {
824         DEBUG ("netcmd plugin: Starting TLS session on [%s]:%s",
825             (peer->node != NULL) ? peer->node : "any",
826             (peer->service != NULL) ? peer->service : NC_DEFAULT_SERVICE);
827         conn->tls_session = nc_tls_get_session (peer);
828         conn->have_tls_session = 1;
829       }
830
831       DEBUG ("Spawning child to handle connection on fd %i", conn->fd);
832
833       pthread_attr_init (&th_attr);
834       pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
835
836       status = pthread_create (&th, &th_attr, nc_handle_client,
837           conn);
838       if (status != 0)
839       {
840         WARNING ("netcmd plugin: pthread_create failed: %s",
841             sstrerror (errno, errbuf, sizeof (errbuf)));
842         nc_connection_close (conn);
843         continue;
844       }
845     }
846   } /* while (listen_thread_loop) */
847
848   for (i = 0; i < pollfd_num; i++)
849   {
850     if (pollfd[i].fd < 0)
851       continue;
852
853     close (pollfd[i].fd);
854     pollfd[i].fd = -1;
855     pollfd[i].events = 0;
856     pollfd[i].revents = 0;
857   }
858
859   sfree (pollfd);
860   pollfd_num = 0;
861
862   return ((void *) 0);
863 } /* }}} void *nc_server_thread */
864
865 /*
866  * <Plugin netcmd>
867  *   <Listen>
868  *     Address "::1"
869  *     Port "1234"
870  *     TLSCertFile "/path/to/cert"
871  *     TLSKeyFile  "/path/to/key"
872  *     TLSCAFile   "/path/to/ca"
873  *     TLSCRLFile  "/path/to/crl"
874  *     TLSVerifyPeer yes|no
875  *   </Listen>
876  * </Plugin>
877  */
878 static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
879 {
880   nc_peer_t *p;
881   int i;
882   _Bool success;
883
884   p = realloc (peers, sizeof (*peers) * (peers_num + 1));
885   if (p == NULL)
886   {
887     ERROR ("netcmd plugin: realloc failed.");
888     return (ENOMEM);
889   }
890   peers = p;
891   p = peers + peers_num;
892   memset (p, 0, sizeof (*p));
893   p->node = NULL;
894   p->service = NULL;
895   p->tls_cert_file = NULL;
896   p->tls_key_file = NULL;
897   p->tls_ca_file = NULL;
898   p->tls_crl_file = NULL;
899   p->tls_verify_peer = 0;
900
901   for (i = 0; i < ci->children_num; i++)
902   {
903     oconfig_item_t *child = ci->children + i;
904
905     if (strcasecmp ("Address", child->key) == 0)
906       cf_util_get_string (child, &p->node);
907     else if (strcasecmp ("Port", child->key) == 0)
908       cf_util_get_string (child, &p->service);
909     else if (strcasecmp ("TLSCertFile", child->key) == 0)
910       cf_util_get_string (child, &p->tls_cert_file);
911     else if (strcasecmp ("TLSKeyFile", child->key) == 0)
912       cf_util_get_string (child, &p->tls_key_file);
913     else if (strcasecmp ("TLSCAFile", child->key) == 0)
914       cf_util_get_string (child, &p->tls_ca_file);
915     else if (strcasecmp ("TLSCRLFile", child->key) == 0)
916       cf_util_get_string (child, &p->tls_crl_file);
917     else if (strcasecmp ("TLSVerifyPeer", child->key) == 0)
918       cf_util_get_boolean (child, &p->tls_verify_peer);
919     else
920       WARNING ("netcmd plugin: The option \"%s\" is not recognized within "
921           "a \"%s\" block.", child->key, ci->key);
922   }
923
924   success = 1;
925
926   if (p->tls_verify_peer
927       && ((p->tls_cert_file == NULL)
928         || (p->tls_key_file == NULL)
929         || (p->tls_ca_file == NULL)))
930   {
931     ERROR ("netcmd plugin: You have requested to verify peers (using the "
932         "\"TLSVerifyPeer\" option), but the TLS setup is incomplete. "
933         "The \"TLSCertFile\", \"TLSKeyFile\" and \"TLSCAFile\" are "
934         "required for this to work. This \"Listen\" block will be disabled.");
935     success = 0;
936   }
937
938   if (!success)
939   {
940     nc_free_peer (p);
941     return (-1);
942   }
943
944   DEBUG ("netcmd plugin: node = \"%s\"; service = \"%s\";", p->node, p->service);
945
946   peers_num++;
947
948   return (0);
949 } /* }}} int nc_config_peer */
950
951 static int nc_config (oconfig_item_t *ci) /* {{{ */
952 {
953   int i;
954
955   for (i = 0; i < ci->children_num; i++)
956   {
957     oconfig_item_t *child = ci->children + i;
958
959     if (strcasecmp ("Listen", child->key) == 0)
960       nc_config_peer (child);
961     else
962       WARNING ("netcmd plugin: The option \"%s\" is not recognized.",
963           child->key);
964   }
965
966   return (0);
967 } /* }}} int nc_config */
968
969 static int nc_init (void) /* {{{ */
970 {
971   static int have_init = 0;
972
973   int status;
974
975   /* Initialize only once. */
976   if (have_init != 0)
977     return (0);
978   have_init = 1;
979
980   gnutls_global_init ();
981
982   listen_thread_loop = 1;
983
984   status = pthread_create (&listen_thread, NULL, nc_server_thread, NULL);
985   if (status != 0)
986   {
987     char errbuf[1024];
988     listen_thread_loop = 0;
989     listen_thread_running = 0;
990     ERROR ("netcmd plugin: pthread_create failed: %s",
991         sstrerror (errno, errbuf, sizeof (errbuf)));
992     return (-1);
993   }
994
995   listen_thread_running = 1;
996   return (0);
997 } /* }}} int nc_init */
998
999 static int nc_shutdown (void) /* {{{ */
1000 {
1001   size_t i;
1002
1003   listen_thread_loop = 0;
1004
1005   if (listen_thread != (pthread_t) 0)
1006   {
1007     void *ret;
1008
1009     pthread_kill (listen_thread, SIGTERM);
1010     pthread_join (listen_thread, &ret);
1011     listen_thread = (pthread_t) 0;
1012   }
1013
1014   plugin_unregister_init ("netcmd");
1015   plugin_unregister_shutdown ("netcmd");
1016
1017   for (i = 0; i < peers_num; i++)
1018     nc_free_peer (peers + i);
1019   peers_num = 0;
1020   sfree (peers);
1021
1022   return (0);
1023 } /* }}} int nc_shutdown */
1024
1025 void module_register (void)
1026 {
1027   plugin_register_complex_config ("netcmd", nc_config);
1028   plugin_register_init ("netcmd", nc_init);
1029   plugin_register_shutdown ("netcmd", nc_shutdown);
1030 } /* void module_register (void) */
1031
1032 /* vim: set sw=2 sts=2 tw=78 et fdm=marker : */