netcmd plugin: Add work-around for GCC warning.
[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     intptr_t fd;
425
426     conn->read_buffer = malloc (NC_READ_BUFFER_SIZE);
427     if (conn->read_buffer == NULL)
428       return (ENOMEM);
429     memset (conn->read_buffer, 0, NC_READ_BUFFER_SIZE);
430
431     /* Make (relatively) sure that 'fd' and 'void*' have the same size to make
432      * GCC happy. */
433     fd = (intptr_t) conn->fd;
434     gnutls_transport_set_ptr (conn->tls_session,
435         (gnutls_transport_ptr_t) fd);
436
437     while (42)
438     {
439       status = gnutls_handshake (conn->tls_session);
440       if (status == GNUTLS_E_SUCCESS)
441         break;
442       else if ((status == GNUTLS_E_AGAIN) || (status == GNUTLS_E_INTERRUPTED))
443         continue;
444       else
445       {
446         ERROR ("netcmd plugin: gnutls_handshake failed: %s",
447             gnutls_strerror (status));
448         return (-1);
449       }
450     }
451
452     return (0);
453   }
454
455   /* Duplicate the file descriptor. We need two file descriptors, because we
456    * create two FILE* objects. If they pointed to the same FD and we called
457    * fclose() on each, that would call close() twice on the same FD. If
458    * another file is opened in between those two calls, it could get assigned
459    * that FD and weird stuff would happen. */
460   fd_copy = dup (conn->fd);
461   if (fd_copy < 0)
462   {
463     ERROR ("netcmd plugin: dup(2) failed: %s",
464         sstrerror (errno, errbuf, sizeof (errbuf)));
465     return (-1);
466   }
467
468   conn->fh_in  = fdopen (conn->fd, "r");
469   if (conn->fh_in == NULL)
470   {
471     ERROR ("netcmd plugin: fdopen failed: %s",
472         sstrerror (errno, errbuf, sizeof (errbuf)));
473     return (-1);
474   }
475   /* Prevent other code from using the FD directly. */
476   conn->fd = -1;
477
478   conn->fh_out = fdopen (fd_copy, "w");
479   /* Prevent nc_connection_close from calling close(2) on this fd. */
480   if (conn->fh_out == NULL)
481   {
482     ERROR ("netcmd plugin: fdopen failed: %s",
483         sstrerror (errno, errbuf, sizeof (errbuf)));
484     return (-1);
485   }
486
487   /* change output buffer to line buffered mode */
488   if (setvbuf (conn->fh_out, NULL, _IOLBF, 0) != 0)
489   {
490     ERROR ("netcmd plugin: setvbuf failed: %s",
491         sstrerror (errno, errbuf, sizeof (errbuf)));
492     nc_connection_close (conn);
493     return (-1);
494   }
495
496   return (0);
497 } /* }}} int nc_connection_init */
498
499 static char *nc_connection_gets (nc_connection_t *conn, /* {{{ */
500     char *buffer, size_t buffer_size)
501 {
502   ssize_t status;
503   char *orig_buffer = buffer;
504
505   if (conn == NULL)
506   {
507     errno = EINVAL;
508     return (NULL);
509   }
510
511   if (!conn->have_tls_session)
512     return (fgets (buffer, (int) buffer_size, conn->fh_in));
513
514   if ((buffer == NULL) || (buffer_size < 2))
515   {
516     errno = EINVAL;
517     return (NULL);
518   }
519
520   /* ensure null termination */
521   memset (buffer, 0, buffer_size);
522   buffer_size--;
523
524   while (42)
525   {
526     size_t max_copy_bytes;
527     size_t newline_pos;
528     _Bool found_newline;
529     size_t i;
530
531     /* If there's no more data in the read buffer, read another chunk from the
532      * socket. */
533     if (conn->read_buffer_fill < 1)
534     {
535       status = gnutls_record_recv (conn->tls_session,
536           conn->read_buffer, NC_READ_BUFFER_SIZE);
537       if (status < 0) /* error */
538       {
539         ERROR ("netcmd plugin: Error while reading from TLS stream.");
540         return (NULL);
541       }
542       else if (status == 0) /* we reached end of file */
543       {
544         if (orig_buffer == buffer) /* nothing has been written to the buffer yet */
545           return (NULL); /* end of file */
546         else
547           return (orig_buffer);
548       }
549       else
550       {
551         conn->read_buffer_fill = (size_t) status;
552       }
553     }
554     assert (conn->read_buffer_fill > 0);
555
556     /* Determine where the first newline character is in the buffer. We're not
557      * using strcspn(3) here, becaus the buffer is possibly not
558      * null-terminated. */
559     newline_pos = conn->read_buffer_fill;
560     found_newline = 0;
561     for (i = 0; i < conn->read_buffer_fill; i++)
562     {
563       if (conn->read_buffer[i] == '\n')
564       {
565         newline_pos = i;
566         found_newline = 1;
567         break;
568       }
569     }
570
571     /* Determine how many bytes to copy at most. This is MIN(buffer available,
572      * read buffer size, characters to newline). */
573     max_copy_bytes = buffer_size;
574     if (max_copy_bytes > conn->read_buffer_fill)
575       max_copy_bytes = conn->read_buffer_fill;
576     if (max_copy_bytes > (newline_pos + 1))
577       max_copy_bytes = newline_pos + 1;
578     assert (max_copy_bytes > 0);
579
580     /* Copy bytes to the output buffer. */
581     memcpy (buffer, conn->read_buffer, max_copy_bytes);
582     buffer += max_copy_bytes;
583     assert (buffer_size >= max_copy_bytes);
584     buffer_size -= max_copy_bytes;
585
586     /* If there is data left in the read buffer, move it to the front of the
587      * buffer. */
588     if (max_copy_bytes < conn->read_buffer_fill)
589     {
590       size_t data_left_size = conn->read_buffer_fill - max_copy_bytes;
591       memmove (conn->read_buffer, conn->read_buffer + max_copy_bytes,
592           data_left_size);
593       conn->read_buffer_fill -= max_copy_bytes;
594     }
595     else
596     {
597       assert (max_copy_bytes == conn->read_buffer_fill);
598       conn->read_buffer_fill = 0;
599     }
600
601     if (found_newline)
602       break;
603
604     if (buffer_size == 0) /* no more space in the output buffer */
605       break;
606   }
607
608   return (orig_buffer);
609 } /* }}} char *nc_connection_gets */
610
611 static void *nc_handle_client (void *arg) /* {{{ */
612 {
613   nc_connection_t *conn;
614   char errbuf[1024];
615   int status;
616
617   conn = arg;
618
619   DEBUG ("netcmd plugin: nc_handle_client: Reading from fd #%i", conn->fd);
620
621   status = nc_connection_init (conn);
622   if (status != 0)
623   {
624     nc_connection_close (conn);
625     pthread_exit ((void *) 1);
626   }
627
628   while (42)
629   {
630     char buffer[1024];
631     char buffer_copy[1024];
632     char *fields[128];
633     int   fields_num;
634     int   len;
635
636     errno = 0;
637     if (nc_connection_gets (conn, buffer, sizeof (buffer)) == NULL)
638     {
639       if (errno != 0)
640       {
641         WARNING ("netcmd plugin: failed to read from socket #%i: %s",
642             fileno (conn->fh_in),
643             sstrerror (errno, errbuf, sizeof (errbuf)));
644       }
645       break;
646     }
647
648     len = strlen (buffer);
649     while ((len > 0)
650         && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
651       buffer[--len] = '\0';
652
653     if (len == 0)
654       continue;
655
656     sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
657
658     fields_num = strsplit (buffer_copy, fields,
659         sizeof (fields) / sizeof (fields[0]));
660
661     if (fields_num < 1)
662     {
663       nc_connection_close (conn);
664       break;
665     }
666
667     if (strcasecmp (fields[0], "getval") == 0)
668     {
669       handle_getval (conn->fh_out, buffer);
670     }
671     else if (strcasecmp (fields[0], "putval") == 0)
672     {
673       handle_putval (conn->fh_out, buffer);
674     }
675     else if (strcasecmp (fields[0], "listval") == 0)
676     {
677       handle_listval (conn->fh_out, buffer);
678     }
679     else if (strcasecmp (fields[0], "putnotif") == 0)
680     {
681       handle_putnotif (conn->fh_out, buffer);
682     }
683     else if (strcasecmp (fields[0], "flush") == 0)
684     {
685       handle_flush (conn->fh_out, buffer);
686     }
687     else
688     {
689       if (fprintf (conn->fh_out, "-1 Unknown command: %s\n", fields[0]) < 0)
690       {
691         WARNING ("netcmd plugin: failed to write to socket #%i: %s",
692             fileno (conn->fh_out),
693             sstrerror (errno, errbuf, sizeof (errbuf)));
694         break;
695       }
696     }
697   } /* while (fgets) */
698
699   DEBUG ("netcmd plugin: nc_handle_client: Exiting..");
700   nc_connection_close (conn);
701
702   pthread_exit ((void *) 0);
703   return ((void *) 0);
704 } /* }}} void *nc_handle_client */
705
706 static void *nc_server_thread (void __attribute__((unused)) *arg) /* {{{ */
707 {
708   int  status;
709   pthread_t th;
710   pthread_attr_t th_attr;
711   char errbuf[1024];
712   size_t i;
713
714   for (i = 0; i < peers_num; i++)
715     nc_open_socket (peers + i);
716
717   if (peers_num == 0)
718     nc_open_socket (NULL);
719
720   if (pollfd_num == 0)
721   {
722     ERROR ("netcmd plugin: No sockets could be opened.");
723     pthread_exit ((void *) -1);
724   }
725
726   while (listen_thread_loop != 0)
727   {
728     status = poll (pollfd, (nfds_t) pollfd_num, /* timeout = */ -1);
729     if (status < 0)
730     {
731       if ((errno == EINTR) || (errno == EAGAIN))
732         continue;
733
734       ERROR ("netcmd plugin: poll(2) failed: %s",
735           sstrerror (errno, errbuf, sizeof (errbuf)));
736       listen_thread_loop = 0;
737       continue;
738     }
739
740     for (i = 0; i < pollfd_num; i++)
741     {
742       nc_peer_t *peer;
743       nc_connection_t *conn;
744
745       if (pollfd[i].revents == 0)
746       {
747         continue;
748       }
749       else if ((pollfd[i].revents & (POLLERR | POLLHUP | POLLNVAL))
750           != 0)
751       {
752         WARNING ("netcmd plugin: File descriptor %i failed.",
753             pollfd[i].fd);
754         close (pollfd[i].fd);
755         pollfd[i].fd = -1;
756         pollfd[i].events = 0;
757         pollfd[i].revents = 0;
758         continue;
759       }
760       pollfd[i].revents = 0;
761
762       peer = nc_fd_to_peer (pollfd[i].fd);
763       if (peer == NULL)
764       {
765         ERROR ("netcmd plugin: Unable to find peer structure for file "
766             "descriptor #%i.", pollfd[i].fd);
767         continue;
768       }
769
770       status = accept (pollfd[i].fd,
771           /* sockaddr = */ NULL,
772           /* sockaddr_len = */ NULL);
773       if (status < 0)
774       {
775         if (errno != EINTR)
776           ERROR ("netcmd plugin: accept failed: %s",
777               sstrerror (errno, errbuf, sizeof (errbuf)));
778         continue;
779       }
780
781       conn = malloc (sizeof (*conn));
782       if (conn == NULL)
783       {
784         ERROR ("netcmd plugin: malloc failed.");
785         close (status);
786         continue;
787       }
788       memset (conn, 0, sizeof (*conn));
789       conn->fh_in = NULL;
790       conn->fh_out = NULL;
791
792       conn->fd = status;
793       if ((peer != NULL)
794           && (peer->tls_cert_file != NULL))
795       {
796         DEBUG ("netcmd plugin: Starting TLS session on [%s]:%s",
797             (peer->node != NULL) ? peer->node : "any",
798             (peer->service != NULL) ? peer->service : NC_DEFAULT_SERVICE);
799         conn->tls_session = nc_tls_get_session (peer);
800         conn->have_tls_session = 1;
801       }
802
803       DEBUG ("Spawning child to handle connection on fd %i", conn->fd);
804
805       pthread_attr_init (&th_attr);
806       pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
807
808       status = pthread_create (&th, &th_attr, nc_handle_client,
809           conn);
810       if (status != 0)
811       {
812         WARNING ("netcmd plugin: pthread_create failed: %s",
813             sstrerror (errno, errbuf, sizeof (errbuf)));
814         nc_connection_close (conn);
815         continue;
816       }
817     }
818   } /* while (listen_thread_loop) */
819
820   for (i = 0; i < pollfd_num; i++)
821   {
822     if (pollfd[i].fd < 0)
823       continue;
824
825     close (pollfd[i].fd);
826     pollfd[i].fd = -1;
827     pollfd[i].events = 0;
828     pollfd[i].revents = 0;
829   }
830
831   sfree (pollfd);
832   pollfd_num = 0;
833
834   return ((void *) 0);
835 } /* }}} void *nc_server_thread */
836
837 /*
838  * <Plugin netcmd>
839  *   <Listen>
840  *     Address "::1"
841  *     Port "1234"
842  *     TLSCertFile "/path/to/cert"
843  *     TLSKeyFile  "/path/to/key"
844  *     TLSCAFile   "/path/to/ca"
845  *     TLSCRLFile  "/path/to/crl"
846  *     TLSVerifyPeer yes|no
847  *   </Listen>
848  * </Plugin>
849  */
850 static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
851 {
852   nc_peer_t *p;
853   int i;
854
855   p = realloc (peers, sizeof (*peers) * (peers_num + 1));
856   if (p == NULL)
857   {
858     ERROR ("netcmd plugin: realloc failed.");
859     return (ENOMEM);
860   }
861   peers = p;
862   p = peers + peers_num;
863   memset (p, 0, sizeof (*p));
864   p->node = NULL;
865   p->service = NULL;
866   p->tls_cert_file = NULL;
867   p->tls_key_file = NULL;
868   p->tls_ca_file = NULL;
869   p->tls_crl_file = NULL;
870   p->tls_verify_peer = 1;
871
872   for (i = 0; i < ci->children_num; i++)
873   {
874     oconfig_item_t *child = ci->children + i;
875
876     if (strcasecmp ("Address", child->key) == 0)
877       cf_util_get_string (child, &p->node);
878     else if (strcasecmp ("Port", child->key) == 0)
879       cf_util_get_string (child, &p->service);
880     else if (strcasecmp ("TLSCertFile", child->key) == 0)
881       cf_util_get_string (child, &p->tls_cert_file);
882     else if (strcasecmp ("TLSKeyFile", child->key) == 0)
883       cf_util_get_string (child, &p->tls_key_file);
884     else if (strcasecmp ("TLSCAFile", child->key) == 0)
885       cf_util_get_string (child, &p->tls_ca_file);
886     else if (strcasecmp ("TLSCRLFile", child->key) == 0)
887       cf_util_get_string (child, &p->tls_crl_file);
888     else if (strcasecmp ("TLSVerifyPeer", child->key) == 0)
889       cf_util_get_boolean (child, &p->tls_verify_peer);
890     else
891       WARNING ("netcmd plugin: The option \"%s\" is not recognized within "
892           "a \"%s\" block.", child->key, ci->key);
893   }
894
895   DEBUG ("netcmd plugin: node = \"%s\"; service = \"%s\";", p->node, p->service);
896
897   peers_num++;
898
899   return (0);
900 } /* }}} int nc_config_peer */
901
902 static int nc_config (oconfig_item_t *ci)
903 {
904   int i;
905
906   for (i = 0; i < ci->children_num; i++)
907   {
908     oconfig_item_t *child = ci->children + i;
909
910     if (strcasecmp ("Listen", child->key) == 0)
911       nc_config_peer (child);
912     else
913       WARNING ("netcmd plugin: The option \"%s\" is not recognized.",
914           child->key);
915   }
916
917   return (0);
918 } /* int nc_config */
919
920 static int nc_init (void)
921 {
922   static int have_init = 0;
923
924   int status;
925
926   /* Initialize only once. */
927   if (have_init != 0)
928     return (0);
929   have_init = 1;
930
931   gnutls_global_init ();
932
933   listen_thread_loop = 1;
934
935   status = pthread_create (&listen_thread, NULL, nc_server_thread, NULL);
936   if (status != 0)
937   {
938     char errbuf[1024];
939     listen_thread_loop = 0;
940     listen_thread_running = 0;
941     ERROR ("netcmd plugin: pthread_create failed: %s",
942         sstrerror (errno, errbuf, sizeof (errbuf)));
943     return (-1);
944   }
945
946   listen_thread_running = 1;
947   return (0);
948 } /* int nc_init */
949
950 static int nc_shutdown (void)
951 {
952   void *ret;
953
954   listen_thread_loop = 0;
955
956   if (listen_thread != (pthread_t) 0)
957   {
958     pthread_kill (listen_thread, SIGTERM);
959     pthread_join (listen_thread, &ret);
960     listen_thread = (pthread_t) 0;
961   }
962
963   plugin_unregister_init ("netcmd");
964   plugin_unregister_shutdown ("netcmd");
965
966   return (0);
967 } /* int nc_shutdown */
968
969 void module_register (void)
970 {
971   plugin_register_complex_config ("netcmd", nc_config);
972   plugin_register_init ("netcmd", nc_init);
973   plugin_register_shutdown ("netcmd", nc_shutdown);
974 } /* void module_register (void) */
975
976 /* vim: set sw=2 sts=2 tw=78 et fdm=marker : */