Merge branch 'collectd-5.4' into collectd-5.5
[collectd.git] / src / libcollectdclient / network_buffer.c
index 9cff2e2..343c285 100644 (file)
@@ -1,25 +1,31 @@
 /**
  * collectd - src/libcollectdclient/network_buffer.c
- * Copyright (C) 2010  Florian octo Forster
+ * Copyright (C) 2010-2015  Florian octo Forster
  *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; only version 2.1 of the License is
- * applicable.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
  *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  *
  * Authors:
- *   Florian octo Forster <octo at verplant.org>
+ *   Florian octo Forster <octo at collectd.org>
  **/
 
+#include "config.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <math.h>
 #include <errno.h>
 #include <arpa/inet.h> /* htons */
 
-#include "network_buffer.h"
+#include <pthread.h>
+
+#if HAVE_LIBGCRYPT
+# include <pthread.h>
+# if defined __APPLE__
+/* default xcode compiler throws warnings even when deprecated functionality
+ * is not used. -Werror breaks the build because of erroneous warnings.
+ * http://stackoverflow.com/questions/10556299/compiler-warnings-with-libgcrypt-v1-5-0/12830209#12830209
+ */
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+# endif
+/* FreeBSD's copy of libgcrypt extends the existing GCRYPT_NO_DEPRECATED
+ * to properly hide all deprecated functionality.
+ * http://svnweb.freebsd.org/ports/head/security/libgcrypt/files/patch-src__gcrypt.h.in
+ */
+# define GCRYPT_NO_DEPRECATED
+# include <gcrypt.h>
+# if defined __APPLE__
+/* Re enable deprecation warnings */
+#  pragma GCC diagnostic warning "-Wdeprecated-declarations"
+# endif
+# if GCRYPT_VERSION_NUMBER < 0x010600
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+# endif
+#endif
+
+#include "collectd/network_buffer.h"
 
 #define TYPE_HOST            0x0000
 #define TYPE_TIME            0x0001
+#define TYPE_TIME_HR         0x0008
 #define TYPE_PLUGIN          0x0002
 #define TYPE_PLUGIN_INSTANCE 0x0003
 #define TYPE_TYPE            0x0004
 #define TYPE_TYPE_INSTANCE   0x0005
 #define TYPE_VALUES          0x0006
 #define TYPE_INTERVAL        0x0007
+#define TYPE_INTERVAL_HR     0x0009
 
 /* Types to transmit notifications */
 #define TYPE_MESSAGE         0x0100
 #define TYPE_SIGN_SHA256     0x0200
 #define TYPE_ENCR_AES256     0x0210
 
+#define PART_SIGNATURE_SHA256_SIZE 36
+#define PART_ENCRYPTION_AES256_SIZE 42
+
+#define ADD_GENERIC(nb,srcptr,size) do {         \
+  assert ((size) <= (nb)->free);                 \
+  memcpy ((nb)->ptr, (srcptr), (size));          \
+  (nb)->ptr += (size);                           \
+  (nb)->free -= (size);                          \
+} while (0)
+
+#define ADD_STATIC(nb,var) \
+  ADD_GENERIC(nb,&(var),sizeof(var));
+
 /*
  * Data types
  */
@@ -56,6 +103,16 @@ struct lcc_network_buffer_s
   lcc_value_list_t state;
   char *ptr;
   size_t free;
+
+  lcc_security_level_t seclevel;
+  char *username;
+  char *password;
+
+#if HAVE_LIBGCRYPT
+  gcry_cipher_hd_t encr_cypher;
+  size_t encr_header_len;
+  char encr_iv[16];
+#endif
 };
 
 #define SSTRNCPY(dst,src,sz) do { \
@@ -66,6 +123,34 @@ struct lcc_network_buffer_s
 /*
  * Private functions
  */
