Apply patch from Scott Lamb adding an output buffer for the TCP sockets.
[tinc] / src / conf.c
index a3c5f69..d567cd2 100644 (file)
@@ -1,9 +1,9 @@
 /*
     conf.c -- configuration code
     Copyright (C) 1998 Robert van der Meulen
-    Copyright (C) 1998,1999,2000 Ivo Timmermans <itimmermans@bigfoot.com>
-                            2000 Guus Sliepen <guus@sliepen.warande.net>
-                           2000 Cris van Pelt <tribbel@arise.dhs.org>
+                  1998-2005 Ivo Timmermans <ivo@tinc-vpn.org>
+                  2000-2005 Guus Sliepen <guus@tinc-vpn.org>
+                 2000 Cris van Pelt <tribbel@arise.dhs.org>
 
     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
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id: conf.c,v 1.9.4.30 2000/12/01 12:36:36 zarq Exp $
+    $Id$
 */
 
-#include "config.h"
+#include "system.h"
 
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
-#include <netdb.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
+#include "avl_tree.h"
+#include "conf.h"
+#include "logger.h"
+#include "netutl.h"                            /* for str2address */
+#include "utils.h"                             /* for cp */
+#include "xalloc.h"
 
-#include <xalloc.h>
-#include <utils.h> /* for cp */
+avl_tree_t *config_tree;
 
-#include "conf.h"
-#include "netutl.h" /* for strtoip */
+int pinginterval = 0;                  /* seconds between pings */
+int pingtimeout = 0;                   /* seconds to wait for response */
+char *confbase = NULL;                 /* directory in which all config files are */
+char *netname = NULL;                  /* name of the vpn network */
 
