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