noip.c: Factor out address-range handling functions.
authorMark Wooding <mdw@distorted.org.uk>
Mon, 2 May 2016 22:05:07 +0000 (23:05 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 2 May 2016 23:14:26 +0000 (00:14 +0100)
  * Introduce `parse_addrrange' and `foreach_addrrange' for parsing.
    The former captures a representation of the range syntax, which can
    contain things like `local' that actually cover multiple ranges, and
    the latter iterates over the implied address ranges.

  * Introduce `dump_addrrange' to produce a readable description of a
    range in the debugging output.

No functional changes.

noip.c

diff --git a/noip.c b/noip.c
index 3756ab9..423822d 100644 (file)
--- a/noip.c
+++ b/noip.c
@@ -89,6 +89,15 @@ typedef struct aclnode {
   unsigned short minport, maxport;
 } aclnode;
 
+/* A type for an address range */
+typedef struct addrrange {
+  int type;
+  union {
+    struct { int af; ipaddr min, max; } range;
+  } u;
+} addrrange;
+enum { EMPTY, ANY, LOCAL, RANGE };
+
 /* Local address records */
 typedef struct full_ipaddr {
   int af;
@@ -507,22 +516,27 @@ static int parse_sockaddr(struct sockaddr *sa, const char *p)
 
 #ifdef DEBUG
 
-/* Write to standard error a description of the ACL node A. */
-static void dump_aclnode(const aclnode *a)
+static void dump_addrrange(int af, const ipaddr *min, const ipaddr *max)
 {
   char buf[ADDRBUFSZ];
   const char *p;
   int plen;
 
-  fprintf(stderr, "noip(%d):   %c ", getpid(), a->act ? '+' : '-');
-  plen = common_prefix_length(a->af, &a->minaddr, &a->maxaddr);
-  p = inet_ntop(a->af, &a->minaddr, buf, sizeof(buf));
+  plen = common_prefix_length(af, min, max);
+  p = inet_ntop(af, min, buf, sizeof(buf));
   fprintf(stderr, strchr(p, ':') ? "[%s]" : "%s", p);
   if (plen < 0) {
-    p = inet_ntop(a->af, &a->maxaddr, buf, sizeof(buf));
+    p = inet_ntop(af, &max, buf, sizeof(buf));
     fprintf(stderr, strchr(p, ':') ? "-[%s]" : "-%s", p);
-  } else if (plen < address_width(a->af))
+  } else if (plen < address_width(af))
     fprintf(stderr, "/%d", plen);
+}
+
+/* Write to standard error a description of the ACL node A. */
+static void dump_aclnode(const aclnode *a)
+{
+  fprintf(stderr, "noip(%d):   %c ", getpid(), a->act ? '+' : '-');
+  dump_addrrange(a->af, &a->minaddr, &a->maxaddr);
   if (a->minport != 0 || a->maxport != 0xffff) {
     fprintf(stderr, ":%u", (unsigned)a->minport);
     if (a->minport != a->maxport)
@@ -985,92 +999,139 @@ static void parse_ports(char **pp, unsigned short *min, unsigned short *max)
   *pp = p;
 }
 
-/* Make a new ACL node.  ACT is the verdict; AF is the address family;
- * MINADDR and MAXADDR are the ranges on IP addresses; MINPORT and MAXPORT
- * are the ranges on port numbers; TAIL is the list tail to attach the new
- * node to.
- */
-#define ACLNODE(tail_, act_,                                           \
-               af_, minaddr_, maxaddr_, minport_, maxport_) do {       \
-  aclnode *a_;                                                         \
-  NEW(a_);                                                             \
-  a_->act = (act_);                                                    \
-  a_->af = (af_);                                                      \
-  a_->minaddr = (minaddr_); a_->maxaddr = (maxaddr_);                  \
-  a_->minport = (minport_); a_->maxport = (maxport_);                  \
-  *tail_ = a_; tail_ = &a_->next;                                      \
-} while (0)
-
-/* Parse an ACL line.  *PP points to the end of the line; *TAIL points to
- * the list tail (i.e., the final link in the list).  An ACL entry has the
- * form +|- [any | local | ADDR | ADDR - ADDR | ADDR/ADDR | ADDR/INT] PORTS
- * where PORTS is parsed by parse_ports above; an ACL line consists of a
- * comma-separated sequence of entries..
+/* Parse an address range designator starting at PP and store a
+ * representation of it in R.  An address range designator has the form:
+ *
+ *     any | local | ADDR | ADDR - ADDR | ADDR/ADDR | ADDR/INT
  */
-static void parse_acl_line(char **pp, aclnode ***tail)
+static int parse_addrrange(char **pp, addrrange *r)
 {
-  ipaddr minaddr, maxaddr;
-  unsigned short minport, maxport;
-  int i, af, n;
-  int act;
+  char *p = *pp, *q;
+  int n;
   int del;
-  char *p = *pp;
-  char *q;
+  int af;
 
-  for (;;) {
+  SKIPSPC;
+  if (KWMATCHP("any")) r->type = ANY;
+  else if (KWMATCHP("local")) r->type = LOCAL;
+  else {
+    parse_nextaddr(&p, &q, &del);
+    af = guess_address_family(q);
+    if (inet_pton(af, q, &r->u.range.min) <= 0) goto bad;
+    RESCAN(del);
     SKIPSPC;
-    if (*p == '+') act = ALLOW;
-    else if (*p == '-') act = DENY;
-    else goto bad;
+    if (*p == '-') {
+      p++;
+      parse_nextaddr(&p, &q, &del);
+      if (inet_pton(af, q, &r->u.range.max) <= 0) goto bad;
+      RESCAN(del);
+    } else if (*p == '/') {
+      p++;
+      NEXTNUMBER(q, del);
+      n = strtoul(q, 0, 0);
+      r->u.range.max = r->u.range.min;
+      mask_address(af, &r->u.range.min, n, 0);
+      mask_address(af, &r->u.range.max, n, 1);
+      RESCAN(del);
+    } else
+      r->u.range.max = r->u.range.min;
+    r->type = RANGE;
+    r->u.range.af = af;
+  }
+  *pp = p;
+  return (0);
 
-    p++;
-    SKIPSPC;
-    if (KWMATCHP("any")) {
-      parse_ports(&p, &minport, &maxport);
+bad:
+  return (-1);
+}
+
+/* Call FUNC on each individual address range in R. */
+static void foreach_addrrange(const addrrange *r,
+                             void (*func)(int af,
+                                          const ipaddr *min,
+                                          const ipaddr *max,
+                                          void *p),
+                             void *p)
+{
+  ipaddr minaddr, maxaddr;
+  int i, af;
+
+  switch (r->type) {
+    case EMPTY:
+      break;
+    case ANY:
       for (i = 0; address_families[i] >= 0; i++) {
        af = address_families[i];
        memset(&minaddr, 0, sizeof(minaddr));
        maxaddr = minaddr; mask_address(af, &maxaddr, 0, 1);
-       ACLNODE(*tail, act, af, minaddr, maxaddr, minport, maxport);
+       func(af, &minaddr, &maxaddr, p);
       }
-    } else if (KWMATCHP("local")) {
-      parse_ports(&p, &minport, &maxport);
+      break;
+    case LOCAL:
       for (i = 0; address_families[i] >= 0; i++) {
        af = address_families[i];
        memset(&minaddr, 0, sizeof(minaddr));
        maxaddr = minaddr; mask_address(af, &maxaddr, 0, 1);
-       ACLNODE(*tail, act, af, minaddr, minaddr, minport, maxport);
-       ACLNODE(*tail, act, af, maxaddr, maxaddr, minport, maxport);
+       func(af, &minaddr, &minaddr, p);
+       func(af, &maxaddr, &maxaddr, p);
       }
       for (i = 0; i < n_local_ipaddrs; i++) {
-       ACLNODE(*tail, act, local_ipaddrs[i].af,
-               local_ipaddrs[i].addr, local_ipaddrs[i].addr,
-               minport, maxport);
+       func(local_ipaddrs[i].af,
+            &local_ipaddrs[i].addr, &local_ipaddrs[i].addr,
+            p);
       }
-    } else {
-      parse_nextaddr(&p, &q, &del);
-      af = guess_address_family(q);
-      if (inet_pton(af, q, &minaddr) <= 0) goto bad;
-      RESCAN(del);
-      SKIPSPC;
-      if (*p == '-') {
-       p++;
-       parse_nextaddr(&p, &q, &del);
-       if (inet_pton(af, q, &maxaddr) <= 0) goto bad;
-       RESCAN(del);
-      } else if (*p == '/') {
-       p++;
-       NEXTNUMBER(q, del);
-       n = strtoul(q, 0, 0);
-       maxaddr = minaddr;
-       mask_address(af, &minaddr, n, 0);
-       mask_address(af, &maxaddr, n, 1);
-       RESCAN(del);
-      } else
-       maxaddr = minaddr;
-      parse_ports(&p, &minport, &maxport);
-      ACLNODE(*tail, act, af, minaddr, maxaddr, minport, maxport);
-    }
+      break;
+    case RANGE:
+      func(r->u.range.af, &r->u.range.min, &r->u.range.max, p);
+      break;
+    default:
+      abort();
+  }
+}
+
+struct add_aclnode_ctx {
+  int act;
+  unsigned short minport, maxport;
+  aclnode ***tail;
+};
+
+static void add_aclnode(int af, const ipaddr *min, const ipaddr *max,
+                       void *p)
+{
+  struct add_aclnode_ctx *ctx = p;
+  aclnode *a;
+
+  NEW(a);
+  a->act = ctx->act;
+  a->af = af;
+  a->minaddr = *min; a->maxaddr = *max;
+  a->minport = ctx->minport; a->maxport = ctx->maxport;
+  **ctx->tail = a; *ctx->tail = &a->next;
+}
+
+/* Parse an ACL line.  *PP points to the end of the line; *TAIL points to
+ * the list tail (i.e., the final link in the list).  An ACL entry has the
+ * form +|- ADDR-RANGE PORTS
+ * where PORTS is parsed by parse_ports above; an ACL line consists of a
+ * comma-separated sequence of entries..
+ */
+static void parse_acl_line(char **pp, aclnode ***tail)
+{
+  struct add_aclnode_ctx ctx;
+  addrrange r;
+  char *p = *pp;
+
+  ctx.tail = tail;
+  for (;;) {
+    SKIPSPC;
+    if (*p == '+') ctx.act = ALLOW;
+    else if (*p == '-') ctx.act = DENY;
+    else goto bad;
+
+    p++;
+    if (parse_addrrange(&p, &r)) goto bad;
+    parse_ports(&p, &ctx.minport, &ctx.maxport);
+    foreach_addrrange(&r, add_aclnode, &ctx);
     SKIPSPC;
     if (*p != ',') break;
     if (*p) p++;