Previous patch

Home

Next patch

./liblibiptc/libiptc.c

Patch: Additional logging in NTP

+/* Library which manipulates firewall rules.  Version $Revision: 1.4 $ */
+
+/* Architecture of firewall rules is as follows:
+ *
+ * Chains go INPUT, FORWARD, OUTPUT then user chains.
+ * Each user chain starts with an ERROR node.
+ * Every chain ends with an unconditional jump: a RETURN for user chains,
+ * and a POLICY for built-ins.
+ */
+
+/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See
+   COPYING for details). */
+
+#ifndef IPT_LIB_DIR
+#define IPT_LIB_DIR "/usr/local/lib/iptables"
+#endif
+
+#ifndef __OPTIMIZE__
+static STRUCT_ENTRY_TARGET *
+GET_TARGET(STRUCT_ENTRY *e)
+{
+  return (void *)e + e->target_offset;
+}
+#endif
+
+static int sockfd = -1;
+static void *iptc_fn = NULL;
+
+static const char *hooknames[]
+= { [HOOK_PRE_ROUTING]  "PREROUTING",
+    [HOOK_LOCAL_IN]     "INPUT",
+    [HOOK_FORWARD]      "FORWARD",
+    [HOOK_LOCAL_OUT]    "OUTPUT",
+    [HOOK_POST_ROUTING] "POSTROUTING",
+#ifdef HOOK_DROPPING
+    [HOOK_DROPPING]  "DROPPING"
+#endif
+};
+
+struct counter_map
+{
+  enum {
+    COUNTER_MAP_NOMAP,
+    COUNTER_MAP_NORMAL_MAP,
+    COUNTER_MAP_ZEROED,
+    COUNTER_MAP_SET
+  } maptype;
+  unsigned int mappos;
+};
+
+/* Convenience structures */
+struct ipt_error_target
+{
+  STRUCT_ENTRY_TARGET t;
+  char error[TABLE_MAXNAMELEN];
+};
+
+struct chain_cache
+{
+  char name[TABLE_MAXNAMELEN];
+  /* This is the first rule in chain. */
+  STRUCT_ENTRY *start;
+  /* Last rule in chain */
+  STRUCT_ENTRY *end;
+};
+
+STRUCT_TC_HANDLE
+{
+  /* Have changes been made? */
+  int changed;
+  /* Size in here reflects original state. */
+  STRUCT_GETINFO info;
+
+  struct counter_map *counter_map;
+  /* Array of hook names */
+  const char **hooknames;
+
+  /* Cached position of chain heads (NULL = no cache). */
+  unsigned int cache_num_chains;
+  unsigned int cache_num_builtins;
+  struct chain_cache *cache_chain_heads;
+
+  /* Chain iterator: current chain cache entry. */
+  struct chain_cache *cache_chain_iteration;
+
+  /* Rule iterator: terminal rule */
+  STRUCT_ENTRY *cache_rule_end;
+
+  /* Number in here reflects current state. */
+  unsigned int new_number;
+  STRUCT_GET_ENTRIES entries;
+};
+
+static void
+set_changed(TC_HANDLE_T h)
+{
+  if (h->cache_chain_heads) {
+    free(h->cache_chain_heads);
+    h->cache_chain_heads = NULL;
+    h->cache_num_chains = 0;
+    h->cache_chain_iteration = NULL;
+    h->cache_rule_end = NULL;
+  }
+  h->changed = 1;
+}
+
+#ifndef NDEBUG
+static void do_check(TC_HANDLE_T h, unsigned int line);
+#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0)
+#else
+#define CHECK(h)
+#endif
+
+static inline int
+get_number(const STRUCT_ENTRY *i,
+     const STRUCT_ENTRY *seek,
+     unsigned int *pos)
+{
+  if (i == seek)
+    return 1;
+  (*pos)++;
+  return 0;
+}
+
+static unsigned int
+entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek)
+{
+  unsigned int pos = 0;
+
+  if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size,
+        get_number, seek, &pos) == 0) {
+    fprintf(stderr, "ERROR: offset %i not an entry!\n",
+      (char *)seek - (char *)h->entries.entrytable);
+    abort();
+  }
+  return pos;
+}
+
+static inline int
+get_entry_n(STRUCT_ENTRY *i,
+      unsigned int number,
+      unsigned int *pos,
+      STRUCT_ENTRY **pe)
+{
+  if (*pos == number) {
+    *pe = i;
+    return 1;
+  }
+  (*pos)++;
+  return 0;
+}
+
+static STRUCT_ENTRY *
+index2entry(TC_HANDLE_T h, unsigned int index)
+{
+  unsigned int pos = 0;
+  STRUCT_ENTRY *ret = NULL;
+
+  ENTRY_ITERATE(h->entries.entrytable, h->entries.size,
+          get_entry_n, index, &pos, &ret);
+
+  return ret;
+}
+
+static inline STRUCT_ENTRY *
+get_entry(TC_HANDLE_T h, unsigned int offset)
+{
+  return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset);
+}
+
+static inline unsigned long
+entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e)
+{
+  return (char *)e - (char *)h->entries.entrytable;
+}
+
+static unsigned long
+index2offset(TC_HANDLE_T h, unsigned int index)
+{
+  return entry2offset(h, index2entry(h, index));
+}
+
+static const char *
+get_errorlabel(TC_HANDLE_T h, unsigned int offset)
+{
+  STRUCT_ENTRY *e;
+
+  e = get_entry(h, offset);
+  if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) {
+    fprintf(stderr, "ERROR: offset %u not an error node!\n",
+      offset);
+    abort();
+  }
+
+  return (const char *)GET_TARGET(e)->data;
+}
+
+/* Allocate handle of given size */
+static TC_HANDLE_T
+alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)
+{
+  size_t len;
+  TC_HANDLE_T h;
+
+  len = sizeof(STRUCT_TC_HANDLE)
+    + size
+    + num_rules * sizeof(struct counter_map);
+
+  if ((h = malloc(len)) == NULL) {
+    errno = ENOMEM;
+    return NULL;
+  }
+
+  h->changed = 0;
+  h->cache_num_chains = 0;
+  h->cache_chain_heads = NULL;
+  h->counter_map = (void *)h
+    + sizeof(STRUCT_TC_HANDLE)
+    + size;
+  strcpy(h->info.name, tablename);
+  strcpy(h->entries.name, tablename);
+
+  return h;
+}
+
+TC_HANDLE_T
+TC_INIT(const char *tablename)
+{
+  TC_HANDLE_T h;
+  STRUCT_GETINFO info;
+  unsigned int i;
+  int tmp;
+  socklen_t s;
+
+  iptc_fn = TC_INIT;
+
+  if(sockfd != -1)
+    close(sockfd);
+  sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
+  if (sockfd < 0)
+    return NULL;
+
+  s = sizeof(info);
+  if (strlen(tablename) >= TABLE_MAXNAMELEN) {
+    errno = EINVAL;
+    return NULL;
+  }
+  strcpy(info.name, tablename);
+  if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0)
+    return NULL;
+
+  if ((h = alloc_handle(info.name, info.size, info.num_entries))
+      == NULL)
+    return NULL;
+
+/* Too hard --RR */
+#if 0
+  sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name);
+  dynlib = dlopen(pathname, RTLD_NOW);
+  if (!dynlib) {
+    errno = ENOENT;
+    return NULL;
+  }
+  h->hooknames = dlsym(dynlib, "hooknames");
+  if (!h->hooknames) {
+    errno = ENOENT;
+    return NULL;
+  }
+#else
+  h->hooknames = hooknames;
+#endif
+
+  /* Initialize current state */
+  h->info = info;
+  h->new_number = h->info.num_entries;
+  for (i = 0; i < h->info.num_entries; i++)
+    h->counter_map[i]
+      = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i});
+
+  h->entries.size = h->info.size;
+
+  tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;
+
+  if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries,
+           &tmp) < 0) {
+    free(h);
+    return NULL;
+  }
+
+  CHECK(h);
+  return h;
+}
+
+static inline int
+print_match(const STRUCT_ENTRY_MATCH *m)
+{
+  printf("Match name: `%s'\n", m->u.user.name);
+  return 0;
+}
+
+static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle);
+ 
+void
+TC_DUMP_ENTRIES(const TC_HANDLE_T handle)
+{
+  CHECK(handle);
+
+  printf("libiptc v%s.  %u entries, %u bytes.\n",
+         NETFILTER_VERSION,
+         handle->new_number, handle->entries.size);
+  printf("Table `%s'\n", handle->info.name);
+  printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n",
+         handle->info.hook_entry[HOOK_PRE_ROUTING],
+         handle->info.hook_entry[HOOK_LOCAL_IN],
+         handle->info.hook_entry[HOOK_FORWARD],
+         handle->info.hook_entry[HOOK_LOCAL_OUT],
+         handle->info.hook_entry[HOOK_POST_ROUTING]);
+  printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n",
+         handle->info.underflow[HOOK_PRE_ROUTING],
+         handle->info.underflow[HOOK_LOCAL_IN],
+         handle->info.underflow[HOOK_FORWARD],
+         handle->info.underflow[HOOK_LOCAL_OUT],
+         handle->info.underflow[HOOK_POST_ROUTING]);
+
+  ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size,
+          dump_entry, handle);
+}
+
+/* Returns 0 if not hook entry, else hooknumber + 1 */
+static inline unsigned int
+is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h)
+{
+  unsigned int i;
+
+  for (i = 0; i < NUMHOOKS; i++) {
+    if ((h->info.valid_hooks & (1 << i))
+        && get_entry(h, h->info.hook_entry[i]) == e)
+      return i+1;
+  }
+  return 0;
+}
+
+static inline int
+add_chain(STRUCT_ENTRY *e, TC_HANDLE_T h, STRUCT_ENTRY **prev)
+{
+  unsigned int builtin;
+
+  /* Last entry.  End it. */
+  if (entry2offset(h, e) + e->next_offset == h->entries.size) {
+    /* This is the ERROR node at end of the table */
+    h->cache_chain_heads[h->cache_num_chains-1].end = *prev;
+    return 0;
+  }
+
+  /* We know this is the start of a new chain if it's an ERROR
+     target, or a hook entry point */
+  if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) {
+    /* prev was last entry in previous chain */
+    h->cache_chain_heads[h->cache_num_chains-1].end
+      = *prev;
+
+    strcpy(h->cache_chain_heads[h->cache_num_chains].name,
+           (const char *)GET_TARGET(e)->data);
+    h->cache_chain_heads[h->cache_num_chains].start
+      = (void *)e + e->next_offset;
+    h->cache_num_chains++;
+  } else if ((builtin = is_hook_entry(e, h)) != 0) {
+    if (h->cache_num_chains > 0)
+      /* prev was last entry in previous chain */
+      h->cache_chain_heads[h->cache_num_chains-1].end
+        = *prev;
+
+    strcpy(h->cache_chain_heads[h->cache_num_chains].name,
+           h->hooknames[builtin-1]);
+    h->cache_chain_heads[h->cache_num_chains].start
+      = (void *)e;
+    h->cache_num_chains++;
+  }
+
+  *prev = e;
+  return 0;
+}
+
+static int alphasort(const void *a, const void *b)
+{
+  return strcmp(((struct chain_cache *)a)->name,
+          ((struct chain_cache *)b)->name);
+}
+
+static int populate_cache(TC_HANDLE_T h)
+{
+  unsigned int i;
+  STRUCT_ENTRY *prev;
+
+  /* # chains < # rules / 2 + num builtins - 1 */
+  h->cache_chain_heads = malloc((h->new_number / 2 + 4)
+              * sizeof(struct chain_cache));
+  if (!h->cache_chain_heads) {
+    errno = ENOMEM;
+    return 0;
+  }
+
+  h->cache_num_chains = 0;
+  h->cache_num_builtins = 0;
+
+  /* Count builtins */
+  for (i = 0; i < NUMHOOKS; i++) {
+    if (h->info.valid_hooks & (1 << i))
+      h->cache_num_builtins++;
+  }
+
+  prev = NULL;
+  ENTRY_ITERATE(h->entries.entrytable, h->entries.size,
+          add_chain, h, &prev);
+
+  qsort(h->cache_chain_heads + h->cache_num_builtins,
+        h->cache_num_chains - h->cache_num_builtins,
+        sizeof(struct chain_cache), alphasort);
+
+  return 1;
+}
+
+/* Returns cache ptr if found, otherwise NULL. */
+static struct chain_cache *
+find_label(const char *name, TC_HANDLE_T handle)
+{
+  unsigned int i;
+
+  if (handle->cache_chain_heads == NULL
+      && !populate_cache(handle))
+    return NULL;
+
+  /* FIXME: Linear search through builtins, then binary --RR */
+  for (i = 0; i < handle->cache_num_chains; i++) {
+    if (strcmp(handle->cache_chain_heads[i].name, name) == 0)
+      return &handle->cache_chain_heads[i];
+  }
+
+  return NULL;
+}
+
+/* Does this chain exist? */
+int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle)
+{
+  return find_label(chain, handle) != NULL;
+}
+
+/* Returns the position of the final (ie. unconditional) element. */
+static unsigned int
+get_chain_end(const TC_HANDLE_T handle, unsigned int start)
+{
+  unsigned int last_off, off;
+  STRUCT_ENTRY *e;
+
+  last_off = start;
+  e = get_entry(handle, start);
+
+  /* Terminate when we meet a error label or a hook entry. */
+  for (off = start + e->next_offset;
+       off < handle->entries.size;
+       last_off = off, off += e->next_offset) {
+    STRUCT_ENTRY_TARGET *t;
+    unsigned int i;
+
+    e = get_entry(handle, off);
+
+    /* We hit an entry point. */
+    for (i = 0; i < NUMHOOKS; i++) {
+      if ((handle->info.valid_hooks & (1 << i))
+          && off == handle->info.hook_entry[i])
+        return last_off;
+    }
+
+    /* We hit a user chain label */
+    t = GET_TARGET(e);
+    if (strcmp(t->u.user.name, ERROR_TARGET) == 0)
+      return last_off;
+  }
+  /* SHOULD NEVER HAPPEN */
+  fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n",
+    handle->entries.size, off);
+  abort();
+}
+
+/* Iterator functions to run through the chains. */
+const char *
+TC_FIRST_CHAIN(TC_HANDLE_T *handle)
+{
+  if ((*handle)->cache_chain_heads == NULL
+      && !populate_cache(*handle))
+    return NULL;
+
+  (*handle)->cache_chain_iteration
+    = &(*handle)->cache_chain_heads[0];
+
+  return (*handle)->cache_chain_iteration->name;
+}
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *
+TC_NEXT_CHAIN(TC_HANDLE_T *handle)
+{
+  (*handle)->cache_chain_iteration++;
+
+  if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads
+      == (*handle)->cache_num_chains)
+    return NULL;
+
+  return (*handle)->cache_chain_iteration->name;
+}
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const STRUCT_ENTRY *
+TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle)
+{
+  struct chain_cache *c;
+
+  c = find_label(chain, *handle);
+  if (!c) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  /* Empty chain: single return/policy rule */
+  if (c->start == c->end)
+    return NULL;
+
+  (*handle)->cache_rule_end = c->end;
+  return c->start;
+}
+
+/* Returns NULL when rules run out. */
+const STRUCT_ENTRY *
+TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)
+{
+  if ((void *)prev + prev->next_offset
+      == (void *)(*handle)->cache_rule_end)
+    return NULL;
+
+  return (void *)prev + prev->next_offset;
+}
+
+#if 0
+/* How many rules in this chain? */
+unsigned int
+TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle)
+{
+  unsigned int off = 0;
+  STRUCT_ENTRY *start, *end;
+
+  CHECK(*handle);
+  if (!find_label(&off, chain, *handle)) {
+    errno = ENOENT;
+    return (unsigned int)-1;
+  }
+
+  start = get_entry(*handle, off);
+  end = get_entry(*handle, get_chain_end(*handle, off));
+
+  return entry2index(*handle, end) - entry2index(*handle, start);
+}
+
+/* Get n'th rule in this chain. */
+const STRUCT_ENTRY *TC_GET_RULE(const char *chain,
+        unsigned int n,
+        TC_HANDLE_T *handle)
+{
+  unsigned int pos = 0, chainindex;
+
+  CHECK(*handle);
+  if (!find_label(&pos, chain, *handle)) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  chainindex = entry2index(*handle, get_entry(*handle, pos));
+
+  return index2entry(*handle, chainindex + n);
+}
+#endif
+
+static const char *
+target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce)
+{
+  int spos;
+  unsigned int labelidx;
+  STRUCT_ENTRY *jumpto;
+
+  /* To avoid const warnings */
+  STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce;
+
+  if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0)
+    return GET_TARGET(e)->u.user.name;
+
+  /* Standard target: evaluate */
+  spos = *(int *)GET_TARGET(e)->data;
+  if (spos < 0) {
+    if (spos == RETURN)
+      return LABEL_RETURN;
+    else if (spos == -NF_ACCEPT-1)
+      return LABEL_ACCEPT;
+    else if (spos == -NF_DROP-1)
+      return LABEL_DROP;
+    else if (spos == -NF_QUEUE-1)
+      return LABEL_QUEUE;
+
+    fprintf(stderr, "ERROR: off %lu/%u not a valid target (%i)\n",
+      entry2offset(handle, e), handle->entries.size,
+      spos);
+    abort();
+  }
+
+  jumpto = get_entry(handle, spos);
+
+  /* Fall through rule */
+  if (jumpto == (void *)e + e->next_offset)
+    return "";
+
+  /* Must point to head of a chain: ie. after error rule */
+  labelidx = entry2index(handle, jumpto) - 1;
+  return get_errorlabel(handle, index2offset(handle, labelidx));
+}
+
+/* Returns a pointer to the target name of this position. */
+const char *TC_GET_TARGET(const STRUCT_ENTRY *e,
+        TC_HANDLE_T *handle)
+{
+  return target_name(*handle, e);
+}
+
+/* Is this a built-in chain?  Actually returns hook + 1. */
+int
+TC_BUILTIN(const char *chain, const TC_HANDLE_T handle)
+{
+  unsigned int i;
+
+  for (i = 0; i < NUMHOOKS; i++) {
+    if ((handle->info.valid_hooks & (1 << i))
+        && handle->hooknames[i]
+        && strcmp(handle->hooknames[i], chain) == 0)
+      return i+1;
+  }
+  return 0;
+}
+
+/* Get the policy of a given built-in chain */
+const char *
+TC_GET_POLICY(const char *chain,
+        STRUCT_COUNTERS *counters,
+        TC_HANDLE_T *handle)
+{
+  unsigned int start;
+  STRUCT_ENTRY *e;
+  int hook;
+
+  hook = TC_BUILTIN(chain, *handle);
+  if (hook != 0)
+    start = (*handle)->info.hook_entry[hook-1];
+  else
+    return NULL;
+
+  e = get_entry(*handle, get_chain_end(*handle, start));
+  *counters = e->counters;
+
+  return target_name(*handle, e);
+}
+
+static int
+correct_verdict(STRUCT_ENTRY *e,
+    char *base,
+    unsigned int offset, int delta_offset)
+{
+  STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e);
+  unsigned int curr = (char *)e - base;
+
+  /* Trap: insert of fall-through rule.  Don't change fall-through
+     verdict to jump-over-next-rule. */
+  if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0
+      && t->verdict > (int)offset
+      && !(curr == offset &&
+     t->verdict == curr + e->next_offset)) {
+    t->verdict += delta_offset;
+  }
+
+  return 0;
+}
+
+/* Adjusts standard verdict jump positions after an insertion/deletion. */
+static int
+set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle)
+{
+  ENTRY_ITERATE((*handle)->entries.entrytable,
+          (*handle)->entries.size,
+          correct_verdict, (char *)(*handle)->entries.entrytable,
+          offset, delta_offset);
+
+  set_changed(*handle);
+  return 1;
+}
+
+/* If prepend is set, then we are prepending to a chain: if the
+ * insertion position is an entry point, keep the entry point. */
+static int
+insert_rules(unsigned int num_rules, unsigned int rules_size,
+       const STRUCT_ENTRY *insert,
+       unsigned int offset, unsigned int num_rules_offset,
+       int prepend,
+       TC_HANDLE_T *handle)
+{
+  TC_HANDLE_T newh;
+  STRUCT_GETINFO newinfo;
+  unsigned int i;
+
+  if (offset >= (*handle)->entries.size) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  newinfo = (*handle)->info;
+
+  /* Fix up entry points. */
+  for (i = 0; i < NUMHOOKS; i++) {
+    /* Entry points to START of chain, so keep same if
+                   inserting on at that point. */
+    if ((*handle)->info.hook_entry[i] > offset)
+      newinfo.hook_entry[i] += rules_size;
+
+    /* Underflow always points to END of chain (policy),
+       so if something is inserted at same point, it
+       should be advanced. */
+    if ((*handle)->info.underflow[i] >= offset)
+      newinfo.underflow[i] += rules_size;
+  }
+
+  newh = alloc_handle((*handle)->info.name,
+          (*handle)->entries.size + rules_size,
+          (*handle)->new_number + num_rules);
+  if (!newh)
+    return 0;
+  newh->info = newinfo;
+
+  /* Copy pre... */
+  memcpy(newh->entries.entrytable, (*handle)->entries.entrytable,offset);
+  /* ... Insert new ... */
+  memcpy((char *)newh->entries.entrytable + offset, insert, rules_size);
+  /* ... copy post */
+  memcpy((char *)newh->entries.entrytable + offset + rules_size,
+         (char *)(*handle)->entries.entrytable + offset,
+         (*handle)->entries.size - offset);
+
+  /* Move counter map. */
+  /* Copy pre... */
+  memcpy(newh->counter_map, (*handle)->counter_map,
+         sizeof(struct counter_map) * num_rules_offset);
+  /* ... copy post */
+  memcpy(newh->counter_map + num_rules_offset + num_rules,
+         (*handle)->counter_map + num_rules_offset,
+         sizeof(struct counter_map) * ((*handle)->new_number
+               - num_rules_offset));
+  /* Set intermediates to no counter copy */
+  for (i = 0; i < num_rules; i++)
+    newh->counter_map[num_rules_offset+i]
+      = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 });
+
+  newh->new_number = (*handle)->new_number + num_rules;
+  newh->entries.size = (*handle)->entries.size + rules_size;
+  newh->hooknames = (*handle)->hooknames;
+
+  if ((*handle)->cache_chain_heads)
+    free((*handle)->cache_chain_heads);
+  free(*handle);
+  *handle = newh;
+
+  return set_verdict(offset, rules_size, handle);
+}
+
+static int
+delete_rules(unsigned int num_rules, unsigned int rules_size,
+       unsigned int offset, unsigned int num_rules_offset,
+       TC_HANDLE_T *handle)
+{
+  unsigned int i;
+
+  if (offset + rules_size > (*handle)->entries.size) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  /* Fix up entry points. */
+  for (i = 0; i < NUMHOOKS; i++) {
+    /* In practice, we never delete up to a hook entry,
+       since the built-in chains are always first,
+       so these two are never equal */
+    if ((*handle)->info.hook_entry[i] >= offset + rules_size)
+      (*handle)->info.hook_entry[i] -= rules_size;
+    else if ((*handle)->info.hook_entry[i] > offset) {
+      fprintf(stderr, "ERROR: Deleting entry %u %u %u\n",
+        i, (*handle)->info.hook_entry[i], offset);
+      abort();
+    }
+
+    /* Underflow points to policy (terminal) rule in
+                   built-in, so sequality is valid here (when deleting
+                   the last rule). */
+    if ((*handle)->info.underflow[i] >= offset + rules_size)
+      (*handle)->info.underflow[i] -= rules_size;
+    else if ((*handle)->info.underflow[i] > offset) {
+      fprintf(stderr, "ERROR: Deleting uflow %u %u %u\n",
+        i, (*handle)->info.underflow[i], offset);
+      abort();
+    }
+  }
+
+  /* Move the rules down. */
+  memmove((char *)(*handle)->entries.entrytable + offset,
+    (char *)(*handle)->entries.entrytable + offset + rules_size,
+    (*handle)->entries.size - (offset + rules_size));
+
+  /* Move the counter map down. */
+  memmove(&(*handle)->counter_map[num_rules_offset],
+    &(*handle)->counter_map[num_rules_offset + num_rules],
+    sizeof(struct counter_map)
+    * ((*handle)->new_number - (num_rules + num_rules_offset)));
+
+  /* Fix numbers */
+  (*handle)->new_number -= num_rules;
+  (*handle)->entries.size -= rules_size;
+
+  return set_verdict(offset, -(int)rules_size, handle);
+}
+
+static int
+standard_map(STRUCT_ENTRY *e, int verdict)
+{
+  STRUCT_STANDARD_TARGET *t;
+
+  t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+
+  if (t->target.u.target_size
+      != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) {
+    errno = EINVAL;
+    return 0;
+  }
+  /* memset for memcmp convenience on delete/replace */
+  memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN);
+  strcpy(t->target.u.user.name, STANDARD_TARGET);
+  t->verdict = verdict;
+
+  return 1;
+}
+
+static int
+map_target(const TC_HANDLE_T handle,
+     STRUCT_ENTRY *e,
+     unsigned int offset,
+     STRUCT_ENTRY_TARGET *old)
+{
+  STRUCT_ENTRY_TARGET *t = GET_TARGET(e);
+
+  /* Save old target (except data, which we don't change, except for
+     standard case, where we don't care). */
+  *old = *t;
+
+  /* Maybe it's empty (=> fall through) */
+  if (strcmp(t->u.user.name, "") == 0)
+    return standard_map(e, offset + e->next_offset);
+  /* Maybe it's a standard target name... */
+  else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0)
+    return standard_map(e, -NF_ACCEPT - 1);
+  else if (strcmp(t->u.user.name, LABEL_DROP) == 0)
+    return standard_map(e, -NF_DROP - 1);
+  else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0)
+    return standard_map(e, -NF_QUEUE - 1);
+  else if (strcmp(t->u.user.name, LABEL_RETURN) == 0)
+    return standard_map(e, RETURN);
+  else if (TC_BUILTIN(t->u.user.name, handle)) {
+    /* Can't jump to builtins. */
+    errno = EINVAL;
+    return 0;
+  } else {
+    /* Maybe it's an existing chain name. */
+    struct chain_cache *c;
+
+    c = find_label(t->u.user.name, handle);
+    if (c)
+      return standard_map(e, entry2offset(handle, c->start));
+  }
+
+  /* Must be a module?  If not, kernel will reject... */
+  /* memset to all 0 for your memcmp convenience. */
+  memset(t->u.user.name + strlen(t->u.user.name),
+         0,
+         FUNCTION_MAXNAMELEN - strlen(t->u.user.name));
+  return 1;
+}
+
+static void
+unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old)
+{
+  STRUCT_ENTRY_TARGET *t = GET_TARGET(e);
+
+  /* Save old target (except data, which we don't change, except for
+     standard case, where we don't care). */
+  *t = *old;
+}
+
+/* Insert the entry `fw' in chain `chain' into position `rulenum'. */
+int
+TC_INSERT_ENTRY(const IPT_CHAINLABEL chain,
+    const STRUCT_ENTRY *e,
+    unsigned int rulenum,
+    TC_HANDLE_T *handle)
+{
+  unsigned int chainindex, offset;
+  STRUCT_ENTRY_TARGET old;
+  struct chain_cache *c;
+  STRUCT_ENTRY *tmp;
+  int ret;
+
+  iptc_fn = TC_INSERT_ENTRY;
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  chainindex = entry2index(*handle, c->start);
+
+  tmp = index2entry(*handle, chainindex + rulenum);
+  if (!tmp || tmp > c->end) {
+    errno = E2BIG;
+    return 0;
+  }
+  offset = index2offset(*handle, chainindex + rulenum);
+
+  /* Mapping target actually alters entry, but that's
+           transparent to the caller. */
+  if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old))
+    return 0;
+
+  ret = insert_rules(1, e->next_offset, e, offset,
+         chainindex + rulenum, rulenum == 0, handle);
+  unmap_target((STRUCT_ENTRY *)e, &old);
+  return ret;
+}
+
+/* Atomically replace rule `rulenum' in `chain' with `fw'. */
+int
+TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain,
+     const STRUCT_ENTRY *e,
+     unsigned int rulenum,
+     TC_HANDLE_T *handle)
+{
+  unsigned int chainindex, offset;
+  STRUCT_ENTRY_TARGET old;
+  struct chain_cache *c;
+  STRUCT_ENTRY *tmp;
+  int ret;
+
+  iptc_fn = TC_REPLACE_ENTRY;
+
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  chainindex = entry2index(*handle, c->start);
+
+  tmp = index2entry(*handle, chainindex + rulenum);
+  if (!tmp || tmp >= c->end) {
+    errno = E2BIG;
+    return 0;
+  }
+
+  offset = index2offset(*handle, chainindex + rulenum);
+  /* Replace = delete and insert. */
+  if (!delete_rules(1, get_entry(*handle, offset)->next_offset,
+        offset, chainindex + rulenum, handle))
+    return 0;
+
+  if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old))
+    return 0;
+
+  ret = insert_rules(1, e->next_offset, e, offset,
+         chainindex + rulenum, 1, handle);
+  unmap_target((STRUCT_ENTRY *)e, &old);
+  return ret;
+}
+
+/* Append entry `fw' to chain `chain'.  Equivalent to insert with
+   rulenum = length of chain. */
+int
+TC_APPEND_ENTRY(const IPT_CHAINLABEL chain,
+    const STRUCT_ENTRY *e,
+    TC_HANDLE_T *handle)
+{
+  struct chain_cache *c;
+  STRUCT_ENTRY_TARGET old;
+  int ret;
+
+  iptc_fn = TC_APPEND_ENTRY;
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  if (!map_target(*handle, (STRUCT_ENTRY *)e,
+      entry2offset(*handle, c->end), &old))
+    return 0;
+
+  ret = insert_rules(1, e->next_offset, e,
+         entry2offset(*handle, c->end),
+         entry2index(*handle, c->end),
+         0, handle);
+  unmap_target((STRUCT_ENTRY *)e, &old);
+  return ret;
+}
+
+static inline int
+match_different(const STRUCT_ENTRY_MATCH *a,
+    const unsigned char *a_elems,
+    const unsigned char *b_elems,
+    unsigned char **maskptr)
+{
+  const STRUCT_ENTRY_MATCH *b;
+  unsigned int i;
+
+  /* Offset of b is the same as a. */
+  b = (void *)b_elems + ((unsigned char *)a - a_elems);
+
+  if (a->u.match_size != b->u.match_size)
+    return 1;
+
+  if (strcmp(a->u.user.name, b->u.user.name) != 0)
+    return 1;
+
+  *maskptr += ALIGN(sizeof(*a));
+
+  for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++)
+    if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0)
+      return 1;
+  *maskptr += i;
+  return 0;
+}
+
+static inline int
+target_different(const unsigned char *a_targdata,
+     const unsigned char *b_targdata,
+     unsigned int tdatasize,
+     const unsigned char *mask)
+{
+  unsigned int i;
+  for (i = 0; i < tdatasize; i++)
+    if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0)
+      return 1;
+
+  return 0;
+}
+
+static int
+is_same(const STRUCT_ENTRY *a,
+  const STRUCT_ENTRY *b,
+  unsigned char *matchmask);
+
+/* Delete the first rule in `chain' which matches `fw'. */
+int
+TC_DELETE_ENTRY(const IPT_CHAINLABEL chain,
+    const STRUCT_ENTRY *origfw,
+    unsigned char *matchmask,
+    TC_HANDLE_T *handle)
+{
+  unsigned int offset;
+  struct chain_cache *c;
+  STRUCT_ENTRY *e, *fw;
+
+  iptc_fn = TC_DELETE_ENTRY;
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  fw = malloc(origfw->next_offset);
+  if (fw == NULL) {
+    errno = ENOMEM;
+    return 0;
+  }
+
+  for (offset = entry2offset(*handle, c->start);
+       offset < entry2offset(*handle, c->end);
+       offset += e->next_offset) {
+    STRUCT_ENTRY_TARGET discard;
+
+    memcpy(fw, origfw, origfw->next_offset);
+
+    /* FIXME: handle this in is_same --RR */
+    if (!map_target(*handle, fw, offset, &discard)) {
+      free(fw);
+      return 0;
+    }
+    e = get_entry(*handle, offset);
+
+#if 0
+    printf("Deleting:\n");
+    dump_entry(newe);
+#endif
+    if (is_same(e, fw, matchmask)) {
+      int ret;
+      ret = delete_rules(1, e->next_offset,
+             offset, entry2index(*handle, e),
+             handle);
+      free(fw);
+      return ret;
+    }
+  }
+
+  free(fw);
+  errno = ENOENT;
+  return 0;
+}
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int
+TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain,
+        unsigned int rulenum,
+        TC_HANDLE_T *handle)
+{
+  unsigned int index;
+  int ret;
+  STRUCT_ENTRY *e;
+  struct chain_cache *c;
+
+  iptc_fn = TC_DELETE_NUM_ENTRY;
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  index = entry2index(*handle, c->start) + rulenum;
+
+  if (index >= entry2index(*handle, c->end)) {
+    errno = E2BIG;
+    return 0;
+  }
+
+  e = index2entry(*handle, index);
+  if (e == NULL) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  ret = delete_rules(1, e->next_offset, entry2offset(*handle, e),
+         index, handle);
+  return ret;
+}
+
+/* Check the packet `fw' on chain `chain'.  Returns the verdict, or
+   NULL and sets errno. */
+const char *
+TC_CHECK_PACKET(const IPT_CHAINLABEL chain,
+    STRUCT_ENTRY *entry,
+    TC_HANDLE_T *handle)
+{
+  errno = ENOSYS;
+  return NULL;
+}
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int
+TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+  unsigned int startindex, endindex;
+  struct chain_cache *c;
+  int ret;
+
+  iptc_fn = TC_FLUSH_ENTRIES;
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+  startindex = entry2index(*handle, c->start);
+  endindex = entry2index(*handle, c->end);
+
+  ret = delete_rules(endindex - startindex,
+         (char *)c->end - (char *)c->start,
+         entry2offset(*handle, c->start), startindex,
+         handle);
+  return ret;
+}
+
+/* Zeroes the counters in a chain. */
+int
+TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+  unsigned int i, end;
+  struct chain_cache *c;
+
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  i = entry2index(*handle, c->start);
+  end = entry2index(*handle, c->end);
+
+  for (; i <= end; i++) {
+    if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP)
+      (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED;
+  }
+  set_changed(*handle);
+
+  return 1;
+}
+
+STRUCT_COUNTERS *
+TC_READ_COUNTER(const IPT_CHAINLABEL chain,
+    unsigned int rulenum,
+    TC_HANDLE_T *handle)
+{
+  STRUCT_ENTRY *e;
+  struct chain_cache *c;
+  unsigned int chainindex, end;
+
+  iptc_fn = TC_READ_COUNTER;
+  CHECK(*handle);
+
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  chainindex = entry2index(*handle, c->start);
+  end = entry2index(*handle, c->end);
+
+  if (chainindex + rulenum > end) {
+    errno = E2BIG;
+    return NULL;
+  }
+
+  e = index2entry(*handle, chainindex + rulenum);
+
+  return &e->counters;
+}
+
+int
+TC_ZERO_COUNTER(const IPT_CHAINLABEL chain,
+    unsigned int rulenum,
+    TC_HANDLE_T *handle)
+{
+  STRUCT_ENTRY *e;
+  struct chain_cache *c;
+  unsigned int chainindex, end;
+  
+  iptc_fn = TC_ZERO_COUNTER;
+  CHECK(*handle);
+
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  chainindex = entry2index(*handle, c->start);
+  end = entry2index(*handle, c->end);
+
+  if (chainindex + rulenum > end) {
+    errno = E2BIG;
+    return 0;
+  }
+
+  e = index2entry(*handle, chainindex + rulenum);
+
+  if ((*handle)->counter_map[chainindex + rulenum].maptype
+      == COUNTER_MAP_NORMAL_MAP) {
+    (*handle)->counter_map[chainindex + rulenum].maptype
+       = COUNTER_MAP_ZEROED;
+  }
+
+  set_changed(*handle);
+
+  return 1;
+}
+
+int 
+TC_SET_COUNTER(const IPT_CHAINLABEL chain,
+         unsigned int rulenum,
+         STRUCT_COUNTERS *counters,
+         TC_HANDLE_T *handle)
+{
+  STRUCT_ENTRY *e;
+  struct chain_cache *c;
+  unsigned int chainindex, end;
+
+  iptc_fn = TC_SET_COUNTER;
+  CHECK(*handle);
+
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  chainindex = entry2index(*handle, c->start);
+  end = entry2index(*handle, c->end);
+
+  if (chainindex + rulenum > end) {
+    errno = E2BIG;
+    return 0;
+  }
+
+  e = index2entry(*handle, chainindex + rulenum);
+
+  (*handle)->counter_map[chainindex + rulenum].maptype
+    = COUNTER_MAP_SET;
+
+  memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS));
+
+  set_changed(*handle);
+
+  return 1;
+}
+
+/* Creates a new chain. */
+/* To create a chain, create two rules: error node and unconditional
+ * return. */
+int
+TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+  int ret;
+  struct {
+    STRUCT_ENTRY head;
+    struct ipt_error_target name;
+    STRUCT_ENTRY ret;
+    STRUCT_STANDARD_TARGET target;
+  } newc;
+
+  iptc_fn = TC_CREATE_CHAIN;
+
+  /* find_label doesn't cover built-in targets: DROP, ACCEPT,
+           QUEUE, RETURN. */
+  if (find_label(chain, *handle)
+      || strcmp(chain, LABEL_DROP) == 0
+      || strcmp(chain, LABEL_ACCEPT) == 0
+      || strcmp(chain, LABEL_QUEUE) == 0
+      || strcmp(chain, LABEL_RETURN) == 0) {
+    errno = EEXIST;
+    return 0;
+  }
+
+  if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  memset(&newc, 0, sizeof(newc));
+  newc.head.target_offset = sizeof(STRUCT_ENTRY);
+  newc.head.next_offset
+    = sizeof(STRUCT_ENTRY)
+    + ALIGN(sizeof(struct ipt_error_target));
+  strcpy(newc.name.t.u.user.name, ERROR_TARGET);
+  newc.name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target));
+  strcpy(newc.name.error, chain);
+
+  newc.ret.target_offset = sizeof(STRUCT_ENTRY);
+  newc.ret.next_offset
+    = sizeof(STRUCT_ENTRY)
+    + ALIGN(sizeof(STRUCT_STANDARD_TARGET));
+  strcpy(newc.target.target.u.user.name, STANDARD_TARGET);
+  newc.target.target.u.target_size
+    = ALIGN(sizeof(STRUCT_STANDARD_TARGET));
+  newc.target.verdict = RETURN;
+
+  /* Add just before terminal entry */
+  ret = insert_rules(2, sizeof(newc), &newc.head,
+         index2offset(*handle, (*handle)->new_number - 1),
+         (*handle)->new_number - 1,
+         0, handle);
+  return ret;
+}
+
+static int
+count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref)
+{
+  STRUCT_STANDARD_TARGET *t;
+
+  if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) {
+    t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+
+    if (t->verdict == offset)
+      (*ref)++;
+  }
+
+  return 0;
+}
+
+/* Get the number of references to this chain. */
+int
+TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain,
+      TC_HANDLE_T *handle)
+{
+  struct chain_cache *c;
+
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  *ref = 0;
+  ENTRY_ITERATE((*handle)->entries.entrytable,
+          (*handle)->entries.size,
+          count_ref, entry2offset(*handle, c->start), ref);
+  return 1;
+}
+
+/* Deletes a chain. */
+int
+TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+  unsigned int labelidx, labeloff;
+  unsigned int references;
+  struct chain_cache *c;
+  int ret;
+
+  if (!TC_GET_REFERENCES(&references, chain, handle))
+    return 0;
+
+  iptc_fn = TC_DELETE_CHAIN;
+
+  if (TC_BUILTIN(chain, *handle)) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  if (references > 0) {
+    errno = EMLINK;
+    return 0;
+  }
+
+  if (!(c = find_label(chain, *handle))) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  if ((void *)c->start != c->end) {
+    errno = ENOTEMPTY;
+    return 0;
+  }
+
+  /* Need label index: preceeds chain start */
+  labelidx = entry2index(*handle, c->start) - 1;
+  labeloff = index2offset(*handle, labelidx);
+
+  ret = delete_rules(2,
+         get_entry(*handle, labeloff)->next_offset
+         + c->start->next_offset,
+         labeloff, labelidx, handle);
+  return ret;
+}
+
+/* Renames a chain. */
+int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname,
+        const IPT_CHAINLABEL newname,
+        TC_HANDLE_T *handle)
+{
+  unsigned int labeloff, labelidx;
+  struct chain_cache *c;
+  struct ipt_error_target *t;
+
+  iptc_fn = TC_RENAME_CHAIN;
+
+  /* find_label doesn't cover built-in targets: DROP, ACCEPT,
+           QUEUE, RETURN. */
+  if (find_label(newname, *handle)
+      || strcmp(newname, LABEL_DROP) == 0
+      || strcmp(newname, LABEL_ACCEPT) == 0
+      || strcmp(newname, LABEL_QUEUE) == 0
+      || strcmp(newname, LABEL_RETURN) == 0) {
+    errno = EEXIST;
+    return 0;
+  }
+
+  if (!(c = find_label(oldname, *handle))
+      || TC_BUILTIN(oldname, *handle)) {
+    errno = ENOENT;
+    return 0;
+  }
+
+  if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  /* Need label index: preceeds chain start */
+  labelidx = entry2index(*handle, c->start) - 1;
+  labeloff = index2offset(*handle, labelidx);
+
+  t = (struct ipt_error_target *)
+    GET_TARGET(get_entry(*handle, labeloff));
+
+  memset(t->error, 0, sizeof(t->error));
+  strcpy(t->error, newname);
+  set_changed(*handle);
+
+  return 1;
+}
+
+/* Sets the policy on a built-in chain. */
+int
+TC_SET_POLICY(const IPT_CHAINLABEL chain,
+        const IPT_CHAINLABEL policy,
+        STRUCT_COUNTERS *counters,
+        TC_HANDLE_T *handle)
+{
+  unsigned int hook;
+  unsigned int policyoff, ctrindex;
+  STRUCT_ENTRY *e;
+  STRUCT_STANDARD_TARGET *t;
+
+  iptc_fn = TC_SET_POLICY;
+  /* Figure out which chain. */
+  hook = TC_BUILTIN(chain, *handle);
+  if (hook == 0) {
+    errno = ENOENT;
+    return 0;
+  } else
+    hook--;
+
+  policyoff = get_chain_end(*handle, (*handle)->info.hook_entry[hook]);
+  if (policyoff != (*handle)->info.underflow[hook]) {
+    printf("ERROR: Policy for `%s' offset %u != underflow %u\n",
+           chain, policyoff, (*handle)->info.underflow[hook]);
+    return 0;
+  }
+
+  e = get_entry(*handle, policyoff);
+  t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+
+  if (strcmp(policy, LABEL_ACCEPT) == 0)
+    t->verdict = -NF_ACCEPT - 1;
+  else if (strcmp(policy, LABEL_DROP) == 0)
+    t->verdict = -NF_DROP - 1;
+  else {
+    errno = EINVAL;
+    return 0;
+  }
+
+  ctrindex = entry2index(*handle, e);
+
+  if (counters) {
+    /* set byte and packet counters */
+    memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS));
+
+    (*handle)->counter_map[ctrindex].maptype
+      = COUNTER_MAP_SET;
+
+  } else {
+    (*handle)->counter_map[ctrindex]
+      = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 });
+  }
+
+  set_changed(*handle);
+
+  return 1;
+}
+
+/* Without this, on gcc 2.7.2.3, we get:
+   libiptc.c: In function `TC_COMMIT':
+   libiptc.c:833: fixed or forbidden register was spilled.
+   This may be due to a compiler bug or to impossible asm
+   statements or clauses.
+*/
+static void
+subtract_counters(STRUCT_COUNTERS *answer,
+      const STRUCT_COUNTERS *a,
+      const STRUCT_COUNTERS *b)
+{
+  answer->pcnt = a->pcnt - b->pcnt;
+  answer->bcnt = a->bcnt - b->bcnt;
+}
+
+int
+TC_COMMIT(TC_HANDLE_T *handle)
+{
+  /* Replace, then map back the counters. */
+  STRUCT_REPLACE *repl;
+  STRUCT_COUNTERS_INFO *newcounters;
+  unsigned int i;
+  size_t counterlen
+    = sizeof(STRUCT_COUNTERS_INFO)
+    + sizeof(STRUCT_COUNTERS) * (*handle)->new_number;
+
+  CHECK(*handle);
+#if 0
+  TC_DUMP_ENTRIES(*handle);
+#endif
+
+  /* Don't commit if nothing changed. */
+  if (!(*handle)->changed)
+    goto finished;
+
+  repl = malloc(sizeof(*repl) + (*handle)->entries.size);
+  if (!repl) {
+    errno = ENOMEM;
+    return 0;
+  }
+
+  /* These are the old counters we will get from kernel */
+  repl->counters = malloc(sizeof(STRUCT_COUNTERS)
+        * (*handle)->info.num_entries);
+  if (!repl->counters) {
+    free(repl);
+    errno = ENOMEM;
+    return 0;
+  }
+
+  /* These are the counters we're going to put back, later. */
+  newcounters = malloc(counterlen);
+  if (!newcounters) {
+    free(repl->counters);
+    free(repl);
+    errno = ENOMEM;
+    return 0;
+  }
+
+  strcpy(repl->name, (*handle)->info.name);
+  repl->num_entries = (*handle)->new_number;
+  repl->size = (*handle)->entries.size;
+  memcpy(repl->hook_entry, (*handle)->info.hook_entry,
+         sizeof(repl->hook_entry));
+  memcpy(repl->underflow, (*handle)->info.underflow,
+         sizeof(repl->underflow));
+  repl->num_counters = (*handle)->info.num_entries;
+  repl->valid_hooks = (*handle)->info.valid_hooks;
+  memcpy(repl->entries, (*handle)->entries.entrytable,
+         (*handle)->entries.size);
+
+  if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,
+           sizeof(*repl) + (*handle)->entries.size) < 0) {
+    free(repl->counters);
+    free(repl);
+    free(newcounters);
+    return 0;
+  }
+
+  /* Put counters back. */
+  strcpy(newcounters->name, (*handle)->info.name);
+  newcounters->num_counters = (*handle)->new_number;
+  for (i = 0; i < (*handle)->new_number; i++) {
+    unsigned int mappos = (*handle)->counter_map[i].mappos;
+    switch ((*handle)->counter_map[i].maptype) {
+    case COUNTER_MAP_NOMAP:
+      newcounters->counters[i]
+        = ((STRUCT_COUNTERS){ 0, 0 });
+      break;
+
+    case COUNTER_MAP_NORMAL_MAP:
+      /* Original read: X.
+       * Atomic read on replacement: X + Y.
+       * Currently in kernel: Z.
+       * Want in kernel: X + Y + Z.
+       * => Add in X + Y
+       * => Add in replacement read.
+       */
+      newcounters->counters[i] = repl->counters[mappos];
+      break;
+
+    case COUNTER_MAP_ZEROED:
+      /* Original read: X.
+       * Atomic read on replacement: X + Y.
+       * Currently in kernel: Z.
+       * Want in kernel: Y + Z.
+       * => Add in Y.
+       * => Add in (replacement read - original read).
+       */
+      subtract_counters(&newcounters->counters[i],
+            &repl->counters[mappos],
+            &index2entry(*handle, i)->counters);
+      break;
+
+    case COUNTER_MAP_SET:
+      /* Want to set counter (iptables-restore) */
+
+      memcpy(&newcounters->counters[i],
+             &index2entry(*handle, i)->counters,
+             sizeof(STRUCT_COUNTERS));
+
+      break;
+    }
+  }
+
+#ifdef KERNEL_64_USERSPACE_32
+  {
+    /* Kernel will think that pointer should be 64-bits, and get
+       padding.  So we accomodate here (assumption: alignment of
+       `counters' is on 64-bit boundary). */
+    u_int64_t *kernptr = (u_int64_t *)&newcounters->counters;
+    if ((unsigned long)&newcounters->counters % 8 != 0) {
+      fprintf(stderr,
+        "counters alignment incorrect! Mail rusty!\n");
+      abort();
+    }
+    *kernptr = newcounters->counters;
+  }
+#endif /* KERNEL_64_USERSPACE_32 */
+
+  if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS,
+           newcounters, counterlen) < 0) {
+    free(repl->counters);
+    free(repl);
+    free(newcounters);
+    return 0;
+  }
+
+  free(repl->counters);
+  free(repl);
+  free(newcounters);
+
+ finished:
+  if ((*handle)->cache_chain_heads)
+    free((*handle)->cache_chain_heads);
+  free(*handle);
+  *handle = NULL;
+  return 1;
+}
+
+/* Get raw socket. */
+int
+TC_GET_RAW_SOCKET()
+{
+  return sockfd;
+}
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *
+TC_STRERROR(int err)
+{
+  unsigned int i;
+  struct table_struct {
+    void *fn;
+    int err;
+    const char *message;
+  } table [] =
+    { { NULL, 0, "Incompatible with this kernel" },
+      { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" },
+      { NULL, ENOSYS, "Will be implemented real soon.  I promise." },
+      { NULL, ENOMEM, "Memory allocation problem" },
+      { TC_INIT, EPERM, "Permission denied (you must be root)" },
+      { TC_INIT, EINVAL, "Module is wrong version" },
+      { TC_INIT, ENOENT, "Table does not exist (do you need to insmod?)" },
+      { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" },
+      { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" },
+      { TC_DELETE_CHAIN, EMLINK,
+        "Can't delete chain with references left" },
+      { TC_CREATE_CHAIN, EEXIST, "Chain already exists" },
+      { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" },
+      { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" },
+      { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" },
+      { TC_READ_COUNTER, E2BIG, "Index of counter too big" },
+      { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" },
+      { TC_INSERT_ENTRY, ELOOP, "Loop found in table" },
+      { TC_INSERT_ENTRY, EINVAL, "Target problem" },
+      /* EINVAL for CHECK probably means bad interface. */
+      { TC_CHECK_PACKET, EINVAL,
+        "Bad arguments (does that interface exist?)" },
+      /* ENOENT for DELETE probably means no matching rule */
+      { TC_DELETE_ENTRY, ENOENT,
+        "Bad rule (does a matching rule exist in that chain?)" },
+      { TC_SET_POLICY, ENOENT,
+        "Bad built-in chain name" },
+      { TC_SET_POLICY, EINVAL,
+        "Bad policy name" },
+      { NULL, ENOENT, "No chain/target/match by that name" }
+    };
+
+  for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) {
+    if ((!table[i].fn || table[i].fn == iptc_fn)
+        && table[i].err == err)
+      return table[i].message;
+  }
+
+  return strerror(err);
+}
+-