ntpd plugin: Fix a possible buffer overflow.
[collectd.git] / src / ntpd.c
index 0fa3b34..08c0c9f 100644 (file)
 # include <sys/poll.h>
 #endif
 
+static char *config_keys[] =
+{
+       "Host",
+       "Port",
+       NULL
+};
+static int config_keys_num = 2;
+
 /* drift */
-static char *time_offset_file = "ntpd/time_offset-%s.rrd";
-static char *time_offset_ds_def[] =
+static char *time_offset_file     = "ntpd/time_offset-%s.rrd";
+static char *time_dispersion_file = "ntpd/time_dispersion-%s.rrd";
+static char *time_delay_file      = "ntpd/delay-%s.rrd";
+
+/* used for `time_offset', `time_dispersion', and `delay' */
+static char *sec_ds_def[] =
 {
-       "DS:ms:GAUGE:"COLLECTD_HEARTBEAT":-1000000:1000000",
+       "DS:seconds:GAUGE:"COLLECTD_HEARTBEAT":-1000000:1000000",
        NULL
 };
-static int time_offset_ds_num = 1;
+static int sec_ds_num = 1;
 
 static char *frequency_offset_file = "ntpd/frequency_offset-%s.rrd";
 static char *frequency_offset_ds_def[] =
@@ -90,6 +102,9 @@ static char *ntpd_port = NULL;
 #define IMPL_XNTPD 3
 #define FP_FRAC 65536.0
 
+#define REFCLOCK_ADDR 0x7f7f0000 /* 127.127.0.0 */
+#define REFCLOCK_MASK 0xffff0000 /* 255.255.0.0 */
+
 /* This structure is missing the message authentication code, since collectd
  * doesn't use it. */
 struct req_pkt
@@ -249,26 +264,83 @@ struct info_kernel
        int32_t  errcnt;
        int32_t  stbcnt;
 };
+
+/* List of reference clock names */
+static char *refclock_names[] =
+{
+       "UNKNOWN",    "LOCAL",        "GPS_TRAK",   "WWV_PST",     /*  0- 3 */
+       "SPECTRACOM", "TRUETIME",     "IRIG_AUDIO", "CHU_AUDIO",   /*  4- 7 */
+       "GENERIC",    "GPS_MX4200",   "GPS_AS2201", "GPS_ARBITER", /*  8-11 */
+       "IRIG_TPRO",  "ATOM_LEITCH",  "MSF_EES",    "GPSTM_TRUE",  /* 12-15 */
+       "GPS_BANC",   "GPS_DATUM",    "ACTS_NIST",  "WWV_HEATH",   /* 16-19 */
+       "GPS_NMEA",   "GPS_VME",      "PPS",        "ACTS_PTB",    /* 20-23 */
+       "ACTS_USNO",  "TRUETIME",     "GPS_HP",     "MSF_ARCRON",  /* 24-27 */
+       "SHM",        "GPS_PALISADE", "GPS_ONCORE", "GPS_JUPITER", /* 28-31 */
+       "CHRONOLOG",  "DUMBCLOCK",    "ULINK_M320", "PCF",         /* 32-35 */
+       "WWV_AUDIO",  "GPS_FG",       "HOPF_S",     "HOPF_P",      /* 36-39 */
+       "JJY",        "TT_IRIG",      "GPS_ZYFER",  "GPS_RIPENCC", /* 40-43 */
+       "NEOCLK4X",   NULL                                         /* 44    */
+};
+static int refclock_names_num = 45;
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * End of the copied stuff..                                         *
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 
+static int ntpd_config (char *key, char *value)
+{
+       if (strcasecmp (key, "host") == 0)
+       {
+               if (ntpd_host != NULL)
+                       free (ntpd_host);
+               if ((ntpd_host = strdup (value)) == NULL)
+                       return (1);
+       }
+       else if (strcasecmp (key, "port") == 0)
+       {
+               if (ntpd_port != NULL)
+                       free (ntpd_port);
+               if ((ntpd_port = strdup (value)) == NULL)
+                       return (1);
+       }
+       else
+       {
+               return (-1);
+       }
+
+       return (0);
+}
+
 static void ntpd_init (void)
 {
        return;
 }
 
