netcmd plugin: Free peers when shutting down.
[collectd.git] / src / netcmd.c
index d5a2909..90aef1a 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/netcmd.c
- * Copyright (C) 2007-2011  Florian octo Forster
+ * Copyright (C) 2007-2012  Florian octo Forster
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -132,6 +132,34 @@ static nc_peer_t *nc_fd_to_peer (int fd) /* {{{ */
   return (NULL);
 } /* }}} nc_peer_t *nc_fd_to_peer */
 
+static void nc_free_peer (nc_peer_t *p) /* {{{ */
+{
+  size_t i;
+  if (p == NULL)
+    return;
+
+  sfree (p->node);
+  sfree (p->service);
+
+  for (i = 0; i < p->fds_num; i++)
+  {
+    if (p->fds[i] >= 0)
+      close (p->fds[i]);
+    p->fds[i] = -1;
+  }
+  p->fds_num = 0;
+  sfree (p->fds);
+
+  sfree (p->tls_cert_file);
+  sfree (p->tls_key_file);
+  sfree (p->tls_ca_file);
+  sfree (p->tls_crl_file);
+
+  gnutls_certificate_free_credentials (p->tls_credentials);
+  gnutls_dh_params_deinit (p->tls_dh_params);
+  gnutls_priority_deinit (p->tls_priority);
+} /* }}} void nc_free_peer */
+
 static int nc_register_fd (nc_peer_t *peer, int fd) /* {{{ */
 {
   struct pollfd *poll_ptr;
@@ -169,25 +197,72 @@ static int nc_register_fd (nc_peer_t *peer, int fd) /* {{{ */
 
 static int nc_tls_init (nc_peer_t *peer) /* {{{ */
 {
+  int status;
+
   if (peer == NULL)
     return (EINVAL);
 
   if ((peer->tls_cert_file == NULL)
       || (peer->tls_key_file == NULL))
+  {
+    DEBUG ("netcmd plugin: Not setting up TLS environment for peer.");
     return (0);
+  }
+
+  DEBUG ("netcmd plugin: Setting up TLS environment for peer.");
 
   /* Initialize the structure holding our certificate information. */
-  gnutls_certificate_allocate_credentials (&peer->tls_credentials);
+  status = gnutls_certificate_allocate_credentials (&peer->tls_credentials);
+  if (status != GNUTLS_E_SUCCESS)
+  {
+    ERROR ("netcmd plugin: gnutls_certificate_allocate_credentials failed: %s",
+        gnutls_strerror (status));
+    return (status);
+  }
 
   /* Set up the configured certificates. */
   if (peer->tls_ca_file != NULL)
-    gnutls_certificate_set_x509_trust_file (peer->tls_credentials,
+  {
+    status = gnutls_certificate_set_x509_trust_file (peer->tls_credentials,
         peer->tls_ca_file, GNUTLS_X509_FMT_PEM);
+    if (status < 0)
+    {
+      ERROR ("netcmd plugin: gnutls_certificate_set_x509_trust_file (%s) "
+          "failed: %s",
+          peer->tls_ca_file, gnutls_strerror (status));
+      return (status);
+    }
+    else
+    {
+      DEBUG ("netcmd plugin: Successfully loaded %i CA(s).", status);
+    }
+  }
+
   if (peer->tls_crl_file != NULL)
-      gnutls_certificate_set_x509_crl_file (peer->tls_credentials,
-          peer->tls_crl_file, GNUTLS_X509_FMT_PEM);
-  gnutls_certificate_set_x509_key_file (peer->tls_credentials,
+  {
+    status = gnutls_certificate_set_x509_crl_file (peer->tls_credentials,
+        peer->tls_crl_file, GNUTLS_X509_FMT_PEM);
+    if (status < 0)
+    {
+      ERROR ("netcmd plugin: gnutls_certificate_set_x509_crl_file (%s) "
+          "failed: %s",
+          peer->tls_crl_file, gnutls_strerror (status));
+      return (status);
+    }
+    else
+    {
+      DEBUG ("netcmd plugin: Successfully loaded %i CRL(s).", status);
+    }
+  }
+
+  status = gnutls_certificate_set_x509_key_file (peer->tls_credentials,
       peer->tls_cert_file, peer->tls_key_file, GNUTLS_X509_FMT_PEM);
+  if (status != GNUTLS_E_SUCCESS)
+  {
+    ERROR ("netcmd plugin: gnutls_certificate_set_x509_key_file failed: %s",
+        gnutls_strerror (status));
+    return (status);
+  }
 
   /* Initialize Diffie-Hellman parameters. */
   gnutls_dh_params_init (&peer->tls_dh_params);
@@ -206,21 +281,41 @@ static int nc_tls_init (nc_peer_t *peer) /* {{{ */
 static gnutls_session_t nc_tls_get_session (nc_peer_t *peer) /* {{{ */
 {
   gnutls_session_t session;
+  int status;
 
   if (peer->tls_credentials == NULL)
     return (NULL);
 
+  DEBUG ("netcmd plugin: nc_tls_get_session (%s)", peer->node);
+
   /* Initialize new session. */
   gnutls_init (&session, GNUTLS_SERVER);
 
   /* Set cipher priority and credentials based on the information stored with
    * the peer. */
-  gnutls_priority_set (session, peer->tls_priority);
-  gnutls_credentials_set (session,
+  status = gnutls_priority_set (session, peer->tls_priority);
+  if (status != GNUTLS_E_SUCCESS)
+  {
+    ERROR ("netcmd plugin: gnutls_priority_set failed: %s",
+        gnutls_strerror (status));
+    gnutls_deinit (session);
+    return (NULL);
+  }
+
+  status = gnutls_credentials_set (session,
       GNUTLS_CRD_CERTIFICATE, peer->tls_credentials);
+  if (status != GNUTLS_E_SUCCESS)
+  {
+    ERROR ("netcmd plugin: gnutls_credentials_set failed: %s",
+        gnutls_strerror (status));
+    gnutls_deinit (session);
+    return (NULL);
+  }
 
-  /* Request the client certificate. */
-  gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUEST);
+  /* Request the client certificate. If TLSVerifyPeer is set to true,
+   * *require* a client certificate. */
+  gnutls_certificate_server_set_request (session,
+      peer->tls_verify_peer ? GNUTLS_CERT_REQUIRE : GNUTLS_CERT_REQUEST);
 
   return (session);
 } /* }}} gnutls_session_t nc_tls_get_session */
@@ -349,14 +444,39 @@ static int nc_connection_init (nc_connection_t *conn) /* {{{ */
   int fd_copy;
   char errbuf[1024];
 
+  DEBUG ("netcmd plugin: nc_connection_init();");
+
   if (conn->have_tls_session)
   {
+    int status;
+    intptr_t fd;
+
     conn->read_buffer = malloc (NC_READ_BUFFER_SIZE);
     if (conn->read_buffer == NULL)
       return (ENOMEM);
     memset (conn->read_buffer, 0, NC_READ_BUFFER_SIZE);
 
-    gnutls_transport_set_ptr (conn->tls_session, &conn->fd);
+    /* Make (relatively) sure that 'fd' and 'void*' have the same size to make
+     * GCC happy. */
+    fd = (intptr_t) conn->fd;
+    gnutls_transport_set_ptr (conn->tls_session,
+        (gnutls_transport_ptr_t) fd);
+
+    while (42)
+    {
+      status = gnutls_handshake (conn->tls_session);
+      if (status == GNUTLS_E_SUCCESS)
+        break;
+      else if ((status == GNUTLS_E_AGAIN) || (status == GNUTLS_E_INTERRUPTED))
+        continue;
+      else
+      {
+        ERROR ("netcmd plugin: gnutls_handshake failed: %s",
+            gnutls_strerror (status));
+        return (-1);
+      }
+    }
+
     return (0);
   }
 
@@ -759,6 +879,7 @@ static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
 {
   nc_peer_t *p;
   int i;
+  _Bool success;
 
   p = realloc (peers, sizeof (*peers) * (peers_num + 1));
   if (p == NULL)
@@ -775,7 +896,7 @@ static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
   p->tls_key_file = NULL;
   p->tls_ca_file = NULL;
   p->tls_crl_file = NULL;
-  p->tls_verify_peer = 1;
+  p->tls_verify_peer = 0;
 
   for (i = 0; i < ci->children_num; i++)
   {
@@ -793,11 +914,33 @@ static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
       cf_util_get_string (child, &p->tls_ca_file);
     else if (strcasecmp ("TLSCRLFile", child->key) == 0)
       cf_util_get_string (child, &p->tls_crl_file);
+    else if (strcasecmp ("TLSVerifyPeer", child->key) == 0)
+      cf_util_get_boolean (child, &p->tls_verify_peer);
     else
       WARNING ("netcmd plugin: The option \"%s\" is not recognized within "
           "a \"%s\" block.", child->key, ci->key);
   }
 
+  success = 1;
+
+  if (p->tls_verify_peer
+      && ((p->tls_cert_file == NULL)
+        || (p->tls_key_file == NULL)
+        || (p->tls_ca_file == NULL)))
+  {
+    ERROR ("netcmd plugin: You have requested to verify peers (using the "
+        "\"TLSVerifyPeer\" option), but the TLS setup is incomplete. "
+        "The \"TLSCertFile\", \"TLSKeyFile\" and \"TLSCAFile\" are "
+        "required for this to work. This \"Listen\" block will be disabled.");
+    success = 0;
+  }
+
+  if (!success)
+  {
+    nc_free_peer (p);
+    return (-1);
+  }
+
   DEBUG ("netcmd plugin: node = \"%s\"; service = \"%s\";", p->node, p->service);
 
   peers_num++;
@@ -805,7 +948,7 @@ static int nc_config_peer (const oconfig_item_t *ci) /* {{{ */
   return (0);
 } /* }}} int nc_config_peer */
 
-static int nc_config (oconfig_item_t *ci)
+static int nc_config (oconfig_item_t *ci) /* {{{ */
 {
   int i;
 
@@ -821,9 +964,9 @@ static int nc_config (oconfig_item_t *ci)
   }
 
   return (0);
-} /* int nc_config */
+} /* }}} int nc_config */
 
-static int nc_init (void)
+static int nc_init (void) /* {{{ */
 {
   static int have_init = 0;
 
@@ -834,6 +977,8 @@ static int nc_init (void)
     return (0);
   have_init = 1;
 
+  gnutls_global_init ();
+
   listen_thread_loop = 1;
 
   status = pthread_create (&listen_thread, NULL, nc_server_thread, NULL);
@@ -849,16 +994,18 @@ static int nc_init (void)
 
   listen_thread_running = 1;
   return (0);
-} /* int nc_init */
+} /* }}} int nc_init */
 
-static int nc_shutdown (void)
+static int nc_shutdown (void) /* {{{ */
 {
-  void *ret;
+  size_t i;
 
   listen_thread_loop = 0;
 
   if (listen_thread != (pthread_t) 0)
   {
+    void *ret;
+
     pthread_kill (listen_thread, SIGTERM);
     pthread_join (listen_thread, &ret);
     listen_thread = (pthread_t) 0;
@@ -867,8 +1014,13 @@ static int nc_shutdown (void)
   plugin_unregister_init ("netcmd");
   plugin_unregister_shutdown ("netcmd");
 
+  for (i = 0; i < peers_num; i++)
+    nc_free_peer (peers + i);
+  peers_num = 0;
+  sfree (peers);
+
   return (0);
-} /* int nc_shutdown */
+} /* }}} int nc_shutdown */
 
 void module_register (void)
 {