-#include "system.h"
+static int config_compare(const config_t *a, const config_t *b)
+{
+       int result;
 
-config_t *config = NULL;
-int debug_lvl = 0;
-int timeout = 0; /* seconds before timeout */
-char *confbase = NULL;           /* directory in which all config files are */
-char *netname = NULL;            /* name of the vpn network */
+       result = strcasecmp(a->variable, b->variable);
 
-/* Will be set if HUP signal is received. It will be processed when it is safe. */
-int sighup = 0;
+       if(result)
+               return result;
 
-/*
-  These are all the possible configurable values
-*/
-static internal_config_t hazahaza[] = {
-/* Main configuration file keywords */
-  { "Name",         config_name,       TYPE_NAME },
-  { "ConnectTo",    config_connectto,      TYPE_NAME },
-  { "PingTimeout",  config_pingtimeout,    TYPE_INT },
-  { "TapDevice",    config_tapdevice,      TYPE_NAME },
-  { "PrivateKey",   config_privatekey,     TYPE_NAME },
-  { "KeyExpire",    config_keyexpire,      TYPE_INT },
-  { "Hostnames",    config_hostnames,    TYPE_BOOL },
-  { "Interface",    config_interface,      TYPE_NAME },
-  { "InterfaceIP",  config_interfaceip,    TYPE_IP },
-/* Host configuration file keywords */
-  { "Address",      config_address,        TYPE_NAME },
-  { "Port",         config_port,           TYPE_INT },
-  { "PublicKey",    config_publickey,      TYPE_NAME },
-  { "Subnet",       config_subnet,         TYPE_IP },          /* Use IPv4 subnets only for now */
-  { "RestrictHosts", config_restricthosts, TYPE_BOOL },
-  { "RestrictSubnets", config_restrictsubnets, TYPE_BOOL },
-  { "RestrictAddress", config_restrictaddress, TYPE_BOOL },
-  { "RestrictPort", config_restrictport,   TYPE_BOOL },
-  { "IndirectData", config_indirectdata,   TYPE_BOOL },
-  { "TCPonly",      config_tcponly,        TYPE_BOOL },
-  { NULL, 0, 0 }
-};
+       result = a->line - b->line;
 
-/*
-  Add given value to the list of configs cfg
-*/
-config_t *
-add_config_val(config_t **cfg, int argtype, char *val)
+       if(result)
+               return result;
+       else
+               return strcmp(a->file, b->file);
+}
+
+void init_configuration(avl_tree_t ** config_tree)
+{
+       cp();
+
+       *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
+}
+
+void exit_configuration(avl_tree_t ** config_tree)
 {
-  config_t *p;
-  char *q;
-cp
-  p = (config_t*)xmalloc(sizeof(*p));
-  p->data.val = 0;
-
-  switch(argtype)
-    {
-    case TYPE_INT:
-      p->data.val = strtol(val, &q, 0);
-      if(q && *q)
-       p->data.val = 0;
-      break;
-    case TYPE_NAME:
-      p->data.ptr = xmalloc(strlen(val) + 1);
-      strcpy(p->data.ptr, val);
-      break;
-    case TYPE_IP:
-      p->data.ip = strtoip(val);
-      break;
-    case TYPE_BOOL:
-      if(!strcasecmp("yes", val))
-       p->data.val = stupid_true;
-      else if(!strcasecmp("no", val))
-       p->data.val = stupid_false;
-      else
-       p->data.val = 0;
-    }
-
-  p->argtype = argtype;
-
-  if(p->data.val)
-    {
-      p->next = *cfg;
-      *cfg = p;
-cp
-      return p;
-    }
-  else
-    {
-      free(p);
-cp
-      return NULL;
-    }
+       cp();
+
+       avl_delete_tree(*config_tree);
+       *config_tree = NULL;
+}
+
+config_t *new_config(void)
+{
+       cp();
+
+       return xmalloc_and_zero(sizeof(config_t));
+}
+
+void free_config(config_t *cfg)
+{
+       cp();
+
+       if(cfg->variable)
+               free(cfg->variable);
+
+       if(cfg->value)
+               free(cfg->value);
+
+       if(cfg->file)
+               free(cfg->file);
+
+       free(cfg);
+}
+
+void config_add(avl_tree_t *config_tree, config_t *cfg)
+{
+       cp();
+
+       avl_insert(config_tree, cfg);
+}
+
+config_t *lookup_config(avl_tree_t *config_tree, char *variable)
+{
+       config_t cfg, *found;
+
+       cp();
+
+       cfg.variable = variable;
+       cfg.file = "";
+       cfg.line = 0;
+
+       found = avl_search_closest_greater(config_tree, &cfg);
+
+       if(!found)
+               return NULL;
+
+       if(strcasecmp(found->variable, variable))
+               return NULL;
+
+       return found;
+}
+
+config_t *lookup_config_next(avl_tree_t *config_tree, const config_t *cfg)
+{
+       avl_node_t *node;
+       config_t *found;
+
+       cp();
+
+       node = avl_search_node(config_tree, cfg);
+
+       if(node) {
+               if(node->next) {
+                       found = node->next->data;
+
+                       if(!strcasecmp(found->variable, cfg->variable))
+                               return found;
+               }
+       }
+
+       return NULL;
+}
+
+bool get_config_bool(const config_t *cfg, bool *result)
+{
+       cp();
+
+       if(!cfg)
+               return false;
+
+       if(!strcasecmp(cfg->value, "yes")) {
+               *result = true;
+               return true;
+       } else if(!strcasecmp(cfg->value, "no")) {
+               *result = false;
+               return true;
+       }
+
+       logger(LOG_ERR, _("\"yes\" or \"no\" expected for configuration variable %s in %s line %d"),
+                  cfg->variable, cfg->file, cfg->line);
+
+       return false;
+}
+
+bool get_config_int(const config_t *cfg, int *result)
+{
+       cp();
+
+       if(!cfg)
+               return false;
+
+       if(sscanf(cfg->value, "%d", result) == 1)
+               return true;
+
+       logger(LOG_ERR, _("Integer expected for configuration variable %s in %s line %d"),
+                  cfg->variable, cfg->file, cfg->line);
+
+       return false;
+}
+
+bool get_config_string(const config_t *cfg, char **result)
+{
+       cp();
+
+       if(!cfg)
+               return false;
+
+       *result = xstrdup(cfg->value);
+
+       return true;
+}
+
+bool get_config_address(const config_t *cfg, struct addrinfo **result)
+{
+       struct addrinfo *ai;
+
+       cp();
+
+       if(!cfg)
+               return false;
+
+       ai = str2addrinfo(cfg->value, NULL, 0);
+
+       if(ai) {
+               *result = ai;
+               return true;
+       }
+
+       logger(LOG_ERR, _("Hostname or IP address expected for configuration variable %s in %s line %d"),
+                  cfg->variable, cfg->file, cfg->line);
+
+       return false;
+}
+
+bool get_config_subnet(const config_t *cfg, subnet_t ** result)
+{
+       subnet_t subnet = {0};
+
+       cp();
+
+       if(!cfg)
+               return false;
+
+       if(!str2net(&subnet, cfg->value)) {
+               logger(LOG_ERR, _("Subnet expected for configuration variable %s in %s line %d"),
+                          cfg->variable, cfg->file, cfg->line);
+               return false;
+       }
+
+       /* Teach newbies what subnets are... */
+
+       if(((subnet.type == SUBNET_IPV4)
+               && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t)))
+               || ((subnet.type == SUBNET_IPV6)
+               && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t)))) {
+               logger(LOG_ERR, _ ("Network address and prefix length do not match for configuration variable %s in %s line %d"),
+                          cfg->variable, cfg->file, cfg->line);
+               return false;
+       }
+
+       *(*result = new_subnet()) = subnet;
+
+       return true;
 }
 
 /*
   Read exactly one line and strip the trailing newline if any.  If the
   file was on EOF, return NULL. Otherwise, return all the data in a
   dynamically allocated buffer.
-  
+
   If line is non-NULL, it will be used as an initial buffer, to avoid
   unnecessary mallocing each time this function is called.  If buf is
   given, and buf needs to be expanded, the var pointed to by buflen
   will be increased.
 */