-static void ntpd_write_time_offset (char *host, char *inst, char *val)
+static void ntpd_write_sec (char *host, char *inst, char *val, char *file)
 {
        char buf[256];
        int  status;
 
-       status = snprintf (buf, 256, time_offset_file, inst);
+       status = snprintf (buf, 256, file, inst);
        if ((status < 1) || (status >= 256))
                return;
 
        rrd_update_file (host, buf, val,
-                       time_offset_ds_def, time_offset_ds_num);
+                       sec_ds_def, sec_ds_num);
+}
+
+static void ntpd_write_time_offset (char *host, char *inst, char *val)
+{
+       ntpd_write_sec (host, inst, val, time_offset_file);
+}
+
+static void ntpd_write_time_dispersion (char *host, char *inst, char *val)
+{
+       ntpd_write_sec (host, inst, val, time_dispersion_file);
+}
+
+static void ntpd_write_delay (char *host, char *inst, char *val)
+{
+       ntpd_write_sec (host, inst, val, time_delay_file);
 }
 
 static void ntpd_write_frequency_offset (char *host, char *inst, char *val)
@@ -488,6 +560,9 @@ static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
                if (status < 0)
                {
                        DBG ("recv(2) failed: %s", strerror (errno));
+                       DBG ("Closing socket #%i", sd);
+                       close (sd);
+                       sock_descr = sd = -1;
                        return (-1);
                }
 
@@ -556,6 +631,14 @@ static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
                        continue;
                }
 
+               if (pkt_item_len > res_item_size)
+               {
+                       syslog (LOG_ERR, "ntpd plugin: (pkt_item_len = %i) "
+                                       ">= (res_item_size = %i)",
+                                       pkt_item_len, res_item_size);
+                       continue;
+               }
+
                /* If this is the first packet (time wise, not sequence wise),
                 * set `res_size'. If it's not the first packet check if the
                 * items have the same size. Discard invalid packets. */
@@ -572,9 +655,16 @@ static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
                        continue;
                }
 
+               /*
+                * Because the items in the packet may be smaller than the
+                * items requested, the following holds true:
+                */
+               assert ((*res_size == pkt_item_len)
+                               && (pkt_item_len <= res_item_size));
+
                /* Calculate the padding. No idea why there might be any padding.. */
                pkt_padding = 0;
-               if (res_item_size > pkt_item_len)
+               if (pkt_item_len < res_item_size)
                        pkt_padding = res_item_size - pkt_item_len;
                DBG ("res_item_size = %i; pkt_padding = %i;",
                                res_item_size, pkt_padding);
@@ -625,11 +715,20 @@ static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
                        syslog (LOG_ERR, "ntpd plugin: realloc failed.");
                        continue;
                }
+               items_num += pkt_item_num;
                *res_data = items;
 
                for (i = 0; i < pkt_item_num; i++)
                {
+                       /* dst: There are already `*res_items' items with
+                        *      res_item_size bytes each in in `*res_data'. Set
+                        *      dst to the first byte after that. */
                        void *dst = (void *) (*res_data + ((*res_items) * res_item_size));
+                       /* src: We use `pkt_item_len' to calculate the offset
+                        *      from the beginning of the packet, because the
+                        *      items in the packet may be smaller than the
+                        *      items that were requested. We skip `i' such
+                        *      items. */
                        void *src = (void *) (((char *) res.data) + (i * pkt_item_len));
 
                        /* Set the padding to zeros */
@@ -637,8 +736,10 @@ static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
                                memset (dst, '\0', res_item_size);
                        memcpy (dst, src, (size_t) pkt_item_len);
 
+                       /* Increment `*res_items' by one, so `dst' will end up
+                        * one further in the next round. */
                        (*res_items)++;
-               }
+               } /* for (pkt_item_num) */
 
                pkt_recvd[pkt_sequence] = (char) 1;
                pkt_recvd_num++;
@@ -648,7 +749,7 @@ static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
        } /* while (done == 0) */
 
        return (0);
-}
+} /* int ntpd_receive_response */
 
 /* For a description of the arguments see `ntpd_do_query' below. */
 static int ntpd_send_request (int req_code, int req_items, int req_size, char *req_data)
