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