-char *readline(FILE *fp, char *buf, size_t *buflen)
+static char *readline(FILE * fp, char **buf, size_t *buflen)
 {
-  char *newline = NULL;
-  char *p;
-  char *line; /* The array that contains everything that has been read
-                 so far */
-  char *idx; /* Read into this pointer, which points to an offset
-                within line */
-  size_t size, newsize; /* The size of the current array pointed to by
-                           line */
-  size_t maxlen; /* Maximum number of characters that may be read with
-                    fgets.  This is newsize - oldsize. */
-
-  if(feof(fp))
-    return NULL;
-
-  if((buf != NULL) && (buflen != NULL))
-    {
-      size = *buflen;
-      line = buf;
-    }
-  else
-    {
-      size = 100;
-      line = xmalloc(size);
-    }
-
-  maxlen = size;
-  idx = line;
-  *idx = 0;
-  for(;;)
-    {
-      errno = 0;
-      p = fgets(idx, maxlen, fp);
-      if(p == NULL)  /* EOF or error */
-       {
-         if(feof(fp))
-           break;
-
-         /* otherwise: error; let the calling function print an error
-             message if applicable */
-         free(line);
-         return NULL;
+       char *newline = NULL;
+       char *p;
+       char *line;                                     /* The array that contains everything that has been read so far */
+       char *idx;                                      /* Read into this pointer, which points to an offset within line */
+       size_t size, newsize;           /* The size of the current array pointed to by line */
+       size_t maxlen;                          /* Maximum number of characters that may be read with fgets.  This is newsize - oldsize. */
+
+       if(feof(fp))
+               return NULL;
+
+       if(buf && buflen) {
+               size = *buflen;
+               line = *buf;
+       } else {
+               size = 100;
+               line = xmalloc(size);
        }
 
-      newline = strchr(p, '\n');
-      if(newline == NULL)
-       /* We haven't yet read everything to the end of the line */
-       {
-         newsize = size << 1;
-         line = xrealloc(line, newsize);
-         idx = &line[size - 1];
-         maxlen = newsize - size + 1;
-         size = newsize;
+       maxlen = size;
+       idx = line;
+       *idx = 0;
+
+       for(;;) {
+               errno = 0;
+               p = fgets(idx, maxlen, fp);
+
+               if(!p) {                                /* EOF or error */
+                       if(feof(fp))
+                               break;
+
+                       /* otherwise: error; let the calling function print an error message if applicable */
+                       free(line);
+                       return NULL;
+               }
+
+               newline = strchr(p, '\n');
+
+               if(!newline) {                  /* We haven't yet read everything to the end of the line */
+                       newsize = size << 1;
+                       line = xrealloc(line, newsize);
+                       idx = &line[size - 1];
+                       maxlen = newsize - size + 1;
+                       size = newsize;
+               } else {
+                       *newline = '\0';        /* kill newline */
+                       break;                          /* yay */
+               }
        }
-      else
-       {
-         *newline = '\0'; /* kill newline */
-         break;  /* yay */
+
+       if(buf && buflen) {
+               *buflen = size;
+               *buf = line;
        }
-    }
 
-  if((buf != NULL) && (buflen != NULL))
-    *buf = size;
-  return line;
+       return line;
 }
 
 /*
   Parse a configuration file and put the results in the configuration tree
   starting at *base.
 */
-int read_config_file(config_t **base, const char *fname)
+int read_config_file(avl_tree_t *config_tree, const char *fname)
 {
-  int err = -1;
-  FILE *fp;
-  char *line;
-  char *p, *q;
-  int i, lineno = 0;
-  config_t *cfg;
-  size_t bufsize;
-  
-cp
-  if((fp = fopen (fname, "r")) == NULL)
-    return -1;
-
-  bufsize = 100;
-  line = xmalloc(bufsize);
-  
-  for(;;)
-    {
-      if((line = readline(fp, line, &bufsize)) == NULL)
-       {
-         err = -1;
-         break;
+       int err = -2;                           /* Parse error */
+       FILE *fp;
+       char *buffer, *line;
+       char *variable, *value, *eol;
+       int lineno = 0;
+       int len;
+       bool ignore = false;
+       config_t *cfg;
+       size_t bufsize;
+
+       cp();
+
+       fp = fopen(fname, "r");
+
+       if(!fp) {
+               logger(LOG_ERR, _("Cannot open config file %s: %s"), fname,
+                          strerror(errno));
+               return -3;
        }
 
-      if(feof(fp))
-       {
-         err = 0;
-         break;
+       bufsize = 100;
+       buffer = xmalloc(bufsize);
+
+       for(;;) {
+               line = readline(fp, &buffer, &bufsize);
+
+               if(!line) {
+                       err = -1;
+                       break;
+               }
+
+               if(feof(fp)) {
+                       err = 0;
+                       break;
+               }
+
+               lineno++;
+
+               if(!*line || *line == '#')
+                       continue;
+
+               if(ignore) {
+                       if(!strncmp(line, "-----END", 8))
+                               ignore = false;
+                       continue;
+               }
+               
+               if(!strncmp(line, "-----BEGIN", 10)) {
+                       ignore = true;
+                       continue;
+               }
+
+               variable = value = line;
+
+               eol = line + strlen(line);
+               while(strchr("\t ", *--eol))
+                       *eol = '\0';
+
+               len = strcspn(value, "\t =");
+               value += len;
+               value += strspn(value, "\t ");
+               if(*value == '=') {
+                       value++;
+                       value += strspn(value, "\t ");
+               }
+               variable[len] = '\0';
+
+       
+               if(!*value) {
+                       logger(LOG_ERR, _("No value for variable `%s' on line %d while reading config file %s"),
+                                  variable, lineno, fname);
+                       break;
+               }
+
+               cfg = new_config();
+               cfg->variable = xstrdup(variable);
+               cfg->value = xstrdup(value);
+               cfg->file = xstrdup(fname);
+               cfg->line = lineno;
+
+               config_add(config_tree, cfg);
        }
 
-      lineno++;
+       free(buffer);
+       fclose(fp);
 
-      if((p = strtok(line, "\t =")) == NULL)
-       continue; /* no tokens on this line */
-
-      if(p[0] == '#')
-       continue; /* comment: ignore */
+       return err;
+}
 
-      for(i = 0; hazahaza[i].name != NULL; i++)
-       if(!strcasecmp(hazahaza[i].name, p))
-         break;
+bool read_server_config()
+{
+       char *fname;
+       int x;
 
-      if(!hazahaza[i].name)
-       {
-         syslog(LOG_ERR, _("Invalid variable name `%s' on line %d while reading config file %s"),
-                 p, lineno, fname);
-          break;
-       }
+       cp();
 
-      if(((q = strtok(NULL, "\t\n\r =")) == NULL) || q[0] == '#')
-       {
-         fprintf(stderr, _("No value for variable `%s' on line %d while reading config file %s"),
-                 hazahaza[i].name, lineno, fname);
-         break;
-       }
+       asprintf(&fname, "%s/tinc.conf", confbase);
+       x = read_config_file(config_tree, fname);
 
-      cfg = add_config_val(base, hazahaza[i].argtype, q);
-      if(cfg == NULL)
-       {
-         fprintf(stderr, _("Invalid value for variable `%s' on line %d while reading config file %s"),
-                 hazahaza[i].name, lineno, fname);
-         break;
+       if(x == -1) {                           /* System error: complain */
+               logger(LOG_ERR, _("Failed to read `%s': %s"), fname, strerror(errno));
        }
 
-      cfg->which = hazahaza[i].which;
-      if(!config)
-       config = cfg;
-    }
+       free(fname);
 
-  free(line);
-  fclose (fp);
-cp
-  return err;
+       return x == 0;
 }
 
-int read_server_config()
+FILE *ask_and_open(const char *filename, const char *what, const char *mode)
 {
-  char *fname;
-  int x;
-cp
-  asprintf(&fname, "%s/tinc.conf", confbase);
-  x = read_config_file(&config, fname);
-  if(x != 0)
-    {
-      fprintf(stderr, _("Failed to read `%s': %m\n"),
-             fname);
-    }
-  free(fname);
-cp
-  return x;  
-}
+       FILE *r;
+       char *directory;
+       char *fn;
+
+       /* Check stdin and stdout */
+       if(!isatty(0) || !isatty(1)) {
+               /* Argh, they are running us from a script or something.  Write
+                  the files to the current directory and let them burn in hell
+                  for ever. */
+               fn = xstrdup(filename);
+       } else {
+               /* Ask for a file and/or directory name. */
+               fprintf(stdout, _("Please enter a file to save %s to [%s]: "),
+                               what, filename);
+               fflush(stdout);
+
+               fn = readline(stdin, NULL, NULL);
+
+               if(!fn) {
+                       fprintf(stderr, _("Error while reading stdin: %s\n"),
+                                       strerror(errno));
+                       return NULL;
+               }
+
+               if(!strlen(fn))
+                       /* User just pressed enter. */
+                       fn = xstrdup(filename);
+       }
 
-/*
-  Look up the value of the config option type
-*/
-const config_t *get_config_val(config_t *p, which_t type)
-{
-cp
-  for(; p != NULL; p = p->next)
-    if(p->which == type)
-      break;
-cp
-  return p;
-}
+#ifdef HAVE_MINGW
+       if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
+#else
+       if(fn[0] != '/') {
+#endif
+               /* The directory is a relative path or a filename. */
+               char *p;
+
+               directory = get_current_dir_name();
+               asprintf(&p, "%s/%s", directory, fn);
+               free(fn);
+               free(directory);
+               fn = p;
+       }
 
-/*
-  Remove the complete configuration tree.
-*/
-void clear_config(config_t **base)
-{
-  config_t *p, *next;
-cp
-  for(p = *base; p != NULL; p = next)
-    {
-      next = p->next;
-      if(p->data.ptr && (p->argtype == TYPE_NAME))
-        {
-          free(p->data.ptr);
-        }
-      free(p);
-    }
-  *base = NULL;
-cp
-}
+       umask(0077);                            /* Disallow everything for group and other */
 
-int isadir(const char* f)
-{
-  struct stat s;
+       /* Open it first to keep the inode busy */
 
-  if(stat(f, &s) < 0)
-    {
-      fprintf(stderr, _("Couldn't stat `%s': %m\n"),
-             f);
-      return -1;
-    }
+       r = fopen(fn, mode);
 
-  return S_ISDIR(s.st_mode);
-}
+       if(!r) {
+               fprintf(stderr, _("Error opening file `%s': %s\n"),
+                               fn, strerror(errno));
+               free(fn);
+               return NULL;
+       }
 
-int is_safe_path(const char *file)
-{
-  char *p;
-  struct stat s;
-
-  p = strrchr(file, '/');
-  assert(p); /* p has to contain a / */
-  *p = '\0';
-  if(stat(file, &s) < 0)
-    {
-      fprintf(stderr, _("Couldn't stat `%s': %m\n"),
-             file);
-      return 0;
-    }
-  if(s.st_uid != geteuid())
-    {
-      fprintf(stderr, _("`%s' is owned by UID %d instead of %d.\n"),
-             file, s.st_uid, geteuid());
-      return 0;
-    }
-  if(S_ISLNK(s.st_mode))
-    {
-      fprintf(stderr, _("Warning: `%s' is a symlink\n"),
-             file);
-      /* fixme: read the symlink and start again */
-    }
-
-  *p = '/';
-  if(stat(file, &s) < 0 && errno != ENOENT)
-    {
-      fprintf(stderr, _("Couldn't stat `%s': %m\n"),
-             file);
-      return 0;
-    }
-  if(errno == ENOENT)
-    return 1;
-  if(s.st_uid != geteuid())
-    {
-      fprintf(stderr, _("`%s' is owned by UID %d instead of %d.\n"),
-             file, s.st_uid, geteuid());
-      return 0;
-    }
-  if(S_ISLNK(s.st_mode))
-    {
-      fprintf(stderr, _("Warning: `%s' is a symlink\n"),
-             file);
-      /* fixme: read the symlink and start again */
-    }
-  if(s.st_mode & 0007)
-    {
-      /* Accessible by others */
-      fprintf(stderr, _("`%s' has unsecure permissions.\n"),
-             file);
-      return 0;
-    }
-  
-  return 1;
-}
+       free(fn);
 
-FILE *ask_and_safe_open(const char* filename, const char* what)
-{
-  FILE *r;
-  char *directory;
-  char *fn;
-  int len;
-
-  /* Check stdin and stdout */
-  if(!isatty(0) || !isatty(1))
-    {
-      /* Argh, they are running us from a script or something.  Write
-         the files to the current directory and let them burn in hell
-         for ever. */
-      fn = xstrdup(filename);
-    }
-  else
-    {
-      /* Ask for a file and/or directory name. */
-      fprintf(stdout, _("Please enter a file to save %s to [%s]: "),
-             what, filename);
-      fflush(stdout);  /* Don't wait for a newline */
-      if((fn = readline(stdin, NULL, NULL)) == NULL)
-       {
-         fprintf(stderr, _("Error while reading stdin: %m\n"));
-         return NULL;
-       }
-      if(strlen(fn) == 0)
-       /* User just pressed enter. */
-       fn = xstrdup(filename);
-    }
-
-  if((strchr(fn, '/') == NULL) || (fn[0] != '/'))
-    {
-      /* The directory is a relative path or a filename. */
-      char *p;
-      
-      directory = get_current_dir_name();
-      len = strlen(fn) + strlen(directory) + 2; /* 1 for the / */
-      p = xmalloc(len);
-      snprintf(p, len, "%s/%s", directory, fn);
-      free(fn);
-      free(directory);
-      fn = p;
-    }
-
-  if(isadir(fn) > 0) /* -1 is error */
-    {
-      char *p;
-
-      len = strlen(fn) + strlen(filename) + 2; /* 1 for the / */
-      p = xmalloc(len);
-      snprintf(p, len, "%s/%s", fn, filename);
-      free(fn);
-      fn = p;
-    }
-
-  umask(0077); /* Disallow everything for group and other */
-  
-  /* Open it first to keep the inode busy */
-  if((r = fopen(fn, "w")) == NULL)
-    {
-      fprintf(stderr, _("Error opening file `%s': %m\n"),
-             fn);
-      free(fn);
-      return NULL;
-    }
-
-  /* Then check the file for nasty attacks */
-  if(!is_safe_path(fn))  /* Do not permit any directories that are
-                            readable or writeable by other users. */
-    {
-      fprintf(stderr, _("The file `%s' (or any of the leading directories) has unsafe permissions.\n"
-                       "I will not create or overwrite this file.\n"),
-                       fn);
-      fclose(r);
-      free(fn);
-      return NULL;
-    }
-
-  free(fn);
-  
-  return r;
+       return r;
 }