@@ -686,7 +787,12 @@ static int ntpd_send_request (int req_code, int req_items, int req_size, char *r
 
        status = swrite (sd, (const char *) &req, REQ_LEN_NOMAC);
        if (status < 0)
+       {
+               DBG ("`swrite' failed. Closing socket #%i", sd);
+               close (sd);
+               sock_descr = sd = -1;
                return (status);
+       }
 
        return (0);
 }
@@ -801,20 +907,31 @@ static void ntpd_read (void)
                struct info_peer_summary *ptr;
                double offset;
 
-               char peername[512];
+               char peername[NI_MAXHOST];
+               int refclock_id;
                
                ptr = ps + i;
+               refclock_id = 0;
 
-               if (((ntohl (ptr->dstadr) & 0xFF000000) == 0x7F000000) || (ptr->dstadr == 0))
+               /*
+               if (((ntohl (ptr->dstadr) & 0xFFFFFF00) == 0x7F000000) || (ptr->dstadr == 0))
                        continue;
+                       */
 
                /* Convert the `long floating point' offset value to double */
                M_LFPTOD (ntohl (ptr->offset_int), ntohl (ptr->offset_frc), offset);
 
                if (ptr->v6_flag)
                {
-                       status = getnameinfo ((const struct sockaddr *) &ptr->srcadr6,
-                                       sizeof (ptr->srcadr6),
+                       struct sockaddr_in6 sa;
+
+                       memset (&sa, 0, sizeof (sa));
+                       sa.sin6_family = AF_INET6;
+                       sa.sin6_port = htons (123);
+                       memcpy (&sa.sin6_addr, &ptr->srcadr6, sizeof (struct in6_addr));
+
+                       status = getnameinfo ((const struct sockaddr *) &sa,
+                                       sizeof (sa),
                                        peername, sizeof (peername),
                                        NULL, 0, 0 /* no flags */);
                        if (status != 0)
@@ -826,6 +943,27 @@ static void ntpd_read (void)
                                continue;
                        }
                }
+               else if ((ntohl (ptr->srcadr) & REFCLOCK_MASK) == REFCLOCK_ADDR)
+               {
+                       struct in_addr  addr_obj;
+                       char *addr_str;
+
+                       refclock_id = (ntohl (ptr->srcadr) >> 8) & 0x000000FF;
+
+                       if (refclock_id < refclock_names_num)
+                       {
+                               strncpy (peername, refclock_names[refclock_id],
+                                               sizeof (peername));
+                       }
+                       else
+                       {
+                               memset ((void *) &addr_obj, '\0', sizeof (addr_obj));
+                               addr_obj.s_addr = ptr->srcadr;
+                               addr_str = inet_ntoa (addr_obj);
+
+                               strncpy (peername, addr_str, sizeof (peername));
+                       }
+               }
                else /* IPv4 */
                {
                        struct in_addr  addr_obj;
@@ -865,7 +1003,11 @@ static void ntpd_read (void)
                                offset,
                                ntpd_read_fp (ptr->dispersion));
 
-               ntpd_submit ("ntpd_time_offset", peername, offset);
+               if (refclock_id != 1) /* not the system clock (offset will always be zero.. */
+                       ntpd_submit ("ntpd_time_offset", peername, offset);
+               ntpd_submit ("ntpd_time_dispersion", peername, ntpd_read_fp (ptr->dispersion));
+               if (refclock_id == 0) /* not a reference clock */
+                       ntpd_submit ("ntpd_delay", peername, ntpd_read_fp (ptr->delay));
        }
 
        free (ps);
@@ -881,7 +1023,10 @@ void module_register (void)
 {
        plugin_register (MODULE_NAME, ntpd_init, ntpd_read, NULL);
        plugin_register ("ntpd_time_offset", NULL, NULL, ntpd_write_time_offset);
+       plugin_register ("ntpd_time_dispersion", NULL, NULL, ntpd_write_time_dispersion);
+       plugin_register ("ntpd_delay", NULL, NULL, ntpd_write_delay);
        plugin_register ("ntpd_frequency_offset", NULL, NULL, ntpd_write_frequency_offset);
+       cf_register (MODULE_NAME, ntpd_config, config_keys, config_keys_num);
 }
 
 #undef MODULE_NAME