+static _Bool have_gcrypt (void) /* {{{ */
+{
+  static _Bool result = 0;
+  static _Bool need_init = 1;
+
+  if (!need_init)
+    return (result);
+  need_init = 0;
+
+#if HAVE_LIBGCRYPT
+# if GCRYPT_VERSION_NUMBER < 0x010600
+  gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+# endif
+
+  if (!gcry_check_version (GCRYPT_VERSION))
+    return (0);
+
+  gcry_control (GCRYCTL_INIT_SECMEM, 32768, 0);
+  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+
+  result = 1;
+  return (1);
+#else
+  return(0);
+#endif
+} /* }}} _Bool have_gcrypt */
+
+#ifndef HAVE_HTONLL
 static uint64_t htonll (uint64_t val) /* {{{ */
 {
   static int config = 0;
@@ -95,6 +180,7 @@ static uint64_t htonll (uint64_t val) /* {{{ */
 
   return ((((uint64_t) lo) << 32) | ((uint64_t) hi));
 } /* }}} uint64_t htonll */
+#endif
 
 static double htond (double val) /* {{{ */
 {
@@ -291,6 +377,15 @@ static int nb_add_number (char **ret_buffer, /* {{{ */
   return (0);
 } /* }}} int nb_add_number */
 
+static int nb_add_time (char **ret_buffer, /* {{{ */
+    size_t *ret_buffer_len,
+    uint16_t type, double value)
+{
+  /* Convert to collectd's "cdtime" representation. */
+  uint64_t cdtime_value = (uint64_t) (value * 1073741824.0);
+  return (nb_add_number (ret_buffer, ret_buffer_len, type, cdtime_value));
+} /* }}} int nb_add_time */
+
 static int nb_add_string (char **ret_buffer, /* {{{ */
     size_t *ret_buffer_len,
     uint16_t type, const char *str, size_t str_len)
@@ -391,16 +486,14 @@ static int nb_add_value_list (lcc_network_buffer_t *nb, /* {{{ */
 
   if (nb->state.time != vl->time)
   {
-    if (nb_add_number (&buffer, &buffer_size, TYPE_TIME,
-          (uint64_t) vl->time))
+    if (nb_add_time (&buffer, &buffer_size, TYPE_TIME_HR, vl->time))
       return (-1);
     nb->state.time = vl->time;
   }
 
   if (nb->state.interval != vl->interval)
   {
-    if (nb_add_number (&buffer, &buffer_size, TYPE_INTERVAL,
-          (uint64_t) vl->interval))
+    if (nb_add_time (&buffer, &buffer_size, TYPE_INTERVAL_HR, vl->interval))
       return (-1);
     nb->state.interval = vl->interval;
   }
@@ -413,6 +506,135 @@ static int nb_add_value_list (lcc_network_buffer_t *nb, /* {{{ */
   return (0);
 } /* }}} int nb_add_value_list */
 
+#if HAVE_LIBGCRYPT
+static int nb_add_signature (lcc_network_buffer_t *nb) /* {{{ */
+{
+  char *buffer;
+  size_t buffer_size;
+
+  gcry_md_hd_t hd;
+  gcry_error_t err;
+  unsigned char *hash;
+  const size_t hash_length = 32;
+
+  /* The type, length and username have already been filled in by
+   * "lcc_network_buffer_initialize". All we do here is calculate the hash over
+   * the username and the data and add the hash value to the buffer. */
+
+  buffer = nb->buffer + PART_SIGNATURE_SHA256_SIZE;
+  assert (nb->size >= (nb->free + PART_SIGNATURE_SHA256_SIZE));
+  buffer_size = nb->size - (nb->free + PART_SIGNATURE_SHA256_SIZE);
+
+  hd = NULL;
+  err = gcry_md_open (&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+  if (err != 0)
+    return (-1);
+
+  assert (nb->password != NULL);
+  err = gcry_md_setkey (hd, nb->password, strlen (nb->password));
+  if (err != 0)
+  {
+    gcry_md_close (hd);
+    return (-1);
+  }
+
+  gcry_md_write (hd, buffer, buffer_size);
+  hash = gcry_md_read (hd, GCRY_MD_SHA256);
+  if (hash == NULL)
+  {
+    gcry_md_close (hd);
+    return (-1);
+  }
+
+  assert (((2 * sizeof (uint16_t)) + hash_length) == PART_SIGNATURE_SHA256_SIZE);
+  memcpy (nb->buffer + (2 * sizeof (uint16_t)), hash, hash_length);
+
+  gcry_md_close (hd);
+  return (0);
+} /* }}} int nb_add_signature */
+
+static int nb_add_encryption (lcc_network_buffer_t *nb) /* {{{ */
+{
+  size_t package_length;
+  char *encr_ptr; /* pointer to data being encrypted */
+  size_t encr_size;
+
+  char *hash_ptr; /* pointer to data being hashed */
+  size_t hash_size;
+  char hash[20];
+
+  uint16_t pkg_length;
+  gcry_error_t err;
+
+  /* Fill in the package length */
+  package_length = nb->size - nb->free;
+  pkg_length = htons ((uint16_t) package_length);
+  memcpy (nb->buffer + 2, &pkg_length, sizeof (pkg_length));
+
+  /* Calculate what to hash */
+  hash_ptr = nb->buffer + PART_ENCRYPTION_AES256_SIZE;
+  hash_size = package_length - nb->encr_header_len;
+
+  /* Calculate what to encrypt */
+  encr_ptr = hash_ptr - sizeof (hash);
+  encr_size = hash_size + sizeof (hash);
+
+  /* Calculate the SHA-1 hash */
+  gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash_ptr, hash_size);
+  memcpy (encr_ptr, hash, sizeof (hash));
+
+  if (nb->encr_cypher == NULL)
+  {
+    unsigned char password_hash[32];
+
+    err = gcry_cipher_open (&nb->encr_cypher,
+        GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_OFB, /* flags = */ 0);
+    if (err != 0)
+      return (-1);
+
+    /* Calculate our 256bit key used for AES */
+    gcry_md_hash_buffer (GCRY_MD_SHA256, password_hash,
+        nb->password, strlen (nb->password));
+
+    err = gcry_cipher_setkey (nb->encr_cypher,
+        password_hash, sizeof (password_hash));
+    if (err != 0)
+    {
+      gcry_cipher_close (nb->encr_cypher);
+      nb->encr_cypher = NULL;
+      return (-1);
+    }
+  }
+  else /* if (nb->encr_cypher != NULL) */
+  {
+    gcry_cipher_reset (nb->encr_cypher);
+  }
+
+  /* Set the initialization vector */
+  err = gcry_cipher_setiv (nb->encr_cypher,
+      nb->encr_iv, sizeof (nb->encr_iv));
+  if (err != 0)
+  {
+    gcry_cipher_close (nb->encr_cypher);
+    nb->encr_cypher = NULL;
+    return (-1);
+  }
+
+  /* Encrypt the buffer in-place */
+  err = gcry_cipher_encrypt (nb->encr_cypher,
+      encr_ptr, encr_size,
+      /* in = */ NULL, /* in len = */ 0);
+  if (err != 0)
+  {
+    gcry_cipher_close (nb->encr_cypher);
+    nb->encr_cypher = NULL;
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int nb_add_encryption */
+#endif
+
 /*
  * Public functions
  */
@@ -446,6 +668,10 @@ lcc_network_buffer_t *lcc_network_buffer_create (size_t size) /* {{{ */
   nb->ptr = nb->buffer;
   nb->free = nb->size;
 
+  nb->seclevel = NONE;
+  nb->username = NULL;
+  nb->password = NULL;
+
   return (nb);
 } /* }}} lcc_network_buffer_t *lcc_network_buffer_create */
 
@@ -460,10 +686,42 @@ void lcc_network_buffer_destroy (lcc_network_buffer_t *nb) /* {{{ */
 
 int lcc_network_buffer_set_security_level (lcc_network_buffer_t *nb, /* {{{ */
     lcc_security_level_t level,
-    const char *user, const char *password)
+    const char *username, const char *password)
 {
-  /* FIXME: Not yet implemented */
-  return (-1);
+  char *username_copy;
+  char *password_copy;
+
+  if (level == NONE)
+  {
+    free (nb->username);
+    free (nb->password);
+    nb->username = NULL;
+    nb->password = NULL;
+    nb->seclevel = NONE;
+    lcc_network_buffer_initialize (nb);
+    return (0);
+  }
+
+  if (!have_gcrypt ())
+    return (ENOTSUP);
+
+  username_copy = strdup (username);
+  password_copy = strdup (password);
+  if ((username_copy == NULL) || (password_copy == NULL))
+  {
+    free (username_copy);
+    free (password_copy);
+    return (ENOMEM);
+  }
+
+  free (nb->username);
+  free (nb->password);
+  nb->username = username_copy;
+  nb->password = password_copy;
+  nb->seclevel = level;
+
+  lcc_network_buffer_initialize (nb);
+  return (0);
 } /* }}} int lcc_network_buffer_set_security_level */
 
 int lcc_network_buffer_initialize (lcc_network_buffer_t *nb) /* {{{ */
@@ -476,8 +734,53 @@ int lcc_network_buffer_initialize (lcc_network_buffer_t *nb) /* {{{ */
   nb->ptr = nb->buffer;
   nb->free = nb->size;
 
-  /* FIXME: If security is enabled, reserve space for the signature /
-   * encryption block here. */
+#if HAVE_LIBGCRYPT
+  if (nb->seclevel == SIGN)
+  {
+    size_t username_len;
+    uint16_t pkg_type = htons (TYPE_SIGN_SHA256);
+    uint16_t pkg_length = PART_SIGNATURE_SHA256_SIZE;
+
+    assert (nb->username != NULL);
+    username_len = strlen (nb->username);
+    pkg_length = htons (pkg_length + ((uint16_t) username_len));
+
+    /* Fill in everything but the hash value here. */
+    memcpy (nb->ptr, &pkg_type, sizeof (pkg_type));
+    memcpy (nb->ptr + sizeof (pkg_type), &pkg_length, sizeof (pkg_length));
+    nb->ptr += PART_SIGNATURE_SHA256_SIZE;
+    nb->free -= PART_SIGNATURE_SHA256_SIZE;
+
+    memcpy (nb->ptr, nb->username, username_len);
+    nb->ptr += username_len;
+    nb->free -= username_len;
+  }
+  else if (nb->seclevel == ENCRYPT)
+  {
+    size_t username_length = strlen (nb->username);
+    uint16_t pkg_type = htons (TYPE_ENCR_AES256);
+    uint16_t pkg_length = 0; /* Filled in in finalize. */
+    uint16_t pkg_user_len = htons ((uint16_t) username_length);
+    char hash[20];
+
+    nb->encr_header_len = username_length;
+    nb->encr_header_len += PART_ENCRYPTION_AES256_SIZE;
+
+    gcry_randomize ((void *) &nb->encr_iv, sizeof (nb->encr_iv),
+        GCRY_STRONG_RANDOM);
+
+    /* Filled in in finalize. */
+    memset (hash, 0, sizeof (hash));
+
+    ADD_STATIC (nb, pkg_type);
+    ADD_STATIC (nb, pkg_length);
+    ADD_STATIC (nb, pkg_user_len);
+    ADD_GENERIC (nb, nb->username, username_length);
+    ADD_GENERIC (nb, nb->encr_iv, sizeof (nb->encr_iv));
+    ADD_GENERIC (nb, hash, sizeof (hash));
+    assert ((nb->encr_header_len + nb->free) == nb->size);
+  }
+#endif
 
   return (0);
 } /* }}} int lcc_network_buffer_initialize */
@@ -487,7 +790,12 @@ int lcc_network_buffer_finalize (lcc_network_buffer_t *nb) /* {{{ */
   if (nb == NULL)
     return (EINVAL);
 
-  /* FIXME: If security is enabled, sign or encrypt the packet here. */
+#if HAVE_LIBGCRYPT
+  if (nb->seclevel == SIGN)
+    return nb_add_signature (nb);
+  else if (nb->seclevel == ENCRYPT)
+    return nb_add_encryption (nb);
+#endif
 
   return (0);
 } /* }}} int lcc_network_buffer_finalize */