<commit_before>/*
   Copyright 2013 The Trustees of Princeton University

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include "libsyndicate/libsyndicate.h"
#include "libsyndicate/storage.h"
#include "libsyndicate/opts.h"
#include "libsyndicate/private/opts.h"
#include "libsyndicate/ms/ms-client.h"
#include "libsyndicate/cache.h"
#include "libsyndicate/proc.h"

#define INI_MAX_LINE 4096
#define INI_STOP_ON_FIRST_ERROR 1

#include "ini.h"

static int md_conf_add_envar( struct md_syndicate_conf* conf, char const* keyvalue );

// stacktrace for uncaught C++ exceptions
void md_uncaught_exception_handler(void) {

   SG_error("%s", "UNCAUGHT EXCEPTION!  Stack trace follows\n");

   void *trace_elems[32];
   size_t p = 0;
   int trace_elem_count(backtrace( trace_elems, 32 ));

   char **stack_syms(backtrace_symbols( trace_elems, trace_elem_count ));

   for( int i = 0 ; i < trace_elem_count ; i++ ) {

       // use addr2line to convert addresses to line numbers.
       // find address.
       p = 0;
       while( stack_syms[i][p] != '(' && stack_syms[i][p] != ' ' && stack_syms[i][p] != '\0' ) {
          p++;
       }

       SG_error("        %s\n", stack_syms[i] );
   }

   SG_safe_free( stack_syms );

   exit(1);
}

// set the hostname
// return 0 on success
// return -ENOMEM if OOM
int md_set_hostname( struct md_syndicate_conf* conf, char const* hostname ) {

   char* new_hostname = SG_strdup_or_null( hostname );
   if( new_hostname == NULL ) {
      return -ENOMEM;
   }

   if( conf->hostname ) {
      SG_safe_free( conf->hostname );
   }

   conf->hostname = new_hostname;
   return 0;
}

// get the hostname
// return a duplicate of the hostname string on success
// return NULL on OOM, or if no host is defined
char* md_get_hostname( struct md_syndicate_conf* conf ) {

   return SG_strdup_or_null( conf->hostname );
}

// look up a gateway cert by ID
// return a pointer to the cert on success
// return NULL if not found
struct ms_gateway_cert* md_gateway_cert_find( ms_cert_bundle* gateway_certs, uint64_t gateway_id ) {

   ms_cert_bundle::iterator itr = gateway_certs->find( gateway_id );
   if( itr != gateway_certs->end() ) {

      return itr->second;
   }
   else {

      return NULL;
   }
}

// load a cert bundle, given the name of the gateway and volume
// return 0 on success, and populate gateway_certs
// return -ENOMEM on OOM
// return -ENOENT on missing certificate
// sets conf->cert_bundle_version
static int md_gateway_certs_load( struct md_syndicate_conf* conf, ms_cert_bundle* gateway_certs ) {

   int rc = 0;
   SG_messages::Manifest cert_bundle;

   rc = md_cert_bundle_load( conf->certs_path, conf->volume_name, &cert_bundle );
   if( rc != 0 ) {
      SG_error("md_cert_bundle_load('%s', '%s') rc = %d\n", conf->certs_path, conf->volume_name, rc );
      return rc;
   }

   // store config information
   conf->cert_bundle_version = cert_bundle.mtime_sec();

   // each gateway
   for( int i = 1; i < cert_bundle.blocks_size(); i++ ) {

      uint64_t gateway_id = cert_bundle.blocks(i).block_id();
      char gateway_id_buf[100];
      ms::ms_gateway_cert* gateway_cert = NULL;
      struct ms_gateway_cert* loaded_cert = NULL;

      sprintf( gateway_id_buf, "%" PRIu64, gateway_id );

      gateway_cert = SG_safe_new( ms::ms_gateway_cert() );
      if( gateway_cert == NULL ) {
         rc = -ENOMEM;
         goto md_gateway_certs_load_out;
      }

      rc = md_gateway_cert_load( conf->certs_path, gateway_id_buf, gateway_cert );
      if( rc != 0 ) {
         SG_error("md_gateway_cert_load('%s', %s) rc = %d\n", conf->certs_path, gateway_id_buf, rc );
         goto md_gateway_certs_load_out;
      }

      loaded_cert = SG_CALLOC( struct ms_gateway_cert, 1 );
      if( loaded_cert == NULL ) {
         rc = -ENOMEM;
         goto md_gateway_certs_load_out;
         return -ENOMEM;
      }

      rc = ms_client_gateway_cert_init( loaded_cert, conf->gateway, gateway_cert );
      if( rc != 0 ) {
         SG_error("ms_gateway_cert_init('%s') rc = %d\n", gateway_cert->name().c_str(), rc );
         goto md_gateway_certs_load_out;
      }

      rc = ms_client_cert_bundle_put( gateway_certs, loaded_cert );
      if( rc != 0 ) {
         SG_error("ms_client_cert_bundle_put('%s') rc = %d\n", gateway_cert->name().c_str(), rc );
         goto md_gateway_certs_load_out;
      }
   }

md_gateway_certs_load_out:
   if( rc != 0 ) {
      ms_client_cert_bundle_free( gateway_certs );
   }

   return rc;
}


// get the name of our syndicate instance
// return 0 on success, and set *ret_syndicate_name
// return -ENOMEM on OOM
// return -EINVAL on invalid syndicate host
static int md_syndicate_name( struct md_syndicate_conf* conf, char** ret_syndicate_name ) {

    int rc = 0;
    char* ms_host = NULL;
    int ms_port = -1;
    char* syndicate_name = NULL;

    rc = md_parse_hostname_portnum( conf->metadata_url, &ms_host, &ms_port );
    if( rc != 0 ) {
       return rc;
    }

    // name of syndicate instance
    syndicate_name = SG_CALLOC( char, strlen(ms_host) + 10 );
    if( syndicate_name == NULL ) {

        SG_safe_free( ms_host );
        return -ENOMEM;
    }

    if( ms_port <= 0 ) {
        // guess from protocol
        if( strstr( conf->metadata_url, "https://" ) != NULL ) {
            ms_port = 443;
        }
        else if( strstr( conf->metadata_url, "http://" ) != NULL ) {
            ms_port = 80;
        }
        else {

            SG_error("Invalid URL '%s'\n", conf->metadata_url );
            SG_safe_free( ms_host );
            return -EINVAL;
        }
    }

    sprintf( syndicate_name, "%s:%d", ms_host, ms_port );
    SG_safe_free( ms_host );

    *ret_syndicate_name = syndicate_name;
    return 0;
}


// Make sure that all the certs we need for this volume are downloaded and fresh.
// Use our helper programs to go and get them.
// The security of this method is dependent on having a secure way to get and validate *user* certificates.
// Once we can be guaranteed that we have the right user certs, then the right volume and gateway certs will follow.
// Return 0 on success, and populate conf with all volume, user, and gateway runtime information obtained from the certs.
// return -EPERM on validation error
// return -ENOMEM on OOM
int md_certs_reload( struct md_syndicate_conf* conf, EVP_PKEY** syndicate_pubkey, ms::ms_user_cert* user_cert, ms::ms_user_cert* volume_owner_cert, ms::ms_volume_metadata* volume_cert, ms_cert_bundle* gateway_certs ) {

   int rc = 0;
   int exit_status = 0;
   char* syndicate_name = NULL;
   char* syndicate_pubkey_pem = NULL;
   size_t syndicate_pubkey_pem_len = 0;
   ms::ms_gateway_cert gateway_cert;

   // use our certificate helper to go refresh the cached certificates
   char* certs_reload_args[] = {
      conf->certs_reload_helper,
      conf->config_file_path,
      conf->ms_username,
      conf->volume_name,
      conf->gateway_name,
      (char*)NULL
   };

   // go fetch
   rc = SG_proc_subprocess( conf->certs_reload_helper, certs_reload_args, conf->helper_env, NULL, 0, NULL, 0, 0, &exit_status );
   if( rc != 0 ) {

      SG_error("SG_proc_subprocess('%s') rc = %d\n", conf->certs_reload_helper, rc );
      return rc;
   }

   if( exit_status != 0 ) {
      SG_error("Subprocess '%s' exit code %d\n", conf->certs_reload_helper, exit_status );
      return -EPERM;
   }

   // load syndicate public key
   rc = md_syndicate_name( conf, &syndicate_name );
   if( rc != 0 ) {
      SG_error("md_syndicate_name('%s') rc = %d\n", conf->metadata_url, rc );
      return rc;
   }

   rc = md_syndicate_pubkey_load( conf->syndicate_path, syndicate_name, &syndicate_pubkey_pem, &syndicate_pubkey_pem_len );
   if( rc != 0 ) {
      SG_error("md_syndicate_pubkey_load('%s/%s') rc = %d\n", conf->syndicate_path, syndicate_name, rc );
      SG_safe_free( syndicate_name );
      return rc;
   }
   else {
      SG_safe_free( syndicate_name );
   }

   rc = md_load_pubkey( syndicate_pubkey, syndicate_pubkey_pem, syndicate_pubkey_pem_len );
   SG_safe_free( syndicate_pubkey_pem );

   if( rc != 0 ) {
      SG_error("%s", "Failed to parse Syndicate public key\n");
      return -EPERM;
   }

   // load our gateway cert
   rc = md_gateway_cert_load( conf->certs_path, conf->gateway_name, &gateway_cert );
   if( rc != 0 ) {
      SG_error("%s", "Failed to load our gateway certificate\n");
      return -EPERM;
   }

   conf->gateway = gateway_cert.gateway_id();

   // load our volume cert
   rc = md_volume_cert_load( conf->certs_path, conf->volume_name, volume_cert );
   if( rc != 0 ) {
      SG_error("%s", "Failed to load our volume certificate\n");
      return -EPERM;
   }

   // sanity checks...
   if( volume_cert->volume_id() != gateway_cert.volume_id() ) {
      SG_error("Volume/gateway cert mismatch: volume ID %" PRIu64 " != %" PRIu64 "\n", volume_cert->volume_id(), gateway_cert.volume_id() );
      return -EPERM;
   }

   // load our volume owner cert
   rc = md_user_cert_load( conf->certs_path, volume_cert->owner_email().c_str(), volume_owner_cert );
   if( rc != 0 ) {
      SG_error("md_user_cert_load('%s', '%s') rc = %d\n", conf->certs_path, volume_cert->owner_email().c_str(), rc );
      return -EPERM;
   }

   // sanity checks...
   if( volume_cert->owner_id() != volume_owner_cert->user_id() ) {
      SG_error("Volume/owner cert mismatch: volume owner ID %" PRIu64 " != %" PRIu64 "\n", volume_cert->owner_id(), volume_owner_cert->user_id() );
      return -EPERM;
   }

   // load our user cert
   rc = md_user_cert_load( conf->certs_path, conf->ms_username, user_cert );
   if( rc != 0 ) {
      SG_error("md_user_cert_load('%s', '%s') rc = %d\n", conf->certs_path, conf->ms_username, rc );
      return -EPERM;
   }

   // propagate config info
   conf->owner = gateway_cert.owner_id();
   conf->volume = volume_cert->volume_id();
   conf->gateway = gateway_cert.gateway_id();
   conf->gateway_type = gateway_cert.gateway_type();
   conf->volume_version = volume_cert->volume_version();
   conf->gateway_version = gateway_cert.version();
   conf->blocksize = volume_cert->blocksize();
   conf->portnum = gateway_cert.port();
   md_set_hostname( conf, gateway_cert.host().c_str() );

   // load each gateway certificate, as well as the cert bundle version
   rc = md_gateway_certs_load( conf, gateway_certs );
   if( rc != 0 ) {
      SG_error("md_gateway_certs_load rc = %d\n", rc);
      return -EPERM;
   }

   return 0;
}


// reload the gateway driver, validating it against the hash we have on file.
// return 0 on success, and store the driver to the given cert.
// return -EPERM on validation error
// return -ENOMEM on OOM
// return -ENOENT if there is no driver
int md_driver_reload( struct md_syndicate_conf* conf, struct ms_gateway_cert* cert ) {

   int rc = 0;
   char* driver_text = NULL;
   size_t driver_text_len = 0;
   int exit_status = 0;
   char driver_hash_str[ 2*SHA256_DIGEST_LENGTH + 1 ];

   // get the hash
   sha256_printable_buf( cert->driver_hash, driver_hash_str );

   // driver_reload args
   char* driver_reload_args[] = {
      conf->driver_reload_helper,
      conf->config_file_path,
      conf->volume_name,
      conf->gateway_name,
      (char*)NULL
   };

   // go fetch
   rc = SG_proc_subprocess( conf->driver_reload_helper, driver_reload_args, conf->helper_env, NULL, 0, NULL, 0, 0, &exit_status );
   if( rc != 0 ) {

      SG_error("SG_proc_subprocess('%s') rc = %d\n", conf->driver_reload_helper, rc );
      return rc;
   }

   if( exit_status != 0 ) {
      SG_error("Subprocess '%s' exit code %d\n", conf->driver_reload_helper, exit_status );
      return -EPERM;
   }

   // load up
   rc = md_driver_load( conf->certs_path, driver_hash_str, &driver_text, &driver_text_len );
   if( rc != 0 ) {
      SG_error("md_driver_load('%s', '%s') rc = %d\n", conf->certs_path, driver_hash_str, rc );
      return rc;
   }

   ms_client_gateway_cert_set_driver( cert, driver_text, driver_text_len );
   return rc;
}


// initialize fields in the config that cannot be loaded from command line options alone.
// (hence 'runtime_init' in the name).
// This includes downloading and loading all files, setting up local storage (if needed), setting up networking (if needed).
// return 0 on success, and populate the volume certificate and gateway certificates
// return -ENONET if we couldn't load a file requested by the config
// return negative if we couldn't initialize local storage, setup crypto, setup networking, or load a sensitive file securely.
// NOTE: if this fails, the caller must free the md_syndicate_conf structure's fields
static int md_runtime_init( struct md_syndicate_conf* c, EVP_PKEY** syndicate_pubkey, ms::ms_volume_metadata* volume_cert, ms_cert_bundle* gateway_certs ) {

   int rc = 0;
   struct mlock_buf gateway_pkey;

   char envar_user_id[100];
   char envar_volume_id[100];
   char envar_gateway_id[100];
   char envar_config_path[PATH_MAX+100];
   char envar_ipc_root[PATH_MAX+100];

   ms::ms_user_cert user_cert;
   ms::ms_user_cert volume_owner_cert;
   struct ms_gateway_cert* gateway_cert = NULL;         // our cert

   GOOGLE_PROTOBUF_VERIFY_VERSION;
   rc = curl_global_init( CURL_GLOBAL_ALL );

   if( rc != 0 ) {
      SG_error("curl_global_init rc = %d\n", rc );
      return rc;
   }

   rc = md_crypt_init();
   if( rc != 0 ) {
      SG_error("md_crypt_init rc = %d\n", rc );
      return rc;
   }

   // get the umask
   mode_t um = md_get_umask();
   c->usermask = um;

   // set up local storage directories
   rc = md_init_local_storage( c );
   if( rc != 0 ) {

      SG_error("md_init_local_storage rc = %d\n", rc );
      return rc;
   }

   SG_debug("Store local data at %s\n", c->data_root );

   // go fetch or revalidate our certs, and re-load versioning information
   rc = md_certs_reload( c, syndicate_pubkey, &user_cert, &volume_owner_cert, volume_cert, gateway_certs );
   if( rc != 0 ) {

      SG_error("md_certs_load rc = %d\n", rc );
      return rc;
   }

   // go fetch or revalidate our driver
   gateway_cert = md_gateway_cert_find( gateway_certs, c->gateway );
   if( gateway_cert == NULL ) {

      SG_error("BUG: no cert on file for us (%" PRIu64 ")\n", c->gateway );
      return -EPERM;
   }

   rc = md_driver_reload( c, gateway_cert );
   if( rc != 0 && rc != -ENOENT ) {

      SG_error("md_driver_reload rc = %d\n", rc );
      return rc;
   }

   // update our environment with the volume name, username, and gateway name
   rc = snprintf( envar_user_id, 100, "SYNDICATE_USER_ID=%" PRIu64, user_cert.user_id() );
   if( rc >= 100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_volume_id, 100, "SYNICATE_VOLUME_ID=%" PRIu64, volume_cert->volume_id() );
   if( rc >= 100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_gateway_id, 100, "SYNDICATE_GATEWAY_ID=%" PRIu64, c->gateway );
   if( rc >= 100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_config_path, PATH_MAX+100, "SYNDICATE_CONFIG_PATH=%s", c->config_file_path );
   if( rc >= PATH_MAX+100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_ipc_root, PATH_MAX+100, "SYNDICATE_IPC_PATH=%s/%" PRIu64 "", c->ipc_root, c->gateway );
   if( rc >= PATH_MAX+100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = md_conf_add_envar( c, envar_user_id );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_volume_id );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_gateway_id );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_config_path );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_ipc_root );
   if( rc != 0 ) {
      return rc;
   }

   // success!
   if( !c->is_client ) {

      // load gateway private key, if we need to
      SG_debug("Load private key for '%s' in '%s'\n", c->gateway_name, c->gateways_path );

      memset( &gateway_pkey, 0, sizeof(struct mlock_buf) );
      rc = md_gateway_private_key_load( c->gateways_path, c->gateway_name, &gateway_pkey );
      if( rc != 0 ) {

         SG_error("md_gateway_private_key_load('%s') rc = %d\n", c->gateway_name, rc );
         return rc;
      }

      // load it up
      c->gateway_key = (char*)gateway_pkey.ptr;
      c->gateway_key_len = gateway_pkey.len;

      memset( &gateway_pkey, 0, sizeof(struct mlock_buf) );

      // non-anonymous gateway has a user public key
      if( c->user_pubkey_pem != NULL ) {
         SG_safe_free( c->user_pubkey_pem );
      }

      // load user public key
      c->user_pubkey_pem = SG_strdup_or_null( user_cert.public_key().c_str() );
      c->user_pubkey_pem_len = user_cert.public_key().size();

      if( c->user_pubkey_pem == NULL ) {
         return -ENOMEM;
      }

      if( c->user_pubkey != NULL ) {
         EVP_PKEY_free( c->user_pubkey );
      }

      rc = md_load_pubkey( &c->user_pubkey, c->user_pubkey_pem, c->user_pubkey_pem_len );
      if( rc < 0 ) {

         SG_error("md_load_pubkey('%s') rc = %d\n", c->ms_username, rc );
         return rc;
      }

      c->owner = user_cert.user_id();

      // set hostname from our certificate
      if( c->content_url == NULL ) {
          SG_debug("Setting content URL to 'http://%s:%d/', from our certificate\n", ms_client_gateway_cert_hostname(gateway_cert), ms_client_gateway_cert_portnum(gateway_cert));
          md_set_hostname( c, ms_client_gateway_cert_hostname(gateway_cert) );
          c->portnum = c->portnum;

          if( c->content_url != NULL ) {
             SG_safe_free( c->content_url );
          }
          c->content_url = SG_CALLOC( char, strlen(c->hostname) + 20 );
          if( c->content_url == NULL ) {
             return -ENOMEM;
          }

          sprintf(c->content_url, "http://%s:%d/", c->hostname, c->portnum);
      }
   }
   else {

      // anonymous gateway
      c->owner = SG_USER_ANON;
   }

   // every volume has a public key
   if( c->volume_pubkey_pem != NULL ) {
      SG_safe_free( c->volume_pubkey_pem );
   }

   c->volume_pubkey_pem = SG_strdup_or_null( volume_owner_cert.public_key().c_str() );
   c->volume_pubkey_pem_len = volume_owner_cert.public_key().size();

   if( c->volume_pubkey_pem == NULL ) {
      return -ENOMEM;
   }

   if( c->volume_pubkey != NULL ) {
      EVP_PKEY_free( c->volume_pubkey );
      c->volume_pubkey = NULL;
   }

   rc = md_load_pubkey( &c->volume_pubkey, c->volume_pubkey_pem, c->volume_pubkey_pem_len );
   if( rc < 0 ) {

      SG_error("md_load_pubkey('%s') rc = %d\n", c->volume_name, rc );
      return rc;
   }

   return rc;
}


// if level >= 1, this turns on debug messages.
// if level >= 2, this turns on locking debug messages
int md_debug( struct md_syndicate_conf* conf, int level ) {
   md_set_debug_level( level );

   conf->debug_lock = false;
   if( level > SG_MAX_VERBOSITY ) {

      // debug locks as well
      conf->debug_lock = true;
   }

   return 0;
}

// if level >= 1, this turns on error messages
// always succeeds
int md_error( struct md_syndicate_conf* conf, int level ) {
   md_set_error_level( level );
   return 0;
}


// shut down the library.
// free all global data structures
// always succeeds
int md_shutdown() {

   // shut down protobufs
   google::protobuf::ShutdownProtobufLibrary();

   md_crypt_shutdown();

   curl_global_cleanup();
   return 0;
}

// read an long value
// return 0 on success, and set *ret to the parsed value
// return -EINVAL if it could not be parsed
long md_conf_parse_long( char const* value, long* ret ) {
   char *end = NULL;
   long val = strtol( value, &end, 10 );
   if( end == value ) {
      SG_error( "bad config line: '%s'\n", value );
      return -EINVAL;
   }
   else {
      *ret = val;
   }
   return 0;
}


// add an environment variable.
// keyvalue will be cloned, so the caller can free it
// return 0 on success
// return -ENOMEM on OOM
static int md_conf_add_envar( struct md_syndicate_conf* conf, char const* keyvalue ) {

    char* value_dup = strdup( keyvalue );
    if( value_dup == NULL ) {
       return -ENOMEM;
    }

    if( conf->num_helper_envs >= conf->max_helper_envs ) {
        if( conf->max_helper_envs == 0 ) {
           conf->max_helper_envs = 2;
        }
        else {
           conf->max_helper_envs *= 2;
        }

        char** new_helper_env = SG_CALLOC( char*, conf->max_helper_envs+1 );
        if( new_helper_env == NULL ) {
            SG_safe_free( value_dup );
            return -ENOMEM;
        }
        memcpy( new_helper_env, conf->helper_env, sizeof(char*) * conf->num_helper_envs );

        SG_safe_free( conf->helper_env );
        conf->helper_env = new_helper_env;
    }

    conf->helper_env[ conf->num_helper_envs ] = value_dup;
    conf->helper_env[ conf->num_helper_envs+1 ] = NULL;
    conf->num_helper_envs++;
    return 0;
}


// ini parser callback
// return 1 on success
// return <= 0 on failure
static int md_conf_ini_parser( void* userdata, char const* section, char const* key, char const* value ) {

   struct md_syndicate_conf* conf = (struct md_syndicate_conf*)userdata;
   int rc = 0;
   long val = 0;

   if( strcmp(section, "syndicate") == 0 ) {

      if( strcmp(key, SG_CONFIG_VOLUMES_PATH) == 0) {

         // path to volume information
         if( conf->volumes_path != NULL ) {
             SG_safe_free( conf->volumes_path );
         }

         conf->volumes_path = SG_strdup_or_null( value );
         if( conf->volumes_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_GATEWAYS_PATH) == 0 ) {

         // path to gateway information
         if( conf->gateways_path != NULL ) {
             SG_safe_free( conf->gateways_path );
         }

         conf->gateways_path = SG_strdup_or_null( value );
         if( conf->gateways_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_USERS_PATH) == 0 ) {

         // path to user information
         if( conf->users_path != NULL ) {
             SG_safe_free( conf->users_path );
         }

         conf->users_path = SG_strdup_or_null( value );
         if( conf->users_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_DRIVERS_PATH) == 0 ) {

         // path to driver storage
         if( conf->drivers_path != NULL ) {
             SG_safe_free( conf->drivers_path );
         }

         conf->drivers_path = SG_strdup_or_null( value );
         if( conf->drivers_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_SYNDICATE_PATH ) == 0 ) {

         // path to syndicate pubkeys
         if( conf->syndicate_path != NULL ) {
             SG_safe_free( conf->syndicate_path );
         }

         conf->syndicate_path = SG_strdup_or_null( value );
         if( conf->syndicate_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_CERTS_ROOT ) == 0 ) {

         // path to certs cache root
         if( conf->certs_root != NULL ) {
             SG_safe_free( conf->certs_root );
         }

         conf->certs_root = SG_strdup_or_null( value );
         if( conf->certs_root == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_IPC_ROOT ) == 0 ) {

         // path to IPC objects root
         if( conf->ipc_root != NULL ) {
            SG_safe_free( conf->ipc_root );
         }

         conf->ipc_root = SG_strdup_or_null( value );
         if( conf->ipc_root == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_MS_URL ) == 0 ) {
         // metadata publisher URL
         if( conf->metadata_url != NULL ) {
            SG_safe_free( conf->metadata_url );
         }

         conf->metadata_url = SG_strdup_or_null( value );
         if( conf->metadata_url == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_DATA_ROOT ) == 0 ) {
         // data root
         size_t len = strlen( value );
         if( len == 0 ) {
            return -EINVAL;
         }

         if( conf->data_root != NULL ) {
            SG_safe_free( conf->data_root );
         }

         if( value[len-1] != '/' ) {
            // must end in /
            conf->data_root = SG_CALLOC( char, len+2 );
            if( conf->data_root == NULL ) {
               return -ENOMEM;
            }

            sprintf( conf->data_root, "%s/", value );
         }
         else {

            conf->data_root = SG_strdup_or_null( value );
            if( conf->data_root == NULL ) {
               return -ENOMEM;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_LOGS_PATH ) == 0 ) {
         // logfile path
         if( conf->logs_path != NULL ) {
            SG_safe_free( conf->logs_path );
         }

         conf->logs_path = SG_strdup_or_null( value );
         if( conf->logs_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_MS_USERNAME ) == 0 ) {
         // metadata server username
         if( conf->ms_username != NULL ) {
            SG_safe_free( conf->ms_username );
         }

         conf->ms_username = SG_strdup_or_null( value );
         if( conf->ms_username == NULL ) {
            return -ENOMEM;
         }
      }

      else {

         SG_error("Unrecognized option '%s' in section '%s'\n", key, section);
         return 0;
      }
   }

   else if( strcmp(section, "helpers") == 0 ) {

      // helpers section
      if( strcmp( key, SG_CONFIG_ENVAR ) == 0 ) {

         rc = md_conf_add_envar( conf, value );
         if( rc != 0 ) {
            return rc;
         }
      }

      else if( strcmp( key, SG_CONFIG_CERTS_RELOAD_HELPER ) == 0 ) {

         if( conf->certs_reload_helper != NULL ) {
            SG_safe_free( conf->certs_reload_helper );
         }

         conf->certs_reload_helper = SG_strdup_or_null( value );
         if( conf->certs_reload_helper == NULL ) {
            return -ENOMEM;
         }

         rc = access( conf->certs_reload_helper, X_OK );
         if( rc < 0 ) {

            rc = -errno;
            SG_error("Cannot access '%s' as an executable, rc = %d\n", conf->certs_reload_helper, rc );
            return rc;
         }
      }

      else if( strcmp( key, SG_CONFIG_DRIVER_RELOAD_HELPER ) == 0 ) {

         if( conf->driver_reload_helper != NULL ) {
            SG_safe_free( conf->driver_reload_helper );
         }

         conf->driver_reload_helper = SG_strdup_or_null( value );
         if( conf->driver_reload_helper == NULL ) {
            return -ENOMEM;
         }

         rc = access( conf->driver_reload_helper, X_OK );
         if( rc < 0 ) {

            rc = -errno;
            SG_error("Cannot access '%s' as an executable, rc = %d\n", conf->driver_reload_helper, rc );
            return rc;
         }
      }
   }

   else if( strcmp(section, "gateway") == 0 ) {

      // have key, value.
      // what to do?
      if( strcmp( key, SG_CONFIG_DEFAULT_READ_FRESHNESS ) == 0 ) {
         // pull time interval
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->default_read_freshness = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_DEFAULT_WRITE_FRESHNESS ) == 0 ) {
         // pull time interval
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->default_write_freshness = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_CONNECT_TIMEOUT ) == 0 ) {
         // read timeout
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->connect_timeout = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_RELOAD_FREQUENCY ) == 0 ) {
         // view reload frequency
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->config_reload_freq = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_TLS_VERIFY_PEER ) == 0 ) {
         // verify peer?
         if( strcasecmp( value, "true" ) == 0 || strcasecmp( value, "yes" ) == 0 || strcasecmp( value, "y" ) == 0 ) {
            conf->verify_peer = 1;
         }
         else if( strcasecmp( value, "false" ) == 0 || strcasecmp( value, "no" ) == 0 || strcasecmp( value, "n" ) == 0 ) {
            conf->verify_peer = 0;
         }
         else {

            rc = md_conf_parse_long( value, &val );
            if( rc == 0 ) {
                conf->verify_peer = (val != 0);
            }
            else {
                return -EINVAL;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_GATHER_STATS ) == 0 ) {
         // gather statistics?
         if( strcasecmp( value, "true" ) == 0 || strcasecmp( value, "yes" ) == 0 || strcasecmp( value, "y" ) == 0 ) {
            conf->gather_stats = 1;
         }
         else if( strcasecmp( value, "false" ) == 0 || strcasecmp( value, "no" ) == 0 || strcasecmp( value, "n" ) == 0 ) {
            conf->gather_stats = 0;
         }
         else {

            rc = md_conf_parse_long( value, &val );
            if( rc == 0 ) {
                conf->gather_stats = (val != 0);
            }
            else {
                return -EINVAL;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_PUBLIC_URL ) == 0 ) {

         // public content URL
         size_t len = strlen( value );
         if( len == 0 ) {
            return -EINVAL;
         }

         if( conf->content_url != NULL ) {
            SG_safe_free( conf->content_url );
         }

         if( value[len-1] != '/' ) {
            // must end in /
            conf->content_url = SG_CALLOC( char, len+2 );
            if( conf->content_url == NULL ) {
               return -ENOMEM;
            }

            sprintf( conf->content_url, "%s/", value );
         }
         else {

            conf->content_url = SG_strdup_or_null( value );
            if( conf->content_url == NULL ) {
               return -ENOMEM;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_DEBUG_LEVEL ) == 0 ) {
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            md_debug( conf, (int)val );
         }
         else {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_DEBUG_LOCK ) == 0 ) {

         if( strcasecmp( value, "true" ) == 0 || strcasecmp( value, "yes" ) == 0 || strcasecmp( value, "y" ) == 0 ) {
            conf->debug_lock = 1;
         }
         else if( strcasecmp( value, "false" ) == 0 || strcasecmp( value, "no" ) == 0 || strcasecmp( value, "n" ) == 0 ) {
            conf->debug_lock = 0;
         }
         else {

            rc = md_conf_parse_long( value, &val );
            if( rc == 0 ) {
               conf->debug_lock = (val != 0 );
            }
            else {
               return -EINVAL;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_TRANSFER_TIMEOUT ) == 0 ) {
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->transfer_timeout = val;
         }<commit_msg>enable lock debugging with -d2<commit_after>/*
   Copyright 2013 The Trustees of Princeton University

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include "libsyndicate/libsyndicate.h"
#include "libsyndicate/storage.h"
#include "libsyndicate/opts.h"
#include "libsyndicate/private/opts.h"
#include "libsyndicate/ms/ms-client.h"
#include "libsyndicate/cache.h"
#include "libsyndicate/proc.h"

#define INI_MAX_LINE 4096
#define INI_STOP_ON_FIRST_ERROR 1

#include "ini.h"

static int md_conf_add_envar( struct md_syndicate_conf* conf, char const* keyvalue );

// stacktrace for uncaught C++ exceptions
void md_uncaught_exception_handler(void) {

   SG_error("%s", "UNCAUGHT EXCEPTION!  Stack trace follows\n");

   void *trace_elems[32];
   size_t p = 0;
   int trace_elem_count(backtrace( trace_elems, 32 ));

   char **stack_syms(backtrace_symbols( trace_elems, trace_elem_count ));

   for( int i = 0 ; i < trace_elem_count ; i++ ) {

       // use addr2line to convert addresses to line numbers.
       // find address.
       p = 0;
       while( stack_syms[i][p] != '(' && stack_syms[i][p] != ' ' && stack_syms[i][p] != '\0' ) {
          p++;
       }

       SG_error("        %s\n", stack_syms[i] );
   }

   SG_safe_free( stack_syms );

   exit(1);
}

// set the hostname
// return 0 on success
// return -ENOMEM if OOM
int md_set_hostname( struct md_syndicate_conf* conf, char const* hostname ) {

   char* new_hostname = SG_strdup_or_null( hostname );
   if( new_hostname == NULL ) {
      return -ENOMEM;
   }

   if( conf->hostname ) {
      SG_safe_free( conf->hostname );
   }

   conf->hostname = new_hostname;
   return 0;
}

// get the hostname
// return a duplicate of the hostname string on success
// return NULL on OOM, or if no host is defined
char* md_get_hostname( struct md_syndicate_conf* conf ) {

   return SG_strdup_or_null( conf->hostname );
}

// look up a gateway cert by ID
// return a pointer to the cert on success
// return NULL if not found
struct ms_gateway_cert* md_gateway_cert_find( ms_cert_bundle* gateway_certs, uint64_t gateway_id ) {

   ms_cert_bundle::iterator itr = gateway_certs->find( gateway_id );
   if( itr != gateway_certs->end() ) {

      return itr->second;
   }
   else {

      return NULL;
   }
}

// load a cert bundle, given the name of the gateway and volume
// return 0 on success, and populate gateway_certs
// return -ENOMEM on OOM
// return -ENOENT on missing certificate
// sets conf->cert_bundle_version
static int md_gateway_certs_load( struct md_syndicate_conf* conf, ms_cert_bundle* gateway_certs ) {

   int rc = 0;
   SG_messages::Manifest cert_bundle;

   rc = md_cert_bundle_load( conf->certs_path, conf->volume_name, &cert_bundle );
   if( rc != 0 ) {
      SG_error("md_cert_bundle_load('%s', '%s') rc = %d\n", conf->certs_path, conf->volume_name, rc );
      return rc;
   }

   // store config information
   conf->cert_bundle_version = cert_bundle.mtime_sec();

   // each gateway
   for( int i = 1; i < cert_bundle.blocks_size(); i++ ) {

      uint64_t gateway_id = cert_bundle.blocks(i).block_id();
      char gateway_id_buf[100];
      ms::ms_gateway_cert* gateway_cert = NULL;
      struct ms_gateway_cert* loaded_cert = NULL;

      sprintf( gateway_id_buf, "%" PRIu64, gateway_id );

      gateway_cert = SG_safe_new( ms::ms_gateway_cert() );
      if( gateway_cert == NULL ) {
         rc = -ENOMEM;
         goto md_gateway_certs_load_out;
      }

      rc = md_gateway_cert_load( conf->certs_path, gateway_id_buf, gateway_cert );
      if( rc != 0 ) {
         SG_error("md_gateway_cert_load('%s', %s) rc = %d\n", conf->certs_path, gateway_id_buf, rc );
         goto md_gateway_certs_load_out;
      }

      loaded_cert = SG_CALLOC( struct ms_gateway_cert, 1 );
      if( loaded_cert == NULL ) {
         rc = -ENOMEM;
         goto md_gateway_certs_load_out;
         return -ENOMEM;
      }

      rc = ms_client_gateway_cert_init( loaded_cert, conf->gateway, gateway_cert );
      if( rc != 0 ) {
         SG_error("ms_gateway_cert_init('%s') rc = %d\n", gateway_cert->name().c_str(), rc );
         goto md_gateway_certs_load_out;
      }

      rc = ms_client_cert_bundle_put( gateway_certs, loaded_cert );
      if( rc != 0 ) {
         SG_error("ms_client_cert_bundle_put('%s') rc = %d\n", gateway_cert->name().c_str(), rc );
         goto md_gateway_certs_load_out;
      }
   }

md_gateway_certs_load_out:
   if( rc != 0 ) {
      ms_client_cert_bundle_free( gateway_certs );
   }

   return rc;
}


// get the name of our syndicate instance
// return 0 on success, and set *ret_syndicate_name
// return -ENOMEM on OOM
// return -EINVAL on invalid syndicate host
static int md_syndicate_name( struct md_syndicate_conf* conf, char** ret_syndicate_name ) {

    int rc = 0;
    char* ms_host = NULL;
    int ms_port = -1;
    char* syndicate_name = NULL;

    rc = md_parse_hostname_portnum( conf->metadata_url, &ms_host, &ms_port );
    if( rc != 0 ) {
       return rc;
    }

    // name of syndicate instance
    syndicate_name = SG_CALLOC( char, strlen(ms_host) + 10 );
    if( syndicate_name == NULL ) {

        SG_safe_free( ms_host );
        return -ENOMEM;
    }

    if( ms_port <= 0 ) {
        // guess from protocol
        if( strstr( conf->metadata_url, "https://" ) != NULL ) {
            ms_port = 443;
        }
        else if( strstr( conf->metadata_url, "http://" ) != NULL ) {
            ms_port = 80;
        }
        else {

            SG_error("Invalid URL '%s'\n", conf->metadata_url );
            SG_safe_free( ms_host );
            return -EINVAL;
        }
    }

    sprintf( syndicate_name, "%s:%d", ms_host, ms_port );
    SG_safe_free( ms_host );

    *ret_syndicate_name = syndicate_name;
    return 0;
}


// Make sure that all the certs we need for this volume are downloaded and fresh.
// Use our helper programs to go and get them.
// The security of this method is dependent on having a secure way to get and validate *user* certificates.
// Once we can be guaranteed that we have the right user certs, then the right volume and gateway certs will follow.
// Return 0 on success, and populate conf with all volume, user, and gateway runtime information obtained from the certs.
// return -EPERM on validation error
// return -ENOMEM on OOM
int md_certs_reload( struct md_syndicate_conf* conf, EVP_PKEY** syndicate_pubkey, ms::ms_user_cert* user_cert, ms::ms_user_cert* volume_owner_cert, ms::ms_volume_metadata* volume_cert, ms_cert_bundle* gateway_certs ) {

   int rc = 0;
   int exit_status = 0;
   char* syndicate_name = NULL;
   char* syndicate_pubkey_pem = NULL;
   size_t syndicate_pubkey_pem_len = 0;
   ms::ms_gateway_cert gateway_cert;

   // use our certificate helper to go refresh the cached certificates
   char* certs_reload_args[] = {
      conf->certs_reload_helper,
      conf->config_file_path,
      conf->ms_username,
      conf->volume_name,
      conf->gateway_name,
      (char*)NULL
   };

   // go fetch
   rc = SG_proc_subprocess( conf->certs_reload_helper, certs_reload_args, conf->helper_env, NULL, 0, NULL, 0, 0, &exit_status );
   if( rc != 0 ) {

      SG_error("SG_proc_subprocess('%s') rc = %d\n", conf->certs_reload_helper, rc );
      return rc;
   }

   if( exit_status != 0 ) {
      SG_error("Subprocess '%s' exit code %d\n", conf->certs_reload_helper, exit_status );
      return -EPERM;
   }

   // load syndicate public key
   rc = md_syndicate_name( conf, &syndicate_name );
   if( rc != 0 ) {
      SG_error("md_syndicate_name('%s') rc = %d\n", conf->metadata_url, rc );
      return rc;
   }

   rc = md_syndicate_pubkey_load( conf->syndicate_path, syndicate_name, &syndicate_pubkey_pem, &syndicate_pubkey_pem_len );
   if( rc != 0 ) {
      SG_error("md_syndicate_pubkey_load('%s/%s') rc = %d\n", conf->syndicate_path, syndicate_name, rc );
      SG_safe_free( syndicate_name );
      return rc;
   }
   else {
      SG_safe_free( syndicate_name );
   }

   rc = md_load_pubkey( syndicate_pubkey, syndicate_pubkey_pem, syndicate_pubkey_pem_len );
   SG_safe_free( syndicate_pubkey_pem );

   if( rc != 0 ) {
      SG_error("%s", "Failed to parse Syndicate public key\n");
      return -EPERM;
   }

   // load our gateway cert
   rc = md_gateway_cert_load( conf->certs_path, conf->gateway_name, &gateway_cert );
   if( rc != 0 ) {
      SG_error("%s", "Failed to load our gateway certificate\n");
      return -EPERM;
   }

   conf->gateway = gateway_cert.gateway_id();

   // load our volume cert
   rc = md_volume_cert_load( conf->certs_path, conf->volume_name, volume_cert );
   if( rc != 0 ) {
      SG_error("%s", "Failed to load our volume certificate\n");
      return -EPERM;
   }

   // sanity checks...
   if( volume_cert->volume_id() != gateway_cert.volume_id() ) {
      SG_error("Volume/gateway cert mismatch: volume ID %" PRIu64 " != %" PRIu64 "\n", volume_cert->volume_id(), gateway_cert.volume_id() );
      return -EPERM;
   }

   // load our volume owner cert
   rc = md_user_cert_load( conf->certs_path, volume_cert->owner_email().c_str(), volume_owner_cert );
   if( rc != 0 ) {
      SG_error("md_user_cert_load('%s', '%s') rc = %d\n", conf->certs_path, volume_cert->owner_email().c_str(), rc );
      return -EPERM;
   }

   // sanity checks...
   if( volume_cert->owner_id() != volume_owner_cert->user_id() ) {
      SG_error("Volume/owner cert mismatch: volume owner ID %" PRIu64 " != %" PRIu64 "\n", volume_cert->owner_id(), volume_owner_cert->user_id() );
      return -EPERM;
   }

   // load our user cert
   rc = md_user_cert_load( conf->certs_path, conf->ms_username, user_cert );
   if( rc != 0 ) {
      SG_error("md_user_cert_load('%s', '%s') rc = %d\n", conf->certs_path, conf->ms_username, rc );
      return -EPERM;
   }

   // propagate config info
   conf->owner = gateway_cert.owner_id();
   conf->volume = volume_cert->volume_id();
   conf->gateway = gateway_cert.gateway_id();
   conf->gateway_type = gateway_cert.gateway_type();
   conf->volume_version = volume_cert->volume_version();
   conf->gateway_version = gateway_cert.version();
   conf->blocksize = volume_cert->blocksize();
   conf->portnum = gateway_cert.port();
   md_set_hostname( conf, gateway_cert.host().c_str() );

   // load each gateway certificate, as well as the cert bundle version
   rc = md_gateway_certs_load( conf, gateway_certs );
   if( rc != 0 ) {
      SG_error("md_gateway_certs_load rc = %d\n", rc);
      return -EPERM;
   }

   return 0;
}


// reload the gateway driver, validating it against the hash we have on file.
// return 0 on success, and store the driver to the given cert.
// return -EPERM on validation error
// return -ENOMEM on OOM
// return -ENOENT if there is no driver
int md_driver_reload( struct md_syndicate_conf* conf, struct ms_gateway_cert* cert ) {

   int rc = 0;
   char* driver_text = NULL;
   size_t driver_text_len = 0;
   int exit_status = 0;
   char driver_hash_str[ 2*SHA256_DIGEST_LENGTH + 1 ];

   // get the hash
   sha256_printable_buf( cert->driver_hash, driver_hash_str );

   // driver_reload args
   char* driver_reload_args[] = {
      conf->driver_reload_helper,
      conf->config_file_path,
      conf->volume_name,
      conf->gateway_name,
      (char*)NULL
   };

   // go fetch
   rc = SG_proc_subprocess( conf->driver_reload_helper, driver_reload_args, conf->helper_env, NULL, 0, NULL, 0, 0, &exit_status );
   if( rc != 0 ) {

      SG_error("SG_proc_subprocess('%s') rc = %d\n", conf->driver_reload_helper, rc );
      return rc;
   }

   if( exit_status != 0 ) {
      SG_error("Subprocess '%s' exit code %d\n", conf->driver_reload_helper, exit_status );
      return -EPERM;
   }

   // load up
   rc = md_driver_load( conf->certs_path, driver_hash_str, &driver_text, &driver_text_len );
   if( rc != 0 ) {
      SG_error("md_driver_load('%s', '%s') rc = %d\n", conf->certs_path, driver_hash_str, rc );
      return rc;
   }

   ms_client_gateway_cert_set_driver( cert, driver_text, driver_text_len );
   return rc;
}


// initialize fields in the config that cannot be loaded from command line options alone.
// (hence 'runtime_init' in the name).
// This includes downloading and loading all files, setting up local storage (if needed), setting up networking (if needed).
// return 0 on success, and populate the volume certificate and gateway certificates
// return -ENONET if we couldn't load a file requested by the config
// return negative if we couldn't initialize local storage, setup crypto, setup networking, or load a sensitive file securely.
// NOTE: if this fails, the caller must free the md_syndicate_conf structure's fields
static int md_runtime_init( struct md_syndicate_conf* c, EVP_PKEY** syndicate_pubkey, ms::ms_volume_metadata* volume_cert, ms_cert_bundle* gateway_certs ) {

   int rc = 0;
   struct mlock_buf gateway_pkey;

   char envar_user_id[100];
   char envar_volume_id[100];
   char envar_gateway_id[100];
   char envar_config_path[PATH_MAX+100];
   char envar_ipc_root[PATH_MAX+100];

   ms::ms_user_cert user_cert;
   ms::ms_user_cert volume_owner_cert;
   struct ms_gateway_cert* gateway_cert = NULL;         // our cert

   GOOGLE_PROTOBUF_VERIFY_VERSION;
   rc = curl_global_init( CURL_GLOBAL_ALL );

   if( rc != 0 ) {
      SG_error("curl_global_init rc = %d\n", rc );
      return rc;
   }

   rc = md_crypt_init();
   if( rc != 0 ) {
      SG_error("md_crypt_init rc = %d\n", rc );
      return rc;
   }

   // get the umask
   mode_t um = md_get_umask();
   c->usermask = um;

   // set up local storage directories
   rc = md_init_local_storage( c );
   if( rc != 0 ) {

      SG_error("md_init_local_storage rc = %d\n", rc );
      return rc;
   }

   SG_debug("Store local data at %s\n", c->data_root );

   // go fetch or revalidate our certs, and re-load versioning information
   rc = md_certs_reload( c, syndicate_pubkey, &user_cert, &volume_owner_cert, volume_cert, gateway_certs );
   if( rc != 0 ) {

      SG_error("md_certs_load rc = %d\n", rc );
      return rc;
   }

   // go fetch or revalidate our driver
   gateway_cert = md_gateway_cert_find( gateway_certs, c->gateway );
   if( gateway_cert == NULL ) {

      SG_error("BUG: no cert on file for us (%" PRIu64 ")\n", c->gateway );
      return -EPERM;
   }

   rc = md_driver_reload( c, gateway_cert );
   if( rc != 0 && rc != -ENOENT ) {

      SG_error("md_driver_reload rc = %d\n", rc );
      return rc;
   }

   // update our environment with the volume name, username, and gateway name
   rc = snprintf( envar_user_id, 100, "SYNDICATE_USER_ID=%" PRIu64, user_cert.user_id() );
   if( rc >= 100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_volume_id, 100, "SYNICATE_VOLUME_ID=%" PRIu64, volume_cert->volume_id() );
   if( rc >= 100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_gateway_id, 100, "SYNDICATE_GATEWAY_ID=%" PRIu64, c->gateway );
   if( rc >= 100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_config_path, PATH_MAX+100, "SYNDICATE_CONFIG_PATH=%s", c->config_file_path );
   if( rc >= PATH_MAX+100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = snprintf( envar_ipc_root, PATH_MAX+100, "SYNDICATE_IPC_PATH=%s/%" PRIu64 "", c->ipc_root, c->gateway );
   if( rc >= PATH_MAX+100 ) {
      SG_error("%s", "FATAL: overflow\n");
      exit(1);
   }

   rc = md_conf_add_envar( c, envar_user_id );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_volume_id );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_gateway_id );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_config_path );
   if( rc != 0 ) {
      return rc;
   }

   rc = md_conf_add_envar( c, envar_ipc_root );
   if( rc != 0 ) {
      return rc;
   }

   // success!
   if( !c->is_client ) {

      // load gateway private key, if we need to
      SG_debug("Load private key for '%s' in '%s'\n", c->gateway_name, c->gateways_path );

      memset( &gateway_pkey, 0, sizeof(struct mlock_buf) );
      rc = md_gateway_private_key_load( c->gateways_path, c->gateway_name, &gateway_pkey );
      if( rc != 0 ) {

         SG_error("md_gateway_private_key_load('%s') rc = %d\n", c->gateway_name, rc );
         return rc;
      }

      // load it up
      c->gateway_key = (char*)gateway_pkey.ptr;
      c->gateway_key_len = gateway_pkey.len;

      memset( &gateway_pkey, 0, sizeof(struct mlock_buf) );

      // non-anonymous gateway has a user public key
      if( c->user_pubkey_pem != NULL ) {
         SG_safe_free( c->user_pubkey_pem );
      }

      // load user public key
      c->user_pubkey_pem = SG_strdup_or_null( user_cert.public_key().c_str() );
      c->user_pubkey_pem_len = user_cert.public_key().size();

      if( c->user_pubkey_pem == NULL ) {
         return -ENOMEM;
      }

      if( c->user_pubkey != NULL ) {
         EVP_PKEY_free( c->user_pubkey );
      }

      rc = md_load_pubkey( &c->user_pubkey, c->user_pubkey_pem, c->user_pubkey_pem_len );
      if( rc < 0 ) {

         SG_error("md_load_pubkey('%s') rc = %d\n", c->ms_username, rc );
         return rc;
      }

      c->owner = user_cert.user_id();

      // set hostname from our certificate
      if( c->content_url == NULL ) {
          SG_debug("Setting content URL to 'http://%s:%d/', from our certificate\n", ms_client_gateway_cert_hostname(gateway_cert), ms_client_gateway_cert_portnum(gateway_cert));
          md_set_hostname( c, ms_client_gateway_cert_hostname(gateway_cert) );
          c->portnum = c->portnum;

          if( c->content_url != NULL ) {
             SG_safe_free( c->content_url );
          }
          c->content_url = SG_CALLOC( char, strlen(c->hostname) + 20 );
          if( c->content_url == NULL ) {
             return -ENOMEM;
          }

          sprintf(c->content_url, "http://%s:%d/", c->hostname, c->portnum);
      }
   }
   else {

      // anonymous gateway
      c->owner = SG_USER_ANON;
   }

   // every volume has a public key
   if( c->volume_pubkey_pem != NULL ) {
      SG_safe_free( c->volume_pubkey_pem );
   }

   c->volume_pubkey_pem = SG_strdup_or_null( volume_owner_cert.public_key().c_str() );
   c->volume_pubkey_pem_len = volume_owner_cert.public_key().size();

   if( c->volume_pubkey_pem == NULL ) {
      return -ENOMEM;
   }

   if( c->volume_pubkey != NULL ) {
      EVP_PKEY_free( c->volume_pubkey );
      c->volume_pubkey = NULL;
   }

   rc = md_load_pubkey( &c->volume_pubkey, c->volume_pubkey_pem, c->volume_pubkey_pem_len );
   if( rc < 0 ) {

      SG_error("md_load_pubkey('%s') rc = %d\n", c->volume_name, rc );
      return rc;
   }

   return rc;
}


// if level >= 1, this turns on debug messages.
// if level >= 2, this turns on locking debug messages
int md_debug( struct md_syndicate_conf* conf, int level ) {
   md_set_debug_level( level );

   conf->debug_lock = false;
   if( level >= SG_MAX_VERBOSITY ) {

      // debug locks as well
      conf->debug_lock = true;
   }

   return 0;
}

// if level >= 1, this turns on error messages
// always succeeds
int md_error( struct md_syndicate_conf* conf, int level ) {
   md_set_error_level( level );
   return 0;
}


// shut down the library.
// free all global data structures
// always succeeds
int md_shutdown() {

   // shut down protobufs
   google::protobuf::ShutdownProtobufLibrary();

   md_crypt_shutdown();

   curl_global_cleanup();
   return 0;
}

// read an long value
// return 0 on success, and set *ret to the parsed value
// return -EINVAL if it could not be parsed
long md_conf_parse_long( char const* value, long* ret ) {
   char *end = NULL;
   long val = strtol( value, &end, 10 );
   if( end == value ) {
      SG_error( "bad config line: '%s'\n", value );
      return -EINVAL;
   }
   else {
      *ret = val;
   }
   return 0;
}


// add an environment variable.
// keyvalue will be cloned, so the caller can free it
// return 0 on success
// return -ENOMEM on OOM
static int md_conf_add_envar( struct md_syndicate_conf* conf, char const* keyvalue ) {

    char* value_dup = strdup( keyvalue );
    if( value_dup == NULL ) {
       return -ENOMEM;
    }

    if( conf->num_helper_envs >= conf->max_helper_envs ) {
        if( conf->max_helper_envs == 0 ) {
           conf->max_helper_envs = 2;
        }
        else {
           conf->max_helper_envs *= 2;
        }

        char** new_helper_env = SG_CALLOC( char*, conf->max_helper_envs+1 );
        if( new_helper_env == NULL ) {
            SG_safe_free( value_dup );
            return -ENOMEM;
        }
        memcpy( new_helper_env, conf->helper_env, sizeof(char*) * conf->num_helper_envs );

        SG_safe_free( conf->helper_env );
        conf->helper_env = new_helper_env;
    }

    conf->helper_env[ conf->num_helper_envs ] = value_dup;
    conf->helper_env[ conf->num_helper_envs+1 ] = NULL;
    conf->num_helper_envs++;
    return 0;
}


// ini parser callback
// return 1 on success
// return <= 0 on failure
static int md_conf_ini_parser( void* userdata, char const* section, char const* key, char const* value ) {

   struct md_syndicate_conf* conf = (struct md_syndicate_conf*)userdata;
   int rc = 0;
   long val = 0;

   if( strcmp(section, "syndicate") == 0 ) {

      if( strcmp(key, SG_CONFIG_VOLUMES_PATH) == 0) {

         // path to volume information
         if( conf->volumes_path != NULL ) {
             SG_safe_free( conf->volumes_path );
         }

         conf->volumes_path = SG_strdup_or_null( value );
         if( conf->volumes_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_GATEWAYS_PATH) == 0 ) {

         // path to gateway information
         if( conf->gateways_path != NULL ) {
             SG_safe_free( conf->gateways_path );
         }

         conf->gateways_path = SG_strdup_or_null( value );
         if( conf->gateways_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_USERS_PATH) == 0 ) {

         // path to user information
         if( conf->users_path != NULL ) {
             SG_safe_free( conf->users_path );
         }

         conf->users_path = SG_strdup_or_null( value );
         if( conf->users_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_DRIVERS_PATH) == 0 ) {

         // path to driver storage
         if( conf->drivers_path != NULL ) {
             SG_safe_free( conf->drivers_path );
         }

         conf->drivers_path = SG_strdup_or_null( value );
         if( conf->drivers_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_SYNDICATE_PATH ) == 0 ) {

         // path to syndicate pubkeys
         if( conf->syndicate_path != NULL ) {
             SG_safe_free( conf->syndicate_path );
         }

         conf->syndicate_path = SG_strdup_or_null( value );
         if( conf->syndicate_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_CERTS_ROOT ) == 0 ) {

         // path to certs cache root
         if( conf->certs_root != NULL ) {
             SG_safe_free( conf->certs_root );
         }

         conf->certs_root = SG_strdup_or_null( value );
         if( conf->certs_root == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_IPC_ROOT ) == 0 ) {

         // path to IPC objects root
         if( conf->ipc_root != NULL ) {
            SG_safe_free( conf->ipc_root );
         }

         conf->ipc_root = SG_strdup_or_null( value );
         if( conf->ipc_root == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_MS_URL ) == 0 ) {
         // metadata publisher URL
         if( conf->metadata_url != NULL ) {
            SG_safe_free( conf->metadata_url );
         }

         conf->metadata_url = SG_strdup_or_null( value );
         if( conf->metadata_url == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_DATA_ROOT ) == 0 ) {
         // data root
         size_t len = strlen( value );
         if( len == 0 ) {
            return -EINVAL;
         }

         if( conf->data_root != NULL ) {
            SG_safe_free( conf->data_root );
         }

         if( value[len-1] != '/' ) {
            // must end in /
            conf->data_root = SG_CALLOC( char, len+2 );
            if( conf->data_root == NULL ) {
               return -ENOMEM;
            }

            sprintf( conf->data_root, "%s/", value );
         }
         else {

            conf->data_root = SG_strdup_or_null( value );
            if( conf->data_root == NULL ) {
               return -ENOMEM;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_LOGS_PATH ) == 0 ) {
         // logfile path
         if( conf->logs_path != NULL ) {
            SG_safe_free( conf->logs_path );
         }

         conf->logs_path = SG_strdup_or_null( value );
         if( conf->logs_path == NULL ) {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_MS_USERNAME ) == 0 ) {
         // metadata server username
         if( conf->ms_username != NULL ) {
            SG_safe_free( conf->ms_username );
         }

         conf->ms_username = SG_strdup_or_null( value );
         if( conf->ms_username == NULL ) {
            return -ENOMEM;
         }
      }

      else {

         SG_error("Unrecognized option '%s' in section '%s'\n", key, section);
         return 0;
      }
   }

   else if( strcmp(section, "helpers") == 0 ) {

      // helpers section
      if( strcmp( key, SG_CONFIG_ENVAR ) == 0 ) {

         rc = md_conf_add_envar( conf, value );
         if( rc != 0 ) {
            return rc;
         }
      }

      else if( strcmp( key, SG_CONFIG_CERTS_RELOAD_HELPER ) == 0 ) {

         if( conf->certs_reload_helper != NULL ) {
            SG_safe_free( conf->certs_reload_helper );
         }

         conf->certs_reload_helper = SG_strdup_or_null( value );
         if( conf->certs_reload_helper == NULL ) {
            return -ENOMEM;
         }

         rc = access( conf->certs_reload_helper, X_OK );
         if( rc < 0 ) {

            rc = -errno;
            SG_error("Cannot access '%s' as an executable, rc = %d\n", conf->certs_reload_helper, rc );
            return rc;
         }
      }

      else if( strcmp( key, SG_CONFIG_DRIVER_RELOAD_HELPER ) == 0 ) {

         if( conf->driver_reload_helper != NULL ) {
            SG_safe_free( conf->driver_reload_helper );
         }

         conf->driver_reload_helper = SG_strdup_or_null( value );
         if( conf->driver_reload_helper == NULL ) {
            return -ENOMEM;
         }

         rc = access( conf->driver_reload_helper, X_OK );
         if( rc < 0 ) {

            rc = -errno;
            SG_error("Cannot access '%s' as an executable, rc = %d\n", conf->driver_reload_helper, rc );
            return rc;
         }
      }
   }

   else if( strcmp(section, "gateway") == 0 ) {

      // have key, value.
      // what to do?
      if( strcmp( key, SG_CONFIG_DEFAULT_READ_FRESHNESS ) == 0 ) {
         // pull time interval
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->default_read_freshness = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_DEFAULT_WRITE_FRESHNESS ) == 0 ) {
         // pull time interval
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->default_write_freshness = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_CONNECT_TIMEOUT ) == 0 ) {
         // read timeout
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->connect_timeout = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_RELOAD_FREQUENCY ) == 0 ) {
         // view reload frequency
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->config_reload_freq = val;
         }
         else {
            return -EINVAL;
         }
      }

      else if( strcmp( key, SG_CONFIG_TLS_VERIFY_PEER ) == 0 ) {
         // verify peer?
         if( strcasecmp( value, "true" ) == 0 || strcasecmp( value, "yes" ) == 0 || strcasecmp( value, "y" ) == 0 ) {
            conf->verify_peer = 1;
         }
         else if( strcasecmp( value, "false" ) == 0 || strcasecmp( value, "no" ) == 0 || strcasecmp( value, "n" ) == 0 ) {
            conf->verify_peer = 0;
         }
         else {

            rc = md_conf_parse_long( value, &val );
            if( rc == 0 ) {
                conf->verify_peer = (val != 0);
            }
            else {
                return -EINVAL;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_GATHER_STATS ) == 0 ) {
         // gather statistics?
         if( strcasecmp( value, "true" ) == 0 || strcasecmp( value, "yes" ) == 0 || strcasecmp( value, "y" ) == 0 ) {
            conf->gather_stats = 1;
         }
         else if( strcasecmp( value, "false" ) == 0 || strcasecmp( value, "no" ) == 0 || strcasecmp( value, "n" ) == 0 ) {
            conf->gather_stats = 0;
         }
         else {

            rc = md_conf_parse_long( value, &val );
            if( rc == 0 ) {
                conf->gather_stats = (val != 0);
            }
            else {
                return -EINVAL;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_PUBLIC_URL ) == 0 ) {

         // public content URL
         size_t len = strlen( value );
         if( len == 0 ) {
            return -EINVAL;
         }

         if( conf->content_url != NULL ) {
            SG_safe_free( conf->content_url );
         }

         if( value[len-1] != '/' ) {
            // must end in /
            conf->content_url = SG_CALLOC( char, len+2 );
            if( conf->content_url == NULL ) {
               return -ENOMEM;
            }

            sprintf( conf->content_url, "%s/", value );
         }
         else {

            conf->content_url = SG_strdup_or_null( value );
            if( conf->content_url == NULL ) {
               return -ENOMEM;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_DEBUG_LEVEL ) == 0 ) {
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            md_debug( conf, (int)val );
         }
         else {
            return -ENOMEM;
         }
      }

      else if( strcmp( key, SG_CONFIG_DEBUG_LOCK ) == 0 ) {

         if( strcasecmp( value, "true" ) == 0 || strcasecmp( value, "yes" ) == 0 || strcasecmp( value, "y" ) == 0 ) {
            conf->debug_lock = true;
         }
         else if( strcasecmp( value, "false" ) == 0 || strcasecmp( value, "no" ) == 0 || strcasecmp( value, "n" ) == 0 ) {
            conf->debug_lock = false;
         }
         else {

            rc = md_conf_parse_long( value, &val );
            if( rc == 0 ) {
               conf->debug_lock = (val != 0 );
            }
            else {
               return -EINVAL;
            }
         }
      }

      else if( strcmp( key, SG_CONFIG_TRANSFER_TIMEOUT ) == 0 ) {
         rc = md_conf_parse_long( value, &val );
         if( rc == 0 ) {
            conf->transfer_timeout = val;
         }
{'fraction_non_alphanumeric': 0.1251384447390999, 'fraction_numerical': 0.00850167693627642, 'mean_word_length': 2.238003838771593, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 473, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6513637', 'n_tokens_mistral': 22820, 'n_tokens_neox': 21611, 'n_words': 5308}