Merge branch 'jennifer/march2016-patches'
authorJennifer Richards <jennifer@painless-security.com>
Mon, 20 Jun 2016 17:39:31 +0000 (13:39 -0400)
committerJennifer Richards <jennifer@painless-security.com>
Mon, 20 Jun 2016 17:39:31 +0000 (13:39 -0400)
Conflicts (both trivial):
common/tr_config.c
common/tr_name.c

common/tr_config.c
common/tr_name.c
gsscon/gsscon_common.c
include/tr_config.h
tid/example/tidc_main.c
tid/example/tids_main.c
tid/tids.c
tr/tr_main.c

index 629b0a1..587a47c 100644 (file)
@@ -908,12 +908,20 @@ TR_CFG_RC tr_cfg_validate (TR_CFG *trc) {
   return rc;
 }
 
-TR_CFG_RC tr_parse_config (TR_INSTANCE *tr, int n, struct dirent **cfg_files) {
+/* Join two paths and return a pointer to the result. This should be freed
+ * via talloc_free. Returns NULL on failure. */
+static char *join_paths(const char *p1, const char *p2) {
+  return talloc_asprintf(NULL, "%s/%s", p1, p2); /* returns NULL on a failure */
+}
+
+/* Reads configuration files in config_dir ("" or "./" will use the current directory) */
+TR_CFG_RC tr_parse_config (TR_INSTANCE *tr, const char *config_dir, int n, struct dirent **cfg_files) {
   json_t *jcfg;
   json_t *jser;
   json_error_t rc;
+  char *file_with_path;
 
-  if ((!tr) || (!cfg_files))
+  if ((!tr) || (!cfg_files) || (n<=0))
     return TR_CFG_BAD_PARAMS;
 
   /* If there is a partial/abandoned config lying around, free it */
@@ -927,13 +935,20 @@ TR_CFG_RC tr_parse_config (TR_INSTANCE *tr, int n, struct dirent **cfg_files) {
 
   /* Parse configuration information from each config file */
   while (n--) {
-    tr_debug("tr_read_config: Parsing %s.", cfg_files[n]->d_name);
-    if (NULL == (jcfg = json_load_file(cfg_files[n]->d_name, 
-                                      JSON_DISABLE_EOF_CHECK, &rc))) {
-      tr_debug("tr_read_config: Error parsing config file %s.", 
-              cfg_files[n]->d_name);
+    file_with_path=join_paths(config_dir, cfg_files[n]->d_name); /* must free result with talloc_free */
+    if(file_with_path == NULL) {
+      tr_crit("tr_parse_config: error joining path.");
+      return TR_CFG_NOMEM;
+    }
+    tr_debug("tr_parse_config: Parsing %s.", cfg_files[n]->d_name); /* print the filename without the path */
+    if (NULL == (jcfg = json_load_file(file_with_path, 
+                                       JSON_DISABLE_EOF_CHECK, &rc))) {
+      tr_debug("tr_parse_config: Error parsing config file %s.", 
+               cfg_files[n]->d_name);
+      talloc_free(file_with_path);
       return TR_CFG_NOPARSE;
     }
+    talloc_free(file_with_path); /* done with filename */
 
     // Look for serial number and log it if it exists
     if (NULL != (jser = json_object_get(jcfg, "serial_number"))) {
@@ -945,10 +960,10 @@ TR_CFG_RC tr_parse_config (TR_INSTANCE *tr, int n, struct dirent **cfg_files) {
     }
 
     if ((TR_CFG_SUCCESS != tr_cfg_parse_internal(tr->new_cfg, jcfg)) ||
-       (TR_CFG_SUCCESS != tr_cfg_parse_rp_clients(tr->new_cfg, jcfg)) ||
-       (TR_CFG_SUCCESS != tr_cfg_parse_idp_realms(tr->new_cfg, jcfg)) ||
-       (TR_CFG_SUCCESS != tr_cfg_parse_default_servers(tr->new_cfg, jcfg)) ||
-       (TR_CFG_SUCCESS != tr_cfg_parse_comms(tr->new_cfg, jcfg))) {
+        (TR_CFG_SUCCESS != tr_cfg_parse_rp_clients(tr->new_cfg, jcfg)) ||
+        (TR_CFG_SUCCESS != tr_cfg_parse_idp_realms(tr->new_cfg, jcfg)) ||
+        (TR_CFG_SUCCESS != tr_cfg_parse_default_servers(tr->new_cfg, jcfg)) ||
+        (TR_CFG_SUCCESS != tr_cfg_parse_comms(tr->new_cfg, jcfg))) {
       tr_cfg_free(tr->new_cfg);
       return TR_CFG_ERROR;
     }
@@ -1010,9 +1025,23 @@ TR_RP_CLIENT *tr_cfg_find_rp (TR_CFG *tr_cfg, TR_NAME *rp_gss, TR_CFG_RC *rc)
 static int is_cfg_file(const struct dirent *dent) {
   int n;
 
-  /* if the last four letters of the filename are .cfg, return true. */
-  if ((4 <= (n = strlen(dent->d_name))) &&
-      (0 == strcmp(&(dent->d_name[n-4]), ".cfg"))) {
+  /* Only accept filenames ending in ".cfg" and starting with a character
+   * other than an ASCII '.' */
+
+  /* filename must be at least 4 characters long to be acceptable */
+  n=strlen(dent->d_name);
+  if (n < 4) {
+    return 0;
+  }
+
+  /* filename must not start with '.' */
+  if ('.' == dent->d_name[0]) {
+    return 0;
+  }
+
+  /* If the above passed and the last four characters of the filename are .cfg, accept.
+   * (n.b., assumes an earlier test checked that the name is >= 4 chars long.) */
+  if (0 == strcmp(&(dent->d_name[n-4]), ".cfg")) {
     return 1;
   }
 
@@ -1020,26 +1049,43 @@ static int is_cfg_file(const struct dirent *dent) {
   return 0;
 }
 
-int tr_find_config_files (struct dirent ***cfg_files) {
+/* Find configuration files in a particular directory. Returns the
+ * number of entries found, 0 if none are found, or <0 for some
+ * errors. If n>=0, the cfg_files parameter will contain a newly
+ * allocated array of pointers to struct dirent entries, as returned
+ * by scandir(). These can be freed with tr_free_config_file_list().
+ */
+int tr_find_config_files (const char *config_dir, struct dirent ***cfg_files) {
   int n = 0, i = 0;
   
-  n = scandir(".", cfg_files, &is_cfg_file, 0);
+  n = scandir(config_dir, cfg_files, &is_cfg_file, 0);
 
   if (n < 0) {
     perror("scandir");
-    tr_debug("tr_find_config: scandir error.");
-    return 0;
-  }
-
-  if (n == 0) {
+    tr_debug("tr_find_config: scandir error trying to scan %s.", config_dir);
+  } else if (n == 0) {
     tr_debug("tr_find_config: No config files found.");
-    return 0;
+  } else {
+    i = n;
+    while(i--) {
+      tr_debug("tr_find_config: Config file found (%s).", (*cfg_files)[i]->d_name);
+    }
   }
 
-  i = n;
-  while(i--) {
-    tr_debug("tr_find_config: Config file found (%s).", (*cfg_files)[i]->d_name);
-  }
-    
   return n;
 }
+
+/* Free memory allocated for configuration file list returned from tr_find_config_files().
+ * This can be called regardless of the return value of tr_find_config_values(). */
+void tr_free_config_file_list(int n, struct dirent ***cfg_files) {
+  int ii;
+
+  /* if n < 0, then scandir did not allocate anything because it failed */
+  if((n>=0) && (*cfg_files != NULL)) {
+    for(ii=0; ii<n; ii++) {
+      free((*cfg_files)[ii]);
+    }
+    free(*cfg_files); /* safe even if n==0 */
+    *cfg_files=NULL; /* this will help prevent accidentally freeing twice */
+  }
+}
index 9d5dc11..dccf0d4 100644 (file)
@@ -82,9 +82,10 @@ int tr_name_cmp(TR_NAME *one, TR_NAME *two)
 {
   if (one->len != two->len)
     return 1;
-  else
-    /* TBD -- should really do a length-based comparison */
-    return strcmp(one->buf, two->buf);
+  else {
+    /* lengths equal */
+    return strncmp(one->buf, two->buf, one->len);
+  }
 }
 
 void tr_name_strlcat(char *dest, const TR_NAME *src, size_t len)
index fe559de..c391698 100755 (executable)
@@ -52,6 +52,8 @@
  * or implied warranty.
  */
 
+#include <signal.h>
+
 #include <gsscon.h>
 
 /* --------------------------------------------------------------------------- */
@@ -131,7 +133,14 @@ static int WriteBuffer (int         inSocket,
     if (!err) {
         const char *ptr = inBuffer;
         do {
-            ssize_t count = write (inSocket, ptr, inBufferLength - bytesWritten);
+            ssize_t count;
+
+            /* disable the SIGPIPE signal while we write so that we can handle a
+             * broken pipe error gracefully */
+            signal(SIGPIPE, SIG_IGN); /* temporarily disable */
+            count = write (inSocket, ptr, inBufferLength - bytesWritten);
+            signal(SIGPIPE, SIG_DFL); /* reenable */
+
             if (count < 0) {
                 /* Try again on EINTR */
                 if (errno != EINTR) { err = errno; }
@@ -142,7 +151,7 @@ static int WriteBuffer (int         inSocket,
         } while (!err && (bytesWritten < inBufferLength));
     } 
     
-    if (err) { gsscon_print_error (err, "WritBuffer failed"); }
+    if (err) { gsscon_print_error (err, "WriteBuffer failed"); }
 
     return err;
 }
index d369618..8a04415 100644 (file)
@@ -78,8 +78,9 @@ typedef struct tr_cfg {
   /* TBD -- Trust Links */
 } TR_CFG;
 
-int tr_find_config_files (struct dirent ***cfg_files);
-TR_CFG_RC tr_parse_config (TR_INSTANCE *tr, int n, struct dirent **cfg_files);
+int tr_find_config_files (const char *config_dir, struct dirent ***cfg_files);
+void tr_free_config_file_list(int n, struct dirent ***cfg_files);
+TR_CFG_RC tr_parse_config (TR_INSTANCE *tr, const char *config_dir, int n, struct dirent **cfg_files);
 TR_CFG_RC tr_apply_new_config (TR_INSTANCE *tr);
 TR_CFG_RC tr_cfg_validate (TR_CFG *trc);
 void tr_cfg_free(TR_CFG *cfg);
index 7050eb8..96e1d07 100644 (file)
 #include <stdlib.h>
 #include <stdio.h>
 #include <talloc.h>
+#include <argp.h>
 
 #include <gsscon.h>
 #include <tr_debug.h>
 #include <tid_internal.h>
 #include <trust_router/tr_dh.h>
 
-void static tidc_print_usage (const char *name)
-{
-  printf("Usage: %s <server> <RP-realm> <target-realm> <community> [<port>]\n", name);
-}
-
 static void tidc_resp_handler (TIDC_INSTANCE * tidc, 
                        TID_REQ *req,
                        TID_RESP *resp, 
@@ -86,18 +82,102 @@ static void tidc_resp_handler (TIDC_INSTANCE * tidc,
   return;
 }
 
+
+/* command-line option setup */
+
+/* argp global parameters */
+const char *argp_program_bug_address=PACKAGE_BUGREPORT; /* bug reporting address */
+
+/* doc strings */
+static const char doc[]=PACKAGE_NAME " - TID Client";
+static const char arg_doc[]="<server> <RP-realm> <target-realm> <community> [<port>]"; /* string describing arguments, if any */
+
+/* define the options here. Fields are:
+ * { long-name, short-name, variable name, options, help description } */
+static const struct argp_option cmdline_options[] = {
+  { NULL }
+};
+
+/* structure for communicating with option parser */
+struct cmdline_args {
+  char *server;
+  char *rp_realm;
+  char *target_realm;
+  char *community;
+  int port; /* optional */
+};
+
+/* parser for individual options - fills in a struct cmdline_args */
+static error_t parse_option(int key, char *arg, struct argp_state *state)
+{
+  /* get a shorthand to the command line argument structure, part of state */
+  struct cmdline_args *arguments=state->input;
+
+  switch (key) {
+  case ARGP_KEY_ARG: /* handle argument (not option) */
+    switch (state->arg_num) {
+    case 0:
+      arguments->server=arg;
+      break;
+
+    case 1:
+      arguments->rp_realm=arg;
+      break;
+
+    case 2:
+      arguments->target_realm=arg;
+      break;
+
+    case 3:
+      arguments->community=arg;
+      break;
+
+    case 4:
+      arguments->port=strtol(arg, NULL, 10); /* optional */
+      break;
+
+    default:
+      /* too many arguments */
+      argp_usage(state);
+    }
+    break;
+
+  case ARGP_KEY_END: /* no more arguments */
+    if (state->arg_num < 4) {
+      /* not enough arguments encountered */
+      argp_usage(state);
+    }
+    break;
+
+  default:
+    return ARGP_ERR_UNKNOWN;
+  }
+
+  return 0; /* success */
+}
+
+/* assemble the argp parser */
+static struct argp argp = {cmdline_options, parse_option, arg_doc, doc};
+
 int main (int argc, 
-         const char *argv[]) 
+          char *argv[]) 
 {
   TIDC_INSTANCE *tidc;
-  char *server = NULL;
-  char *rp_realm = NULL;
-  char *realm = NULL;
-  char *coi = NULL;
-  int port = TID_PORT;
   int conn = 0;
   int rc;
   gss_ctx_id_t gssctx;
+  struct cmdline_args opts;
+
+  /* parse the command line*/
+  /* set defaults */
+  opts.server=NULL;
+  opts.rp_realm=NULL;
+  opts.target_realm=NULL;
+  opts.community=NULL;
+  opts.port=TID_PORT;
+
+  argp_parse(&argp, argc, argv, 0, 0, &opts);
+  /* TBD -- validity checking, dealing with quotes, etc. */
 
   /* Use standalone logging */
   tr_log_open();
@@ -107,23 +187,7 @@ int main (int argc,
   tr_log_threshold(LOG_CRIT);
   tr_console_threshold(LOG_DEBUG);
 
-  /* Parse command-line arguments */ 
-  if (argc < 5 || argc > 6) {
-    tidc_print_usage(argv[0]);
-    exit(1);
-  }
-
-  /* TBD -- validity checking, dealing with quotes, etc. */
-  server = (char *)argv[1];
-  rp_realm = (char *) argv[2];
-  realm = (char *)argv[3];
-  coi = (char *)argv[4];
-
-  if (argc > 5) {
-    port = strtol(argv[5], NULL, 10);
-  }
-
-  printf("TIDC Client:\nServer = %s, rp_realm = %s, target_realm = %s, community = %s, port = %i\n", server, rp_realm, realm, coi, port);
+  printf("TIDC Client:\nServer = %s, rp_realm = %s, target_realm = %s, community = %s, port = %i\n", opts.server, opts.rp_realm, opts.target_realm, opts.community, opts.port);
  
   /* Create a TID client instance & the client DH */
   tidc = tidc_create();
@@ -133,14 +197,14 @@ int main (int argc,
   }
 
   /* Set-up TID connection */
-  if (-1 == (conn = tidc_open_connection(tidc, server, port, &gssctx))) {
+  if (-1 == (conn = tidc_open_connection(tidc, opts.server, opts.port, &gssctx))) {
     /* Handle error */
     printf("Error in tidc_open_connection.\n");
     return 1;
   };
 
   /* Send a TID request */
-  if (0 > (rc = tidc_send_request(tidc, conn, gssctx, rp_realm, realm, coi
+  if (0 > (rc = tidc_send_request(tidc, conn, gssctx, opts.rp_realm, opts.target_realm, opts.community
                                  &tidc_resp_handler, NULL))) {
     /* Handle error */
     printf("Error in tidc_send_request, rc = %d.\n", rc);
index f60ed0b..89b5d52 100644 (file)
@@ -37,6 +37,7 @@
 #include <stdlib.h>
 #include <talloc.h>
 #include <sqlite3.h>
+#include <argp.h>
 
 #include <tr_debug.h>
 #include <tid_internal.h>
@@ -255,29 +256,103 @@ static int tids_req_handler (TIDS_INSTANCE *tids,
 
   return s_keylen;
 }
+
 static int auth_handler(gss_name_t gss_name, TR_NAME *client,
                        void *expected_client)
 {
   TR_NAME *expected_client_trname = (TR_NAME*) expected_client;
-  return tr_name_cmp(client, expected_client_trname);
+  int result=tr_name_cmp(client, expected_client_trname);
+  if (result != 0) {
+    tr_notice("Auth denied for incorrect gss-name ('%.*s' requested, expected '%.*s').",
+              client->len, client->buf,
+              expected_client_trname->len, expected_client_trname->buf);
+  }
+  return result;
 }
 
+/* command-line option setup */
+
+/* argp global parameters */
+const char *argp_program_bug_address=PACKAGE_BUGREPORT; /* bug reporting address */
+
+/* doc strings */
+static const char doc[]=PACKAGE_NAME " - TID Server";
+static const char arg_doc[]="<ip-address> <gss-name> <hostname> <database-name>"; /* string describing arguments, if any */
+
+/* define the options here. Fields are:
+ * { long-name, short-name, variable name, options, help description } */
+static const struct argp_option cmdline_options[] = {
+  { NULL }
+};
+
+/* structure for communicating with option parser */
+struct cmdline_args {
+  char *ip_address;
+  char *gss_name;
+  char *hostname;
+  char *database_name;
+};
+
+/* parser for individual options - fills in a struct cmdline_args */
+static error_t parse_option(int key, char *arg, struct argp_state *state)
+{
+  /* get a shorthand to the command line argument structure, part of state */
+  struct cmdline_args *arguments=state->input;
+
+  switch (key) {
+  case ARGP_KEY_ARG: /* handle argument (not option) */
+    switch (state->arg_num) {
+    case 0:
+      arguments->ip_address=arg;
+      break;
+
+    case 1:
+      arguments->gss_name=arg;
+      break;
+
+    case 2:
+      arguments->hostname=arg;
+      break;
+
+    case 3:
+      arguments->database_name=arg;
+      break;
+
+    default:
+      /* too many arguments */
+      argp_usage(state);
+    }
+    break;
+
+  case ARGP_KEY_END: /* no more arguments */
+    if (state->arg_num < 4) {
+      /* not enough arguments encountered */
+      argp_usage(state);
+    }
+    break;
+
+  default:
+    return ARGP_ERR_UNKNOWN;
+  }
+
+  return 0; /* success */
+}
+
+/* assemble the argp parser */
+static struct argp argp = {cmdline_options, parse_option, arg_doc, doc};
 
 int main (int argc, 
-         const char *argv[]) 
+          char *argv[]) 
 {
   TIDS_INSTANCE *tids;
   int rc = 0;
-  char *ipaddr = NULL;
-  const char *hostname = NULL;
   TR_NAME *gssname = NULL;
+  struct cmdline_args opts={NULL};
+
+  /* parse the command line*/
+  argp_parse(&argp, argc, argv, 0, 0, &opts);
 
   talloc_set_log_stderr();
-  /* Parse command-line arguments */ 
-  if (argc != 5) {
-    fprintf(stdout, "Usage: %s <ip-address> <gss-name> <hostname> <database-name>\n", argv[0]);
-    exit(1);
-  }
 
   /* Use standalone logging */
   tr_log_open();
@@ -286,11 +361,9 @@ int main (int argc,
   tr_log_threshold(LOG_CRIT);
   tr_console_threshold(LOG_DEBUG);
 
-  ipaddr = (char *)argv[1];
-  gssname = tr_new_name((char *) argv[2]);
-  hostname = argv[3];
-  if (SQLITE_OK != sqlite3_open(argv[4], &db)) {
-    tr_crit("Error opening database %s", argv[4]);
+  gssname = tr_new_name(opts.gss_name);
+  if (SQLITE_OK != sqlite3_open(opts.database_name, &db)) {
+    tr_crit("Error opening database %s", opts.database_name);
     exit(1);
   }
   sqlite3_busy_timeout( db, 1000);
@@ -305,10 +378,10 @@ int main (int argc,
     return 1;
   }
 
-  tids->ipaddr = ipaddr;
+  tids->ipaddr = opts.ip_address;
 
   /* Start-up the server, won't return unless there is an error. */
-  rc = tids_start(tids, &tids_req_handler , auth_handler, hostname, TID_PORT, gssname);
+  rc = tids_start(tids, &tids_req_handler , auth_handler, opts.hostname, TID_PORT, gssname);
   
   tr_crit("Error in tids_start(), rc = %d. Exiting.", rc);
 
index 925ff62..eb081dd 100644 (file)
@@ -122,15 +122,24 @@ static int tids_listen (TIDS_INSTANCE *tids, int port)
     return conn;
 }
 
+/* returns EACCES if authorization is denied */
 static int tids_auth_cb(gss_name_t clientName, gss_buffer_t displayName,
                        void *data)
 {
   struct tids_instance *inst = (struct tids_instance *) data;
   TR_NAME name ={(char *) displayName->value,
                 displayName->length};
-  return inst->auth_handler(clientName, &name, inst->cookie);
+  int result=0;
+
+  if (0!=inst->auth_handler(clientName, &name, inst->cookie)) {
+    tr_debug("tids_auth_cb: client '%.*s' denied authorization.", name.len, name.buf);
+    result=EACCES; /* denied */
+  }
+
+  return result;
 }
 
+/* returns 0 on authorization success, 1 on failure, or -1 in case of error */
 static int tids_auth_connection (struct tids_instance *inst,
                                 int conn, gss_ctx_id_t *gssctx)
 {
@@ -363,6 +372,7 @@ TIDS_INSTANCE *tids_create (void)
   return tids;
 }
 
+/* Process tids requests forever. Should not return except on error. */
 int tids_start (TIDS_INSTANCE *tids, 
                TIDS_REQ_FUNC *req_handler,
                tids_auth_func *auth_handler,
@@ -402,7 +412,7 @@ int tids_start (TIDS_INSTANCE *tids,
       close(listen);
       tids_handle_connection(tids, conn);
       close(conn);
-      return 0;
+      exit(0); /* exit to kill forked child process */
     } else {
       close(conn);
     }
index 74aec0d..2590474 100644 (file)
@@ -35,6 +35,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <jansson.h>
+#include <argp.h>
 
 #include <tr.h>
 #include <tr_filter.h>
@@ -254,35 +255,108 @@ static int tr_tids_gss_handler(gss_name_t client_name, TR_NAME *gss_name,
   return 0;
 }
 
+/* Strip trailing / from a path name.*/
+static void remove_trailing_slash(char *s) {
+  size_t n;
 
-int main (int argc, const char *argv[])
+  n=strlen(s);
+  if(s[n-1]=='/') {
+    s[n-1]='\0';
+  }
+}
+
+/* command-line option setup */
+
+/* argp global parameters */
+const char *argp_program_bug_address=PACKAGE_BUGREPORT; /* bug reporting address */
+
+/* doc strings */
+static const char doc[]=PACKAGE_NAME " - Moonshot Trust Router";
+static const char arg_doc[]=""; /* string describing arguments, if any */
+
+/* define the options here. Fields are:
+ * { long-name, short-name, variable name, options, help description } */
+static const struct argp_option cmdline_options[] = {
+    { "config-dir", 'c', "DIR", 0, "Specify configuration file location (default is current directory)"},
+    { NULL }
+};
+
+/* structure for communicating with option parser */
+struct cmdline_args {
+  char *config_dir;
+};
+
+/* parser for individual options - fills in a struct cmdline_args */
+static error_t parse_option(int key, char *arg, struct argp_state *state)
+{
+  /* get a shorthand to the command line argument structure, part of state */
+  struct cmdline_args *arguments=state->input;
+
+  switch (key) {
+  case 'c':
+    if (arg == NULL) {
+      /* somehow we got called without an argument */
+      return ARGP_ERR_UNKNOWN;
+    }
+    arguments->config_dir=arg;
+    break;
+
+  default:
+    return ARGP_ERR_UNKNOWN;
+  }
+
+  return 0; /* success */
+}
+
+/* assemble the argp parser */
+static struct argp argp = {cmdline_options, parse_option, arg_doc, doc};
+
+
+int main (int argc, char *argv[])
 {
   TR_INSTANCE *tr = NULL;
   struct dirent **cfg_files = NULL;
   TR_CFG_RC rc = TR_CFG_SUCCESS;       /* presume success */
-  int err = 0, n = 0;;
-
-  /* parse command-line arguments? -- TBD */
+  int err = 0, n = 0;
+  struct cmdline_args opts;
 
   /* Use standalone logging */
   tr_log_open();
 
+  /* parse command-line arguments */
+  /* set defaults */
+  opts.config_dir=".";
+
+  /* parse the command line*/
+  argp_parse(&argp, argc, argv, 0, 0, &opts);
+
+  /* process options */
+  remove_trailing_slash(opts.config_dir);
+
   /* create a Trust Router instance */
   if (NULL == (tr = tr_create())) {
     tr_crit("Unable to create Trust Router instance, exiting.");
     return 1;
   }
 
-  /* find the configuration files */
-  if (0 == (n = tr_find_config_files(&cfg_files))) {
+  /* find the configuration files -- n.b., tr_find_config_files()
+   * allocates memory to cfg_files which we must later free */
+  tr_debug("Reading configuration files from %s/", opts.config_dir);
+  n = tr_find_config_files(opts.config_dir, &cfg_files);
+  if (n <= 0) {
     tr_crit("Can't locate configuration files, exiting.");
+    tr_free_config_file_list(n, &cfg_files);
     exit(1);
   }
 
-  if (TR_CFG_SUCCESS != tr_parse_config(tr, n, cfg_files)) {
+  if (TR_CFG_SUCCESS != tr_parse_config(tr, opts.config_dir, n, cfg_files)) {
     tr_crit("Error decoding configuration information, exiting.");
+    tr_free_config_file_list(n, &cfg_files);
     exit(1);
   }
+  
+  /* we are now done with the config filenames, free those */
+  tr_free_config_file_list(n, &cfg_files);
 
   /* apply initial configuration */
   if (TR_CFG_SUCCESS != (rc = tr_apply_new_config(tr))) {