summaryrefslogtreecommitdiffstats
path: root/sntp/networking.c
diff options
context:
space:
mode:
Diffstat (limited to 'sntp/networking.c')
-rw-r--r--sntp/networking.c536
1 files changed, 536 insertions, 0 deletions
diff --git a/sntp/networking.c b/sntp/networking.c
new file mode 100644
index 0000000..31bc5f8
--- /dev/null
+++ b/sntp/networking.c
@@ -0,0 +1,536 @@
+#include <config.h>
+#include "networking.h"
+
+char adr_buf[INET6_ADDRSTRLEN];
+
+
+/* resolve_hosts consumes an array of hostnames/addresses and its length, stores a pointer
+ * to the array with the resolved hosts in res and returns the size of the array res.
+ * pref_family enforces IPv4 or IPv6 depending on commandline options and system
+ * capability. If pref_family is NULL or PF_UNSPEC any compatible family will be accepted.
+ * Check here: Probably getaddrinfo() can do without ISC's IPv6 availability check?
+ */
+int
+resolve_hosts (
+ const char **hosts,
+ int hostc,
+ struct addrinfo ***res,
+ int pref_family
+ )
+{
+ register unsigned int a;
+ unsigned int resc;
+ struct addrinfo **tres;
+
+ if (hostc < 1 || NULL == res)
+ return 0;
+
+ tres = emalloc(sizeof(struct addrinfo *) * hostc);
+ for (a = 0, resc = 0; a < hostc; a++) {
+ struct addrinfo hints;
+ int error;
+
+ tres[resc] = NULL;
+#ifdef DEBUG
+ printf("sntp resolve_hosts: Starting host resolution for %s...\n", hosts[a]);
+#endif
+ memset(&hints, 0, sizeof(hints));
+ if (AF_UNSPEC == pref_family)
+ hints.ai_family = PF_UNSPEC;
+ else
+ hints.ai_family = pref_family;
+ hints.ai_socktype = SOCK_DGRAM;
+ error = getaddrinfo(hosts[a], "123", &hints, &tres[resc]);
+ if (error) {
+ msyslog(LOG_DEBUG, "Error looking up %s%s: %s",
+ (AF_UNSPEC == hints.ai_family)
+ ? ""
+ : (AF_INET == hints.ai_family)
+ ? "(A) "
+ : "(AAAA) ",
+ hosts[a], gai_strerror(error));
+ } else {
+#ifdef DEBUG
+ for (dres = tres[resc]; dres; dres = dres->ai_next) {
+ getnameinfo(dres->ai_addr, dres->ai_addrlen, adr_buf, sizeof(adr_buf), NULL, 0, NI_NUMERICHOST);
+ STDLINE
+ printf("Resolv No.: %i Result of getaddrinfo for %s:\n", resc, hosts[a]);
+ printf("socktype: %i ", dres->ai_socktype);
+ printf("protocol: %i ", dres->ai_protocol);
+ printf("Prefered socktype: %i IP: %s\n", dres->ai_socktype, adr_buf);
+ STDLINE
+ }
+#endif
+ resc++;
+ }
+ }
+
+ if (resc)
+ *res = realloc(tres, sizeof(struct addrinfo *) * resc);
+ else {
+ free(tres);
+ *res = NULL;
+ }
+ return resc;
+}
+
+/* Creates a socket and returns. */
+void
+create_socket (
+ SOCKET *rsock,
+ sockaddr_u *dest
+ )
+{
+ *rsock = socket(AF(dest), SOCK_DGRAM, 0);
+
+ if (-1 == *rsock && ENABLED_OPT(NORMALVERBOSE))
+ printf("Failed to create UDP socket with family %d\n", AF(dest));
+}
+
+/* Send a packet */
+void
+sendpkt (
+ SOCKET rsock,
+ sockaddr_u *dest,
+ struct pkt *pkt,
+ int len
+ )
+{
+ int cc;
+
+#ifdef DEBUG
+ printf("sntp sendpkt: Packet data:\n");
+ pkt_output(pkt, len, stdout);
+#endif
+
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ getnameinfo(&dest->sa, SOCKLEN(dest), adr_buf, sizeof(adr_buf), NULL, 0, NI_NUMERICHOST);
+ printf("sntp sendpkt: Sending packet to %s... ", adr_buf);
+ }
+
+ cc = sendto(rsock, (void *)pkt, len, 0, &dest->sa, SOCKLEN(dest));
+ if (cc == SOCKET_ERROR) {
+#ifdef DEBUG
+ printf("\n sntp sendpkt: Socket error: %i. Couldn't send packet!\n", cc);
+#endif
+ if (errno != EWOULDBLOCK && errno != ENOBUFS) {
+ /* oh well */
+ }
+ } else if (ENABLED_OPT(NORMALVERBOSE)) {
+ printf("Packet sent.\n");
+ }
+}
+
+/* Receive raw data */
+int
+recvdata(
+ SOCKET rsock,
+ sockaddr_u *sender,
+ char *rdata,
+ int rdata_length
+ )
+{
+ GETSOCKNAME_SOCKLEN_TYPE slen;
+ int recvc;
+
+#ifdef DEBUG
+ printf("sntp recvdata: Trying to receive data from...\n");
+#endif
+ slen = sizeof(*sender);
+ recvc = recvfrom(rsock, rdata, rdata_length, 0,
+ &sender->sa, &slen);
+#ifdef DEBUG
+ if (recvc > 0) {
+ printf("Received %d bytes from %s:\n", recvc, stoa(sender));
+ pkt_output((struct pkt *) rdata, recvc, stdout);
+ } else {
+ saved_errno = errno;
+ printf("recvfrom error %d (%s)\n", errno, strerror(errno));
+ errno = saved_errno;
+ }
+#endif
+ return recvc;
+}
+
+/* Receive data from broadcast. Couldn't finish that. Need to do some digging
+ * here, especially for protocol independence and IPv6 multicast */
+int
+recv_bcst_data (
+ SOCKET rsock,
+ char *rdata,
+ int rdata_len,
+ sockaddr_u *sas,
+ sockaddr_u *ras
+ )
+{
+ char *buf;
+ int btrue = 1;
+ int recv_bytes = 0;
+ int rdy_socks;
+ GETSOCKNAME_SOCKLEN_TYPE ss_len;
+ struct timeval timeout_tv;
+ fd_set bcst_fd;
+#ifdef MCAST
+ struct ip_mreq mdevadr;
+ TYPEOF_IP_MULTICAST_LOOP mtrue = 1;
+#endif
+#ifdef INCLUDE_IPV6_MULTICAST_SUPPORT
+ struct ipv6_mreq mdevadr6;
+#endif
+
+ setsockopt(rsock, SOL_SOCKET, SO_REUSEADDR, &btrue, sizeof(btrue));
+ if (IS_IPV4(sas)) {
+ if (bind(rsock, &sas->sa, SOCKLEN(sas)) < 0) {
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp recv_bcst_data: Couldn't bind() address %s:%d.\n",
+ stoa(sas), SRCPORT(sas));
+ }
+
+#ifdef MCAST
+ if (setsockopt(rsock, IPPROTO_IP, IP_MULTICAST_LOOP, &mtrue, sizeof(mtrue)) < 0) {
+ /* some error message regarding setting up multicast loop */
+ return BROADCAST_FAILED;
+ }
+ mdevadr.imr_multiaddr.s_addr = NSRCADR(sas);
+ mdevadr.imr_interface.s_addr = htonl(INADDR_ANY);
+ if (mdevadr.imr_multiaddr.s_addr == -1) {
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ printf("sntp recv_bcst_data: %s:%d is not a broad-/multicast address, aborting...\n",
+ stoa(sas), SRCPORT(sas));
+ }
+ return BROADCAST_FAILED;
+ }
+ if (setsockopt(rsock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mdevadr, sizeof(mdevadr)) < 0) {
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ buf = ss_to_str(sas);
+ printf("sntp recv_bcst_data: Couldn't add IP membership for %s\n", buf);
+ free(buf);
+ }
+ }
+#endif /* MCAST */
+ }
+#ifdef ISC_PLATFORM_HAVEIPV6
+ else if (IS_IPV6(sas)) {
+ if (bind(rsock, &sas->sa, SOCKLEN(sas)) < 0) {
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp recv_bcst_data: Couldn't bind() address.\n");
+ }
+#ifdef INCLUDE_IPV6_MULTICAST_SUPPORT
+ if (setsockopt(rsock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &btrue, sizeof (btrue)) < 0) {
+ /* some error message regarding setting up multicast loop */
+ return BROADCAST_FAILED;
+ }
+ memset(&mdevadr6, 0, sizeof(mdevadr6));
+ mdevadr6.ipv6mr_multiaddr = SOCK_ADDR6(sas);
+ if (!IN6_IS_ADDR_MULTICAST(&mdevadr6.ipv6mr_multiaddr)) {
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ buf = ss_to_str(sas);
+ printf("sntp recv_bcst_data: %s is not a broad-/multicast address, aborting...\n", buf);
+ free(buf);
+ }
+ return BROADCAST_FAILED;
+ }
+ if (setsockopt(rsock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mdevadr6, sizeof(mdevadr6)) < 0) {
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ buf = ss_to_str(sas);
+ printf("sntp recv_bcst_data: Couldn't join group for %s\n", buf);
+ free(buf);
+ }
+ }
+#endif /* INCLUDE_IPV6_MULTICAST_SUPPORT */
+ }
+#endif /* ISC_PLATFORM_HAVEIPV6 */
+ FD_ZERO(&bcst_fd);
+ FD_SET(rsock, &bcst_fd);
+ if (ENABLED_OPT(TIMEOUT))
+ timeout_tv.tv_sec = (int) OPT_ARG(TIMEOUT);
+ else
+ timeout_tv.tv_sec = 68; /* ntpd broadcasts every 64s */
+ timeout_tv.tv_usec = 0;
+ rdy_socks = select(rsock + 1, &bcst_fd, 0, 0, &timeout_tv);
+ switch (rdy_socks) {
+ case -1:
+ if (ENABLED_OPT(NORMALVERBOSE))
+ perror("sntp recv_bcst_data: select()");
+ return BROADCAST_FAILED;
+ break;
+ case 0:
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp recv_bcst_data: select() reached timeout (%u sec), aborting.\n",
+ (unsigned)timeout_tv.tv_sec);
+ return BROADCAST_FAILED;
+ break;
+ default:
+ ss_len = sizeof(*ras);
+ recv_bytes = recvfrom(rsock, rdata, rdata_len, 0, &ras->sa, &ss_len);
+ break;
+ }
+ if (recv_bytes == -1) {
+ if (ENABLED_OPT(NORMALVERBOSE))
+ perror("sntp recv_bcst_data: recvfrom:");
+ recv_bytes = BROADCAST_FAILED;
+ }
+#ifdef MCAST
+ if (IS_IPV4(sas))
+ setsockopt(rsock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &btrue, sizeof(btrue));
+#endif
+#ifdef INCLUDE_IPV6_MULTICAST_SUPPORT
+ if (IS_IPV6(sas))
+ setsockopt(rsock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &btrue, sizeof(btrue));
+#endif
+ return recv_bytes;
+}
+
+int
+process_pkt (
+ struct pkt *rpkt,
+ sockaddr_u *sas,
+ int pkt_len,
+ int mode,
+ struct pkt *spkt,
+ char * func_name
+ )
+{
+ unsigned int key_id = 0;
+ struct key *pkt_key = NULL;
+ int is_authentic = 0;
+ unsigned int exten_words, exten_words_used = 0;
+ int mac_size;
+ /*
+ * Parse the extension field if present. We figure out whether
+ * an extension field is present by measuring the MAC size. If
+ * the number of words following the packet header is 0, no MAC
+ * is present and the packet is not authenticated. If 1, the
+ * packet is a crypto-NAK; if 3, the packet is authenticated
+ * with DES; if 5, the packet is authenticated with MD5; if 6,
+ * the packet is authenticated with SHA. If 2 or 4, the packet
+ * is a runt and discarded forthwith. If greater than 6, an
+ * extension field is present, so we subtract the length of the
+ * field and go around again.
+ */
+ if (pkt_len < LEN_PKT_NOMAC || (pkt_len & 3) != 0) {
+unusable:
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp %s: Funny packet length: %i. Discarding package.\n", func_name, pkt_len);
+ return PACKET_UNUSEABLE;
+ }
+ /* skip past the extensions, if any */
+ exten_words = ((unsigned)pkt_len - LEN_PKT_NOMAC) >> 2;
+ while (exten_words > 6) {
+ unsigned int exten_len;
+ exten_len = ntohl(rpkt->exten[exten_words_used]) & 0xffff;
+ exten_len = (exten_len + 7) >> 2; /* convert to words, add 1 */
+ if (exten_len > exten_words || exten_len < 5)
+ goto unusable;
+ exten_words -= exten_len;
+ exten_words_used += exten_len;
+ }
+
+ switch (exten_words) {
+ case 1:
+ key_id = ntohl(rpkt->exten[exten_words_used]);
+ printf("Crypto NAK = 0x%08x\n", key_id);
+ break;
+ case 5:
+ case 6:
+ /* Look for the key used by the server in the specified keyfile
+ * and if existent, fetch it or else leave the pointer untouched */
+ key_id = ntohl(rpkt->exten[exten_words_used]);
+ get_key(key_id, &pkt_key);
+ if (!pkt_key) {
+ printf("unrecognized key ID = 0x%08x\n", key_id);
+ break;
+ }
+ /* Seems like we've got a key with matching keyid */
+ /* Generate a md5sum of the packet with the key from our keyfile
+ * and compare those md5sums */
+ mac_size = exten_words << 2;
+ if (!auth_md5((char *)rpkt, pkt_len - mac_size, mac_size - 4, pkt_key)) {
+ break;
+ }
+ /* Yay! Things worked out! */
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ char *hostname = ss_to_str(sas);
+ printf("sntp %s: packet received from %s successfully authenticated using key id %i.\n",
+ func_name, hostname, key_id);
+ free(hostname);
+ }
+ is_authentic = 1;
+ break;
+ case 0:
+ break;
+ default:
+ goto unusable;
+ break;
+ }
+ if (!is_authentic) {
+ if (ENABLED_OPT(AUTHENTICATION)) {
+ /* We want a authenticated packet */
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ char *hostname = ss_to_str(sas);
+ printf("sntp %s: packet received from %s is not authentic. Will discard it.\n",
+ func_name, hostname);
+ free(hostname);
+ }
+ return SERVER_AUTH_FAIL;
+ }
+ /* We don't know if the user wanted authentication so let's
+ * use it anyways */
+ if (ENABLED_OPT(NORMALVERBOSE)) {
+ char *hostname = ss_to_str(sas);
+ printf("sntp %s: packet received from %s is not authentic. Authentication not enforced.\n",
+ func_name, hostname);
+ free(hostname);
+ }
+ }
+ /* Check for server's ntp version */
+ if (PKT_VERSION(rpkt->li_vn_mode) < NTP_OLDVERSION ||
+ PKT_VERSION(rpkt->li_vn_mode) > NTP_VERSION) {
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp %s: Packet shows wrong version (%i)\n",
+ func_name, PKT_VERSION(rpkt->li_vn_mode));
+ return SERVER_UNUSEABLE;
+ }
+ /* We want a server to sync with */
+ if (PKT_MODE(rpkt->li_vn_mode) != mode &&
+ PKT_MODE(rpkt->li_vn_mode) != MODE_PASSIVE) {
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp %s: mode %d stratum %i\n", func_name,
+ PKT_MODE(rpkt->li_vn_mode), rpkt->stratum);
+ return SERVER_UNUSEABLE;
+ }
+ /* Stratum is unspecified (0) check what's going on */
+ if (STRATUM_PKT_UNSPEC == rpkt->stratum) {
+ char *ref_char;
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp %s: Stratum unspecified, going to check for KOD (stratum: %i)\n",
+ func_name, rpkt->stratum);
+ ref_char = (char *) &rpkt->refid;
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp %s: Packet refid: %c%c%c%c\n", func_name,
+ ref_char[0], ref_char[1], ref_char[2], ref_char[3]);
+ /* If it's a KOD packet we'll just use the KOD information */
+ if (ref_char[0] != 'X') {
+ if (strncmp(ref_char, "DENY", 4) == 0)
+ return KOD_DEMOBILIZE;
+ if (strncmp(ref_char, "RSTR", 4) == 0)
+ return KOD_DEMOBILIZE;
+ if (strncmp(ref_char, "RATE", 4) == 0)
+ return KOD_RATE;
+ /* There are other interesting kiss codes which might be interesting for authentication */
+ }
+ }
+ /* If the server is not synced it's not really useable for us */
+ if (LEAP_NOTINSYNC == PKT_LEAP(rpkt->li_vn_mode)) {
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp %s: Server not in sync, skipping this server\n", func_name);
+ return SERVER_UNUSEABLE;
+ }
+
+ /*
+ * Decode the org timestamp and make sure we're getting a response
+ * to our last request, but only if we're not in broadcast mode.
+ */
+#ifdef DEBUG
+ printf("rpkt->org:\n");
+ l_fp_output(&rpkt->org, stdout);
+ printf("spkt->xmt:\n");
+ l_fp_output(&spkt->xmt, stdout);
+#endif
+ if (mode != MODE_BROADCAST && !L_ISEQU(&rpkt->org, &spkt->xmt)) {
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp process_pkt: pkt.org and peer.xmt differ\n");
+ return PACKET_UNUSEABLE;
+ }
+
+ return pkt_len;
+}
+
+int
+recv_bcst_pkt (
+ SOCKET rsock,
+ struct pkt *rpkt,
+ unsigned int rsize,
+ sockaddr_u *sas
+ )
+{
+ sockaddr_u sender;
+ int pkt_len = recv_bcst_data(rsock, (char *)rpkt, rsize, sas, &sender);
+ if (pkt_len < 0) {
+ return BROADCAST_FAILED;
+ }
+ pkt_len = process_pkt(rpkt, sas, pkt_len, MODE_BROADCAST, NULL, "recv_bcst_pkt");
+ return pkt_len;
+}
+
+/* Fetch data, check if it's data for us and whether it's useable or not. If not, return
+ * a failure code so we can delete this server from our list and continue with another one.
+ */
+int
+recvpkt (
+ SOCKET rsock,
+ struct pkt *rpkt, /* received packet (response) */
+ unsigned int rsize, /* size of rpkt buffer */
+ struct pkt *spkt /* sent packet (request) */
+ )
+{
+ int rdy_socks;
+ int pkt_len;
+ sockaddr_u sender;
+ struct timeval timeout_tv;
+ fd_set recv_fd;
+
+ FD_ZERO(&recv_fd);
+ FD_SET(rsock, &recv_fd);
+ if (ENABLED_OPT(TIMEOUT))
+ timeout_tv.tv_sec = (int) OPT_ARG(TIMEOUT);
+ else
+ timeout_tv.tv_sec = 68; /* ntpd broadcasts every 64s */
+ timeout_tv.tv_usec = 0;
+ rdy_socks = select(rsock + 1, &recv_fd, 0, 0, &timeout_tv);
+ switch (rdy_socks) {
+ case -1:
+ if (ENABLED_OPT(NORMALVERBOSE))
+ perror("sntp recvpkt: select()");
+ return PACKET_UNUSEABLE;
+ break;
+ case 0:
+ if (ENABLED_OPT(NORMALVERBOSE))
+ printf("sntp recvpkt: select() reached timeout (%u sec), aborting.\n",
+ (unsigned)timeout_tv.tv_sec);
+ return PACKET_UNUSEABLE;
+ break;
+ default:
+ break;
+ }
+ pkt_len = recvdata(rsock, &sender, (char *)rpkt, rsize);
+ if (pkt_len > 0)
+ pkt_len = process_pkt(rpkt, &sender, pkt_len, MODE_SERVER, spkt, "recvpkt");
+
+ return pkt_len;
+}
+
+/*
+ * is_reachable - check to see if we have a route to given destination
+ */
+int
+is_reachable (
+ struct addrinfo *dst
+ )
+{
+ SOCKET sockfd = socket(dst->ai_family, SOCK_DGRAM, 0);
+
+ if (-1 == sockfd) {
+#ifdef DEBUG
+ printf("is_reachable: Couldn't create socket\n");
+#endif
+ return 0;
+ }
+ if (connect(sockfd, dst->ai_addr, SOCKLEN((sockaddr_u *)dst->ai_addr))) {
+ closesocket(sockfd);
+ return 0;
+ }
+ closesocket(sockfd);
+ return 1;
+}
OpenPOWER on IntegriCloud