Previous patch

Home

Next patch

./src/configs.c.in

Patch: Additional logging in NTP

+/***************************************
+
+    This is part of frox: A simple transparent FTP proxy
+    Copyright (C) 2000 James Hollingshead
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  config.c: configuration file parsing
+
+  %_REPLACE_COMMENT_%
+  
+  ***************************************/
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <grp.h>
+#include <syslog.h>
+
+#include "common.h"
+
+void usage(int option);
+void set_defaults(void);
+
+struct options config;
+
+enum vartype { BOOL, FILENAME, DIRECTRY, STRING, ADDRESS, ADDRPRT, INT, PRTRNGE,
+  ACL, SUBSECT
+};
+
+struct option_array {
+  const char *name;
+  enum vartype type;
+  void *var;
+  char cmdline;
+  int reloadable;
+  int essential;
+};
+
+struct option_array opts[] = {
+  /* %_REPLACE_OPTIONS_INIT_% */
+};
+
+void print_config(void);
+int name2addr(sstr * str, struct in_addr *addr);
+int store_acl(sstr * str, struct acl_list *acls);
+int store_subsect(sstr * str, struct subsect_list *list);
+int set_opt(struct option_array *opt, sstr * str);
+u_int16_t *get_portlist(sstr * str);
+int process_opts(void);
+int parse_line(sstr * buf);
+int match_acl(struct acl_item *acl, struct sockaddr_in *src,
+        struct sockaddr_in *dst, const char *user);
+
+static char cmdline_set[100] = { 0 };
+static int rereading = FALSE;
+static int sublevel = 0;
+static int line_no;
+
+/* ------------------------------------------------------------- **
+** Functions to read / parse the config file.
+** ------------------------------------------------------------- */
+int process_cmdline(int argc, char *argv[])
+{
+  int i;
+  struct option_array *opt;
+  sstr *arg;
+  char *optp, *argp;
+
+  set_defaults();
+  if (argc <= 1)
+    return 0;
+
+  arg = sstr_init(0);
+
+  i = 1;
+  optp = argv[i] + 1;
+
+  while (i < argc) {
+    if (argv[i][0] != '-')
+      usage(-1);
+    opt = opts;
+    if (*optp == '-')
+      usage(-1);  /*?TODO longopts */
+    else {
+      while (opt->name != NULL && opt->cmdline != *optp)
+        opt++;
+      if (opt->name == NULL)
+        usage(*optp);
+      if (strlen(cmdline_set) < 99)
+        cmdline_set[strlen(cmdline_set)] = *optp;
+      if (opt->type == BOOL) {
+        sstr_cpy2(arg, "yes");
+        set_opt(opt, arg);
+        if (*(++optp) == 0) {
+          i++;
+          optp = argv[i] + 1;
+        }
+      } else {
+        argp = optp + 1;
+        if (*argp == 0) {
+          if (i >= argc - 1 ||
+              *(argp = argv[++i]) == '-')
+            usage(*optp);
+
+        }
+        sstr_cpy2(arg, argp);
+        set_opt(opt, arg);
+        i++;
+        optp = argv[i] + 1;
+      }
+    }
+  }
+  sstr_free(arg);
+  return (0);
+}
+
+/*This is out here as a global so that if config is copied to a backup
+  location read_config can be called recursively to read a subsection.*/
+FILE *fp = NULL;
+
+int read_config()
+{
+  struct option_array *opt;
+  sstr *buf;
+
+  if (!fp) {
+    if (config.config_file == NULL) fp = fopen(CONFIG_FILE, "r");
+    else fp = fopen(config.config_file, "r");
+    if (fp == NULL) return (-1);
+    line_no=0;
+  }
+
+  buf = sstr_init(0);
+  while (sstr_fgets(buf, fp) != NULL) {
+    line_no++;
+    switch (parse_line(buf)) {
+    case -1:
+      sstr_free(buf);
+      if (sublevel == 0) fclose(fp);
+      return (-1);
+    case 0:
+      break;
+
+    case 1:  /* end subsection */
+      sstr_free(buf);
+      return (0);
+    }
+  };
+  sstr_free(buf);
+  fclose(fp);
+  fp = NULL;
+
+  opt = opts;
+  while (opt->name != NULL && !opt->essential)
+    opt++;
+  if (opt->name != NULL) {
+    fprintf(stderr, "Essential option \"%s\" not specified.\n",
+      opt->name);
+    return (-1);
+  }
+  process_opts();
+#ifdef DEBUG
+  if(!config.inetd) print_config();
+#endif
+  return (0);
+}
+
+int parse_line(sstr * buf)
+{
+  struct option_array *opt;
+  sstr *tok;
+  tok = sstr_init(0);
+
+  if (sstr_getchar(buf, 0) == '#')
+    return (0);
+  if (sstr_token(buf, tok, " =\t\r\n", 0) == -1)
+    return (0);
+
+  if (!sstr_casecmp2(tok, "EndSection"))
+    return (sublevel > 0 ? 1 : -1);
+
+  opt = opts;
+  while (opt->name != NULL && sstr_casecmp2(tok, opt->name))
+    opt++;
+  if (opt->name == NULL) {
+    fprintf(stderr,"Unrecognised option \"%s\" at line %d of %s\n",
+      sstr_buf(tok), line_no, config.config_file);
+    return (-1);
+  }
+  if (opt->cmdline && strchr(cmdline_set, opt->cmdline)) {
+    fprintf(stderr,
+      "\"%s\" specified on the command line: ignored\n",
+      opt->name);
+    return (0);
+  }
+  if (set_opt(opt, buf) == -1) {
+    fprintf(stderr, "Invalid argument to %s at line %d of %s\n",
+      opt->name, line_no, config.config_file);
+    return (-1);
+  }
+  return (0);
+}
+
+void usage(int option)
+{
+  if (option != 'h' && option != -1)
+    fprintf(stderr, "Unknown option %c\n", option);
+  fprintf(stderr, "Usage frox [-f config_file] [-h]\n");
+  exit(option == 'h' ? 0 : -1);
+}
+
+void set_defaults(void)
+{
+  sstr *tmp;
+  tmp = sstr_init(0);
+
+  /* %_REPLACE_OPTIONS_DEFAULTS_% */
+
+  sstr_free(tmp);
+}
+
+/*Any final processing that needs to be done*/
+int process_opts(void)
+{
+  if (config.chroot) {  /*So that filename stripping works properly */
+    if (config.chroot[strlen(config.chroot) - 1] == '/')
+      config.chroot[strlen(config.chroot) - 1] = 0;
+  }
+
+  /* Get uid/gid before we chroot */
+
+  if (config.group) {
+    struct group *grp;
+    if ((grp = getgrnam(config.group)) == NULL) {
+      fprintf(stderr, "Unable to find group %s\n",
+        config.group);
+      return (-1);
+    }
+    config.gid = grp->gr_gid;
+  }
+
+  if (config.user) {
+    struct passwd *pw;
+    if ((pw = getpwnam(config.user)) == NULL) {
+      fprintf(stderr, "Unable to find user %s\n",
+        config.user);
+      return (-1);
+    }
+    config.uid = pw->pw_uid;
+  }
+
+  config.listen_address.sin_addr = config.listen;
+  config.listen_address.sin_port = htons(config.lport);
+  config.listen_address.sin_family = AF_INET;
+  return (0);
+}
+
+int reread_config(void)
+{
+  int i;
+  struct acl_list acls;
+
+  acls = config.acls;
+  config.acls.num = 0;
+  config.acls.list = NULL;
+
+  rereading = TRUE;
+  sublevel = 0;
+  i = read_config();
+
+  if (i == -1)
+    config.acls = acls;
+  else
+    free(acls.list);
+
+  return (i);
+}
+
+int set_opt(struct option_array *opt, sstr * str)
+{
+  sstr *tok;
+  int sep;
+
+  if (rereading && !opt->reloadable)
+    return (0);
+
+  tok = sstr_init(0);
+
+  sstr_ncat2(str, "\n", 1);  /*For sstr_token */
+  opt->essential = FALSE;
+
+  switch (opt->type) {
+  case BOOL:
+    sstr_token(str, tok, " \t\r\n", 0);
+    if (!sstr_casecmp2(tok, "yes"))
+      *(int *) opt->var = TRUE;
+    else if (!sstr_casecmp2(tok, "no"))
+      *(int *) opt->var = FALSE;
+    else
+      return (-1);
+    break;
+  case FILENAME:
+  case STRING:
+    sstr_token(str, tok, " \t\r\n", SSTR_QTOK);
+    *(char **) opt->var = strdup(sstr_buf(tok));
+    break;
+  case DIRECTRY:
+    sstr_token(str, tok, " \t\r\n", SSTR_QTOK);
+    if (sstr_getchar(tok, sstr_len(tok) - 1) != '/')
+      sstr_ncat2(tok, "/", 1);
+    *(char **) opt->var = strdup(sstr_buf(tok));
+    break;
+  case ADDRESS:
+    sstr_token(str, tok, " \t\r\n", 0);
+    return name2addr(tok, (struct in_addr *) opt->var);
+    break;
+  case ADDRPRT:
+    sep = sstr_token(str, tok, " :\t\r\n", 0);
+    if( name2addr(tok, &((struct sockaddr_in *) opt->var)->sin_addr)==-1)
+      return(-1);
+    if (sep == ':')
+      ((struct sockaddr_in *) opt->var)->sin_port
+          = ntohs(sstr_atoi(str));
+    ((struct sockaddr_in *) opt->var)->sin_family = AF_INET;
+    break;
+  case INT:
+    sstr_token(str, tok, " \t\r\n", 0);
+    *(int *) opt->var = sstr_atoi(tok);
+    break;
+  case PRTRNGE:
+    sstr_token(str, tok, " \t-,\r\n", 0);
+    ((int *) opt->var)[0] = sstr_atoi(tok);
+    ((int *) opt->var)[1] = sstr_atoi(str);
+    if(!valid_uint16(((int *) opt->var)[0]) ||
+       !valid_uint16(((int *) opt->var)[1])) {
+      fprintf(stderr, "Port out of range\n");
+      return -1;
+    }
+    if(((int *) opt->var)[0] >= ((int *) opt->var)[1]) {
+      fprintf(stderr, "Port range is inverted\n");
+      return -1;
+    }
+    break;
+  case ACL:
+    return store_acl(str, (struct acl_list *) opt->var);
+    break;
+  case SUBSECT:
+    return store_subsect(str, (struct subsect_list *) opt->var);
+  }
+  sstr_free(tok);
+  return (0);
+}
+
+int name2addr(sstr * str, struct in_addr *addr)
+{
+  int c;
+  c = sstr_getchar(str, 0);
+  if (sstr_len(str) == 1 && c == '*') {
+    addr->s_addr = INADDR_ANY;
+    return (0);
+  }
+  if (c < '0' || c > '9') {
+    struct hostent *hostinfo;
+
+    hostinfo = gethostbyname(sstr_buf(str));
+    if (hostinfo == NULL) {
+      fprintf(stderr, "Unable to resolve host %s\n",
+        sstr_buf(str));
+      return (-1);
+    }
+    *addr = *((struct in_addr *) *hostinfo->h_addr_list);
+  } else {
+    if (inet_aton(sstr_buf(str), addr) == 0)
+      return (-1);
+  }
+  return (0);
+}
+
+int addrange_match(struct in_addr addr, char *range)
+{
+  char *s;
+  if(*range=='*') return TRUE;
+  else if((s=strchr(range, '/'))) {
+    struct in_addr a, m;
+    s = strchr(range, '/');
+    *s++='\0';
+    if(inet_aton(range, &a) == 0) {
+      write_log(ERROR, "Unable to parse ACL. Ignoring.");
+      return FALSE;
+    }
+    if(strchr(s, '.')) {
+      if (inet_aton(s, &m) == 0) {
+        write_log(ERROR,
+            "Unable to parse ACL. Ignoring.");
+        return FALSE;
+      }
+    } else {
+      int i = atoi(s);
+      m.s_addr = htonl(0xFFFFFFFF << (32-i));
+    }
+    if((addr.s_addr & m.s_addr) == (a.s_addr & m.s_addr))
+      return TRUE;
+    else return FALSE;
+  } else if(*range >= '0' && *range<='9') {
+    struct in_addr a;
+    if(inet_aton(range, &a) == 0) {
+      write_log(ERROR, "Unable to parse ACL. Ignoring.");
+      return FALSE;
+    }
+    if(a.s_addr == addr.s_addr) return TRUE;
+    else return FALSE;
+  } else {
+    struct hostent *hostinfo;
+    struct in_addr *ap;
+    int i;
+
+    hostinfo = gethostbyname(range);
+    if (hostinfo == NULL) {
+      write_log(ERROR, "Unable to resolve host %s in ACL.",
+          range);
+      return FALSE;
+    }
+    for(i=0;hostinfo->h_addr_list[i];i++){
+      ap = (struct in_addr *) hostinfo->h_addr_list[i];
+      if(ap->s_addr == addr.s_addr) return TRUE;
+    }
+    return FALSE;
+  }
+}
+
+int store_addressrange(sstr * str, struct in_addr *addr, struct in_addr *msk)
+{
+  sstr *tok;
+  int sep, i;
+  tok = sstr_init(0);
+
+  sep = sstr_token(str, tok, " /->\r\n", 0);
+  if(name2addr(tok, addr)==-1) return(-1);
+
+  if (sep == '/') {
+    sep = sstr_token(str, tok, " /->\r\n", 0);
+    if (sstr_chr(tok, '.')==-1) {  /* x.x.x.x/yy */
+      i = sstr_atoi(tok);
+      msk->s_addr = htonl(0xFFFFFFFF << (32 - i));
+    } else
+      if(name2addr(tok, msk)==-1) return(-1);
+  } else {    /*No mask given */
+    if (addr->s_addr == INADDR_ANY)
+      msk->s_addr = INADDR_ANY;
+    else
+      msk->s_addr = INADDR_BROADCAST;
+  }
+  sstr_free(tok);
+  return (0);
+}
+
+u_int16_t *get_portlist(sstr * str)
+{
+  sstr *tok;
+  int i = 0, sep;
+  u_int16_t *ports = NULL;
+  tok = sstr_init(0);
+
+  do {
+    ports = realloc(ports, (i + 2) * sizeof(u_int16_t));
+    if(!ports) die(ERROR, "Out of memory reading config file",
+             0, 0, -1);
+    sep = sstr_token(str, tok, " \t,-\r\n", 0);
+    if (sep == -1)
+      break;
+
+    if (!sstr_cmp2(tok, "*")) {
+      ports[i] = 1;
+      ports[i + 1] = 0xFFFF;
+      i += 2;
+      break;
+    }
+    ports[i] = ports[i + 1] = sstr_atoi(tok);
+
+    if (sep == '-') {
+      sep = sstr_token(str, tok, " \t,\n", 0);
+      if (sep == -1)
+        ports[i + 1] = 0xFFFF;
+      else
+        ports[i + 1] = sstr_atoi(tok);
+    }
+    i += 2;
+  } while (sep != -1);
+
+  ports[i] = ports[i + 1] = 0;
+  sstr_free(tok);
+  return (ports);
+}
+
+void parse_match(sstr *str, struct acl_item *item)
+{
+  sstr *tok;
+  tok = sstr_init(0);
+
+  item->user = NULL;
+  sstr_token(str, tok, " \t\n", 0);
+  item->src = strdup(sstr_buf(tok));
+  sstr_token(str, NULL, " \t\n", 0); /*Removes the " - " from the ACL*/
+  if(sstr_chr(str, '@')!=-1) {
+    sstr_token(str, tok, "@", 0);
+    item->user = strdup(sstr_buf(tok));
+    config.fakentp = TRUE;
+  }
+  sstr_token(str, tok, " \t\n", 0);
+  item->dst = strdup(sstr_buf(tok));
+  item->ntp_ports = get_portlist(str);
+
+  sstr_free(tok);
+}
+
+int store_acl(sstr * str, struct acl_list *acls)
+{
+  sstr *tok;
+  struct acl_item item;
+
+  tok = sstr_init(0);
+
+  sstr_token(str, tok, " \t\n", 0);
+
+  if (!sstr_casecmp2(tok, "Allow"))
+    item.action = ALLOW;
+  else if (!sstr_casecmp2(tok, "Deny"))
+    item.action = DENY;
+  else
+    return (-1);
+  sstr_free(tok);
+
+  parse_match(str, &item);
+
+  acls->list = realloc(acls->list,
+           (acls->num + 1) * sizeof(struct acl_item));
+  if(!acls->list) return(-1);
+  memcpy(acls->list + acls->num, &item, sizeof(struct acl_item));
+  acls->num++;
+  return (0);
+}
+
+/* This is quite horrible, but it does seem to work... We make a
+   shallow copy of config, and then recall read_config. config will
+   now be changed with any additional options in the subsection - we
+   copy it where we want it, and restore config. The copy is only a
+   shallow one, but we get away with it as other funcs overwrite
+   pointers not their contents. */
+int store_subsect(sstr * str, struct subsect_list *subsecs)
+{
+  struct options tmp;
+  struct subsect item;
+
+  parse_match(str, &item.match);
+
+  memcpy(&tmp, &config, sizeof(struct options));
+  sublevel++;
+  config.acls.list = NULL;
+  config.subsecs.list = NULL;
+  config.acls.num = config.subsecs.num = 0;
+
+  if (read_config() == -1)
+    return (-1);
+
+  sublevel--;
+  if(process_opts()==-1) return(-1);
+
+  memcpy(&item.config, &config, sizeof(struct options));
+  memcpy(&config, &tmp, sizeof(struct options));
+
+  subsecs->list = realloc(subsecs->list,
+        (subsecs->num + 1) * sizeof(struct subsect));
+  memcpy(subsecs->list + subsecs->num, &item, sizeof(struct subsect));
+  subsecs->num++;
+  return (0);
+}
+
+/* ------------------------------------------------------------- **
+** Alter absolute pathnames to still work after chroot.
+** ------------------------------------------------------------- */
+void strip_filenames(void)
+{
+  struct option_array *opt = opts;
+  char *filename;
+
+  if (config.chroot == NULL)
+    return;
+
+  do {
+    if (opt->type != DIRECTRY && opt->type != FILENAME)
+      continue;
+    filename = *(char **) opt->var;
+    if (filename == NULL)
+      continue;
+    debug2("stripping %s: ", opt->name);
+    debug2("%s-->", filename);
+    if (strncmp(config.chroot, filename, strlen(config.chroot)))
+      *filename = 0;
+    else {
+      memmove(filename, filename + strlen(config.chroot),
+        strlen(filename) - strlen(config.chroot) + 1);
+      if(opt->type == DIRECTRY && 
+         filename[strlen(filename)-1]!='/')
+        strcat(filename, "/");
+    }
+    debug2("%s\n", filename);
+  } while ((++opt)->name != NULL);
+
+  free(config.chroot);
+  config.chroot="/";
+}
+
+/* ------------------------------------------------------------- **
+** Functions to return config file options.
+** ------------------------------------------------------------- */
+int match_acl(struct acl_item *acl, struct sockaddr_in *src,
+        struct sockaddr_in *dst, const char *user)
+{
+  u_int16_t port;
+  int i;
+  char ftpstr[4];
+  const char *userp;
+  if(!strcasecmp(user, "anonymous")) {
+    strcpy(ftpstr, "ftp");
+    userp=ftpstr;
+  } else userp = user;
+
+  port = ntohs(dst->sin_port);
+  if (addrange_match(src->sin_addr, acl->src) && 
+      addrange_match(dst->sin_addr, acl->dst) &&
+      (!acl->user || !strcasecmp(userp, acl->user))) {
+    for (i = 0; acl->ntp_ports[i] != 0; i += 2)
+      if (port >= acl->ntp_ports[i] &&
+          port <= acl->ntp_ports[i + 1]) {
+        return (TRUE);
+      }
+    if (i == 0)
+      return (TRUE);
+  }
+  return (FALSE);
+}
+
+int config_connectionok(struct sockaddr_in *src, struct sockaddr_in *dst,
+            const char *user)
+{
+  int i;
+
+  for (i = 0; i < config.acls.num; i++)
+    if (match_acl(config.acls.list + i, src, dst, user))
+      break;
+  if (i != config.acls.num) {
+    if (config.acls.list[i].action == ALLOW) {
+      config_change(src, dst, user);
+      return (TRUE);
+    } else
+      return (FALSE);
+  }
+  return (FALSE);
+}
+
+void config_change(struct sockaddr_in *src, struct sockaddr_in *dst,
+       const char *user)
+{
+  int i;
+
+  for (i = 0; i < config.subsecs.num; i++)
+    if (match_acl(&config.subsecs.list[i].match, src, dst, user))
+      break;
+
+  if (i != config.subsecs.num) {
+    write_log(VERBOSE, "Changing to config file subsection %d", i+1);
+    memcpy(&config, &config.subsecs.list[i].config, sizeof(config));
+  }
+}
+
+int config_pasvok(struct sockaddr_in *addr)
+{
+  if (addr->sin_addr.s_addr == 0)
+    return (FALSE);
+
+  if (config.sameaddress) {
+    if (addr->sin_addr.s_addr !=
+        info->server_control.address.sin_addr.s_addr) {
+      return (FALSE);
+    }
+  }
+  return (TRUE);
+}
+
+int config_portok(struct sockaddr_in *addr)
+{
+  if (addr->sin_addr.s_addr == 0)
+    return (FALSE);
+
+  if (config.sameaddress)
+    if (addr->sin_addr.s_addr !=
+        info->client_control.address.sin_addr.s_addr)
+      return (FALSE);
+
+  if (config.bdefend)
+    if (ntohs(addr->sin_port) < 1024)
+      return (FALSE);
+
+  return (TRUE);
+}
+
+#ifdef DEBUG
+void print_config(void)
+{
+  struct option_array *opt;
+  struct subsect *p;
+  int i, j;
+
+  opt = opts;
+  while (opt->name != NULL) {
+    switch (opt->type) {
+    case BOOL:
+      fprintf(stderr, "%s = %s\n", opt->name,
+        *(int *) opt->var == TRUE ? "TRUE" : "FALSE");
+      break;
+    case FILENAME:
+    case DIRECTRY:
+    case STRING:
+      fprintf(stderr, "%s = %s\n", opt->name,
+        *(char **) opt->var);
+      break;
+    case ADDRESS:
+      fprintf(stderr, "%s = %s\n", opt->name,
+        inet_ntoa(*(struct in_addr *) opt->var));
+      break;
+    case ADDRPRT:
+      fprintf(stderr, "%s = %s:%d\n", opt->name,
+        inet_ntoa(((struct sockaddr_in *) opt->
+             var)->sin_addr),
+        htons(((struct sockaddr_in *) opt->var)->
+              sin_port));
+      break;
+    case INT:
+      fprintf(stderr, "%s = %d\n", opt->name,
+        *(int *) opt->var);
+      break;
+    case PRTRNGE:
+      fprintf(stderr, "%s = %d-%d\n", opt->name,
+        ((int *) opt->var)[0], ((int *) opt->var)[1]);
+      break;
+    case ACL:
+    case SUBSECT:
+      break;
+    }
+    opt++;
+  }
+  for (i = 0; i < config.acls.num; i++) {
+    fprintf(stderr, "ACL: %s", config.acls.list[i].src);
+    fprintf(stderr, " --> %s @ %s", 
+        config.acls.list[i].user ?
+             config.acls.list[i].user : "*",
+        config.acls.list[i].dst);
+    fprintf(stderr, ": %s ",
+      config.acls.list[i].action == DENY ? "DENY" : "ALLOW");
+    for (j = 0; config.acls.list[i].ntp_ports[j] != 0; j += 2) {
+      fprintf(stderr, "%d-%d,",
+        config.acls.list[i].ntp_ports[j],
+        config.acls.list[i].ntp_ports[j + 1]);
+    }
+    fprintf(stderr, "\n");
+
+  }
+
+  for (i = 0; i < config.subsecs.num; i++) {
+    struct options tmp;
+    p = config.subsecs.list + i;
+
+    fprintf(stderr, "Subsection: %s", p->match.src);
+    fprintf(stderr, " --> %s @ %s", 
+        p->match.user ? p->match.user : "*",
+        p->match.dst);
+    for (j = 0; p->match.ntp_ports[j] != 0; j += 2) {
+      fprintf(stderr, "%d-%d,", p->match.ntp_ports[j],
+        p->match.ntp_ports[j + 1]);
+    }
+    fprintf(stderr, "  {\n");
+    memcpy(&tmp, &config, sizeof(struct options));
+    memcpy(&config, &p->config, sizeof(struct options));
+    print_config();
+    memcpy(&config, &tmp, sizeof(struct options));
+    fprintf(stderr, "}\n");
+  }
+}
+#endif
+-