/*
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 * Copyright (C) 2014 Vivocha S.p.A.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "../mainrelay.h"
#include "dbd_mongo.h"

#if !defined(TURN_NO_MONGO)
#include <mongoc.h>
#include <bson.h>

///////////////////////////////////////////////////////////////////////////////////////////////////////////

const char * MONGO_DEFAULT_DB = "turn";

struct _MONGO {
  mongoc_uri_t * uri;
  mongoc_client_t * client;
  const char * database;
};

typedef struct _MONGO MONGO;

static void mongo_logger(mongoc_log_level_t log_level, const char * log_domain, const char * message, void * user_data) {
	UNUSED_ARG(log_domain);
	UNUSED_ARG(user_data);

  TURN_LOG_LEVEL l = TURN_LOG_LEVEL_INFO;
  
  UNUSED_ARG(l);

  switch(log_level) {
    case MONGOC_LOG_LEVEL_ERROR:
      l = TURN_LOG_LEVEL_ERROR;
      break;
    case MONGOC_LOG_LEVEL_WARNING:
      l = TURN_LOG_LEVEL_WARNING;
      break;
    default:
      l = TURN_LOG_LEVEL_INFO;
      break;
  }
	TURN_LOG_FUNC(l, "%s\n", message);
}

static void MongoFree(MONGO * info) {
	if(info) {
		if(info->uri) mongoc_uri_destroy(info->uri);
		if(info->client) mongoc_client_destroy(info->client);
		free(info);
	}
}

static MONGO * get_mongodb_connection(void) {

	persistent_users_db_t * pud = get_persistent_users_db();

	MONGO * mydbconnection = (MONGO *) pthread_getspecific(connection_key);

	if (!mydbconnection) {
		mongoc_init();
		mongoc_log_set_handler(&mongo_logger, NULL);

		mydbconnection = (MONGO *) malloc(sizeof(MONGO));
		bzero(mydbconnection, sizeof(MONGO));

		mydbconnection->uri = mongoc_uri_new(pud->userdb);

		if (!mydbconnection->uri) {
			TURN_LOG_FUNC(
					TURN_LOG_LEVEL_ERROR,
					"Cannot open parse MongoDB URI <%s>, connection string format error\n",
					pud->userdb);
			MongoFree(mydbconnection);
			mydbconnection = NULL;
		} else {
			mydbconnection->client = mongoc_client_new_from_uri(
					mydbconnection->uri);
			if (!mydbconnection->client) {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
						"Cannot initialize MongoDB connection\n");
				MongoFree(mydbconnection);
				mydbconnection = NULL;
			} else {
				mydbconnection->database = mongoc_uri_get_database(
						mydbconnection->uri);
				if (!mydbconnection->database)
					mydbconnection->database = MONGO_DEFAULT_DB;
				if(mydbconnection) {
					(void) pthread_setspecific(connection_key, mydbconnection);
				}
				TURN_LOG_FUNC(
					TURN_LOG_LEVEL_INFO,
					"Opened MongoDB URI <%s>\n",
					pud->userdb);
			}
		}
	}
	return mydbconnection;
}

static mongoc_collection_t * mongo_get_collection(const char * name) {
	MONGO * mc = get_mongodb_connection();

	if(!mc) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error getting a connection to MongoDB\n");
		return NULL;
	}
    
  mongoc_collection_t * collection;
  collection = mongoc_client_get_collection(mc->client, mc->database, name);

  if (!collection) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving MongoDB collection '%s'\n", name);
  }

  return collection;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////

static int mongo_get_auth_secrets(secrets_list_t *sl, uint8_t *realm) {
  mongoc_collection_t * collection = mongo_get_collection("turn_secret"); 

	if(!collection)
    return -1;
    
  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "realm", (const char *)realm);

  bson_t fields;
  bson_init(&fields);
  BSON_APPEND_INT32(&fields, "value", 1);
  
  mongoc_cursor_t * cursor;
  cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0, &query, &fields, NULL);

  int ret = -1;
  
  if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection 'turn_secret'\n");
  } else {
    const bson_t * item;
    uint32_t length;
    bson_iter_t iter;
    const char * value;
    while(mongoc_cursor_next(cursor, &item)) {
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "value") && BSON_ITER_HOLDS_UTF8(&iter)) {
        value = bson_iter_utf8(&iter, &length);
				add_to_secrets_list(sl, value);
      }
    }
    mongoc_cursor_destroy(cursor);
    ret = 0;
  }

  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  bson_destroy(&fields);
  return ret;
}
  
static int mongo_get_user_key(uint8_t *usname, uint8_t *realm, hmackey_t key) {
  mongoc_collection_t * collection = mongo_get_collection("turnusers_lt"); 

	if(!collection)
    return -1;
    
  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "name", (const char *)usname);
  BSON_APPEND_UTF8(&query, "realm", (const char *)realm);

  bson_t fields;
  bson_init(&fields);
  BSON_APPEND_INT32(&fields, "hmackey", 1);
  
  mongoc_cursor_t * cursor;
  cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 1, 0, &query, &fields, NULL);
  
  int ret = -1;

  if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection 'turnusers_lt'\n");
  } else {
    const bson_t * item;
    uint32_t length;
    bson_iter_t iter;
    const char * value;
    if (mongoc_cursor_next(cursor, &item)) {
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "hmackey") && BSON_ITER_HOLDS_UTF8(&iter)) {
        value = bson_iter_utf8(&iter, &length);
				size_t sz = get_hmackey_size(SHATYPE_DEFAULT) * 2;
				if(length < sz) {
					TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong key format: string length=%d (must be %d): user %s\n", (int)length, (int)sz, usname);
				} else {
					char kval[sizeof(hmackey_t) + sizeof(hmackey_t) + 1];
					bcopy(value, kval, sz);
					kval[sz] = 0;
					if(convert_string_key_to_binary(kval, key, sz / 2) < 0) {
						TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong key: %s, user %s\n", kval, usname);
					} else {
						ret = 0;
					}
				}
      }
    }
    mongoc_cursor_destroy(cursor);
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  bson_destroy(&fields);
  return ret;
}

static int mongo_get_oauth_key(const uint8_t *kid, oauth_key_data_raw *key) {

	mongoc_collection_t * collection = mongo_get_collection("oauth_key");

	if (!collection)
		return -1;

	bson_t query;
	bson_init(&query);
	BSON_APPEND_UTF8(&query, "kid", (const char *)kid);

	bson_t fields;
	bson_init(&fields);
	BSON_APPEND_INT32(&fields, "lifetime", 1);
	BSON_APPEND_INT32(&fields, "timestamp", 1);
	BSON_APPEND_INT32(&fields, "as_rs_alg", 1);
	BSON_APPEND_INT32(&fields, "realm", 1);
	BSON_APPEND_INT32(&fields, "ikm_key", 1);

	mongoc_cursor_t * cursor;
	cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 1, 0,
			&query, &fields, NULL);

	int ret = -1;

	bzero(key,sizeof(oauth_key_data_raw));
	STRCPY(key->kid,kid);

	if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
				"Error querying MongoDB collection 'oauth_key'\n");
	} else {
		const bson_t * item;
		uint32_t length;
		bson_iter_t iter;
		if (mongoc_cursor_next(cursor, &item)) {
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "as_rs_alg") && BSON_ITER_HOLDS_UTF8(&iter)) {
				STRCPY(key->as_rs_alg,bson_iter_utf8(&iter, &length));
			}
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "realm") && BSON_ITER_HOLDS_UTF8(&iter)) {
				STRCPY(key->realm,bson_iter_utf8(&iter, &length));
			}
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "ikm_key") && BSON_ITER_HOLDS_UTF8(&iter)) {
				STRCPY(key->ikm_key,bson_iter_utf8(&iter, &length));
			}
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "timestamp") && BSON_ITER_HOLDS_INT64(&iter)) {
				key->timestamp = (uint64_t)bson_iter_int64(&iter);
			}
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "lifetime") && BSON_ITER_HOLDS_INT32(&iter)) {
				key->lifetime = (uint32_t)bson_iter_int32(&iter);
			}
			ret = 0;
		}
		mongoc_cursor_destroy(cursor);
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&fields);
	return ret;
}
  
static int mongo_set_user_key(uint8_t *usname, uint8_t *realm, const char *key) {
  mongoc_collection_t * collection = mongo_get_collection("turnusers_lt"); 

	if(!collection)
    return -1;
    
  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "name", (const char *)usname);
  BSON_APPEND_UTF8(&query, "realm", (const char *)realm);
      
  bson_t doc;
  bson_init(&doc);
  BSON_APPEND_UTF8(&doc, "name", (const char *)usname);
  BSON_APPEND_UTF8(&doc, "realm", (const char *)realm);
  BSON_APPEND_UTF8(&doc, "hmackey", (const char *)key);

  int ret = -1;
  
  if (!mongoc_collection_update(collection, MONGOC_UPDATE_UPSERT, &query, &doc, NULL, NULL)) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting/updating user key information\n");
  } else {
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&doc);
  bson_destroy(&query);
  return ret;
}

static int mongo_set_oauth_key(oauth_key_data_raw *key) {

  mongoc_collection_t * collection = mongo_get_collection("oauth_key");

  if(!collection)
    return -1;

  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "kid", (const char *)key->kid);

  bson_t doc;
  bson_init(&doc);
  BSON_APPEND_UTF8(&doc, "kid", (const char *)key->kid);
  BSON_APPEND_UTF8(&doc, "as_rs_alg", (const char *)key->as_rs_alg);
  BSON_APPEND_UTF8(&doc, "realm", (const char *)key->realm);
  BSON_APPEND_UTF8(&doc, "ikm_key", (const char *)key->ikm_key);
  BSON_APPEND_INT64(&doc, "timestamp", (int64_t)key->timestamp);
  BSON_APPEND_INT32(&doc, "lifetime", (int32_t)key->lifetime);

  int ret = -1;

  if (!mongoc_collection_update(collection, MONGOC_UPDATE_UPSERT, &query, &doc, NULL, NULL)) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting/updating oauth key information\n");
  } else {
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&doc);
  bson_destroy(&query);
  return ret;
}
  
static int mongo_del_user(uint8_t *usname, uint8_t *realm) {
  mongoc_collection_t * collection = mongo_get_collection("turnusers_lt");

	if(!collection)
    return -1;
    
  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "name", (const char *)usname);
  BSON_APPEND_UTF8(&query, "realm", (const char *)realm);
  
  int ret = -1;    

  if (!mongoc_collection_delete(collection, MONGOC_DELETE_SINGLE_REMOVE, &query, NULL, NULL)) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error deleting user key information\n");
  } else {
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  return ret;
}

static int mongo_del_oauth_key(const uint8_t *kid) {

  mongoc_collection_t * collection = mongo_get_collection("oauth_key");

  if(!collection)
    return -1;

  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "kid", (const char *)kid);

  int ret = -1;

  if (!mongoc_collection_delete(collection, MONGOC_DELETE_SINGLE_REMOVE, &query, NULL, NULL)) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error deleting oauth key information\n");
  } else {
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  return ret;
}
  
static int mongo_list_users(uint8_t *realm, secrets_list_t *users, secrets_list_t *realms)
{
  const char * collection_name = "turnusers_lt";
  mongoc_collection_t * collection = mongo_get_collection(collection_name);

  uint8_t realm0[STUN_MAX_REALM_SIZE+1] = "\0";
  if(!realm) realm=realm0;

  if(!collection)
    return -1;
    
  bson_t query, child;
  bson_init(&query);
  bson_append_document_begin(&query, "$orderby", -1, &child);
  bson_append_int32(&child, "realm", -1, 1);
  bson_append_int32(&child, "name", -1, 1);
  bson_append_document_end(&query, &child);
  bson_append_document_begin(&query, "$query", -1, &child);
  if (realm && realm[0]) {
    BSON_APPEND_UTF8(&child, "realm", (const char *)realm);
  }
  bson_append_document_end(&query, &child);

  bson_t fields;
  bson_init(&fields);
  BSON_APPEND_INT32(&fields, "name", 1);
  BSON_APPEND_INT32(&fields, "realm", 1);

  mongoc_cursor_t * cursor;
  cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0, &query, &fields, NULL);

  int ret = -1;
  
  if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection '%s'\n", collection_name);
  } else {
    const bson_t * item;
    uint32_t length;
    bson_iter_t iter;
    bson_iter_t iter_realm;
    const char * value;
    while (mongoc_cursor_next(cursor, &item)) {
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "name") && BSON_ITER_HOLDS_UTF8(&iter)) {
    		value = bson_iter_utf8(&iter, &length);
    		if (length) {
        		const char *rval = "";
    			if (bson_iter_init(&iter_realm, item) && bson_iter_find(&iter_realm, "realm") && BSON_ITER_HOLDS_UTF8(&iter_realm)) {
    				rval = bson_iter_utf8(&iter_realm, &length);
    			}
    			if(users) {
    				add_to_secrets_list(users,value);
    				if(realms) {
    					if(rval && *rval) {
    						add_to_secrets_list(realms,rval);
    					} else {
    						add_to_secrets_list(realms,(char*)realm);
    					}
    				}
    			} else {
    				printf("%s[%s]\n", value, rval);
    			}
    		}
    	}
    }
    mongoc_cursor_destroy(cursor);
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  bson_destroy(&fields);
  return ret;
}

static int mongo_list_oauth_keys(secrets_list_t *kids,secrets_list_t *teas,secrets_list_t *tss,secrets_list_t *lts,secrets_list_t *realms) {

  const char * collection_name = "oauth_key";
  mongoc_collection_t * collection = mongo_get_collection(collection_name);

  if(!collection)
    return -1;

  bson_t query;
  bson_init(&query);

  bson_t child;
  bson_append_document_begin(&query, "$orderby", -1, &child);
  bson_append_int32(&child, "kid", -1, 1);
  bson_append_document_end(&query, &child);
  bson_append_document_begin(&query, "$query", -1, &child);
  bson_append_document_end(&query, &child);

  bson_t fields;
  bson_init(&fields);
  BSON_APPEND_INT32(&fields, "kid", 1);
  BSON_APPEND_INT32(&fields, "lifetime", 1);
  BSON_APPEND_INT32(&fields, "timestamp", 1);
  BSON_APPEND_INT32(&fields, "as_rs_alg", 1);
  BSON_APPEND_INT32(&fields, "realm", 1);
  BSON_APPEND_INT32(&fields, "ikm_key", 1);

  mongoc_cursor_t * cursor;
  cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0, &query, &fields, NULL);

  int ret = -1;

  if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection '%s'\n", collection_name);
  } else {
    const bson_t * item;
	oauth_key_data_raw key_;
	oauth_key_data_raw *key=&key_;
    uint32_t length;
    bson_iter_t iter;
    while (mongoc_cursor_next(cursor, &item)) {

    	bzero(key,sizeof(oauth_key_data_raw));
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "kid") && BSON_ITER_HOLDS_UTF8(&iter)) {
    		STRCPY(key->kid,bson_iter_utf8(&iter, &length));
    	}
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "as_rs_alg") && BSON_ITER_HOLDS_UTF8(&iter)) {
    	    STRCPY(key->as_rs_alg,bson_iter_utf8(&iter, &length));
    	}
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "realm") && BSON_ITER_HOLDS_UTF8(&iter)) {
    	    STRCPY(key->realm,bson_iter_utf8(&iter, &length));
    	}
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "ikm_key") && BSON_ITER_HOLDS_UTF8(&iter)) {
    		STRCPY(key->ikm_key,bson_iter_utf8(&iter, &length));
    	}
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "timestamp") && BSON_ITER_HOLDS_INT64(&iter)) {
    		key->timestamp = (uint64_t)bson_iter_int64(&iter);
    	}
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "lifetime") && BSON_ITER_HOLDS_INT32(&iter)) {
    		key->lifetime = (uint32_t)bson_iter_int32(&iter);
    	}
    	if(kids) {
    		add_to_secrets_list(kids,key->kid);
    		add_to_secrets_list(teas,key->as_rs_alg);
    		add_to_secrets_list(realms,key->realm);
			{
				char ts[256];
				snprintf(ts,sizeof(ts)-1,"%llu",(unsigned long long)key->timestamp);
				add_to_secrets_list(tss,ts);
			}
			{
				char lt[256];
				snprintf(lt,sizeof(lt)-1,"%lu",(unsigned long)key->lifetime);
				add_to_secrets_list(lts,lt);
			}
    	} else {
    		printf("  kid=%s, ikm_key=%s, timestamp=%llu, lifetime=%lu, as_rs_alg=%s, realm=%s\n",
    						key->kid, key->ikm_key, (unsigned long long)key->timestamp, (unsigned long)key->lifetime,
    						key->as_rs_alg, key->realm);
    	}
    }
    mongoc_cursor_destroy(cursor);
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  bson_destroy(&fields);
  return ret;
}
  
static int mongo_list_secrets(uint8_t *realm, secrets_list_t *secrets, secrets_list_t *realms)
{
	mongoc_collection_t * collection = mongo_get_collection("turn_secret");

	uint8_t realm0[STUN_MAX_REALM_SIZE+1] = "\0";
	if(!realm) realm=realm0;

	if(!collection)
		return -1;
    
	bson_t query, child;
	bson_init(&query);
	bson_append_document_begin(&query, "$orderby", -1, &child);
	bson_append_int32(&child, "realm", -1, 1);
	bson_append_int32(&child, "value", -1, 1);
	bson_append_document_end(&query, &child);
	bson_append_document_begin(&query, "$query", -1, &child);
	if (realm && realm[0]) {
		BSON_APPEND_UTF8(&child, "realm", (const char *)realm);
	}
	bson_append_document_end(&query, &child);

	bson_t fields;
	bson_init(&fields);
	BSON_APPEND_INT32(&fields, "value", 1);
	BSON_APPEND_INT32(&fields, "realm", 1);

	mongoc_cursor_t * cursor;
	cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0, &query, &fields, NULL);

	int ret = -1;
  
	if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection 'turn_secret'\n");
	} else {
		const bson_t * item;
		uint32_t length;
		bson_iter_t iter;
	    bson_iter_t iter_realm;
		const char * value;
		while (mongoc_cursor_next(cursor, &item)) {
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "value") && BSON_ITER_HOLDS_UTF8(&iter)) {
				value = bson_iter_utf8(&iter, &length);
				if (length) {
					const char *rval = "";
					if (bson_iter_init(&iter_realm, item) && bson_iter_find(&iter_realm, "realm") && BSON_ITER_HOLDS_UTF8(&iter_realm)) {
						rval = bson_iter_utf8(&iter_realm, &length);
					}
					if(secrets) {
						add_to_secrets_list(secrets,value);
					    if(realms) {
					    	if(rval && *rval) {
					    		add_to_secrets_list(realms,rval);
					    	} else {
					    		add_to_secrets_list(realms,(char*)realm);
					    	}
					    }
					} else {
						printf("%s[%s]\n", value, rval);
					}
				}
			}
		}
		mongoc_cursor_destroy(cursor);
		ret = 0;
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&fields);
	return ret;
}
  
static int mongo_del_secret(uint8_t *secret, uint8_t *realm) {
  mongoc_collection_t * collection = mongo_get_collection("turn_secret"); 

	if(!collection)
    return -1;
    
  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "realm", (const char *)realm);
	if(secret && (secret[0]!=0)) {
    BSON_APPEND_UTF8(&query, "value", (const char *)secret);
  }

  mongoc_collection_delete(collection, MONGOC_DELETE_NONE, &query, NULL, NULL);
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  return 0;
}
  
static int mongo_set_secret(uint8_t *secret, uint8_t *realm) {
  mongoc_collection_t * collection = mongo_get_collection("turn_secret"); 

	if(!collection)
    return -1;
    
  bson_t query;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "realm", (const char *)realm);
  BSON_APPEND_UTF8(&query, "value", (const char *)secret);

  int res = mongoc_collection_insert(collection, MONGOC_INSERT_NONE, &query, NULL, NULL);
  mongoc_collection_destroy(collection);
  bson_destroy(&query);

  if (!res) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting/updating secret key information\n");
    return -1;
  } else {
    return 0;
  }
}

static int mongo_set_permission_ip(const char *kind, uint8_t *realm, const char* ip, int del)
{
	char sub_collection_name[129];
	snprintf(sub_collection_name,sizeof(sub_collection_name)-1,"%s_peer_ip",kind);

	mongoc_collection_t * collection = mongo_get_collection("realm");

	if(!collection)
		return -1;

	int ret = -1;

	uint8_t realm0[STUN_MAX_REALM_SIZE+1] = "\0";
	if(!realm) realm=realm0;

	bson_t query, doc, child;
	bson_init(&query);
	BSON_APPEND_UTF8(&query, "realm", (const char *)realm);
	bson_init(&doc);
	if(del) {
		bson_append_document_begin(&doc, "$pull", -1, &child);
	} else {
		bson_append_document_begin(&doc, "$addToSet", -1, &child);
	}
	BSON_APPEND_UTF8(&child, sub_collection_name, (const char *)ip);
	bson_append_document_end(&doc, &child);

	mongoc_update_flags_t flags = MONGOC_UPDATE_NONE;

	if(del) {
		flags = MONGOC_UPDATE_MULTI_UPDATE;
	} else {
		flags = MONGOC_UPDATE_UPSERT;
	}

	if (!mongoc_collection_update(collection, flags, &query, &doc, NULL, NULL)) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting permission ip information\n");
	} else {
		ret = 0;
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&doc);
	return ret;
}
  
static int mongo_add_origin(uint8_t *origin, uint8_t *realm)
{
	mongoc_collection_t * collection = mongo_get_collection("realm");

	if(!collection)
		return -1;
    
	int ret = -1;

	uint8_t realm0[STUN_MAX_REALM_SIZE+1] = "\0";
	if(!realm) realm=realm0;
  
	bson_t query, doc, child;
	bson_init(&query);
	BSON_APPEND_UTF8(&query, "realm", (const char *)realm);
	bson_init(&doc);
	bson_append_document_begin(&doc, "$addToSet", -1, &child);
	BSON_APPEND_UTF8(&child, "origin", (const char *)origin);
	bson_append_document_end(&doc, &child);

	if (!mongoc_collection_update(collection, MONGOC_UPDATE_UPSERT, &query, &doc, NULL, NULL)) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting/updating realm origin information\n");
	} else {
		ret = 0;
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&doc);
	return ret;
}
  
static int mongo_del_origin(uint8_t *origin)
{
  mongoc_collection_t * collection = mongo_get_collection("realm"); 

  if(!collection)
    return -1;
    
  int ret = -1;
  
  bson_t query, doc, child;
  bson_init(&query);
  bson_init(&doc);
  bson_append_document_begin(&doc, "$pull", -1, &child);
  BSON_APPEND_UTF8(&child, "origin", (const char *)origin);
  bson_append_document_end(&doc, &child);

  if (!mongoc_collection_update(collection, MONGOC_UPDATE_MULTI_UPDATE, &query, &doc, NULL, NULL)) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error deleting origin information\n");
  } else {
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  bson_destroy(&doc);
  return ret;
}
  
static int mongo_list_origins(uint8_t *realm, secrets_list_t *origins, secrets_list_t *realms)
{
	mongoc_collection_t * collection = mongo_get_collection("realm");

	if(!collection)
		return -1;

	uint8_t realm0[STUN_MAX_REALM_SIZE+1] = "\0";
	if(!realm) realm=realm0;

	bson_t query, child;
	bson_init(&query);
	bson_append_document_begin(&query, "$orderby", -1, &child);
	BSON_APPEND_INT32(&child, "realm", 1);
	bson_append_document_end(&query, &child);
	bson_append_document_begin(&query, "$query", -1, &child);
	if (realm && realm[0]) {
		BSON_APPEND_UTF8(&child, "realm", (const char *)realm);
	}
	bson_append_document_end(&query, &child);

	bson_t fields;
	bson_init(&fields);
	BSON_APPEND_INT32(&fields, "origin", 1);
	BSON_APPEND_INT32(&fields, "realm", 1);
  
	mongoc_cursor_t * cursor;
	cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0, &query, &fields, NULL);

	int ret = -1;
  
	if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection 'realm'\n");
	} else {
		const bson_t * item;
		uint32_t length;
		bson_iter_t iter;

		while (mongoc_cursor_next(cursor, &item)) {
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "realm") && BSON_ITER_HOLDS_UTF8(&iter)) {
				const char * _realm = bson_iter_utf8(&iter, &length);

				if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "origin") && BSON_ITER_HOLDS_ARRAY(&iter)) {
					const uint8_t *docbuf = NULL;
					uint32_t doclen = 0;
					bson_t origin_array;
					bson_iter_t origin_iter;

					bson_iter_array(&iter, &doclen, &docbuf);
					bson_init_static(&origin_array, docbuf, doclen);

					if (bson_iter_init(&origin_iter, &origin_array)) {
						while(bson_iter_next(&origin_iter)) {
							if (BSON_ITER_HOLDS_UTF8(&origin_iter)) {
								const char * _origin = bson_iter_utf8(&origin_iter, &length);
								if(origins) {
									add_to_secrets_list(origins,_origin);
									if(realms) {
										add_to_secrets_list(realms,_realm);
									}
								} else {
									printf("%s ==>> %s\n", _realm, _origin);
								}
							}
						}
					}
				}
			}
		}
		mongoc_cursor_destroy(cursor);
		ret = 0;
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&fields);
	return ret;
}
  
static int mongo_set_realm_option_one(uint8_t *realm, unsigned long value, const char* opt) {
  mongoc_collection_t * collection = mongo_get_collection("realm"); 

	if(!collection)
    return -1;
    
  bson_t query, doc, child;
  bson_init(&query);
  BSON_APPEND_UTF8(&query, "realm", (const char *)realm);
  bson_init(&doc);
  
  size_t klen = 9 + strlen(opt);
  char * _k = (char *)malloc(klen);
  strcpy(_k, "options.");
  strcat(_k, opt);
  
  if (value > 0) {
    bson_append_document_begin(&doc, "$set", -1, &child);
    BSON_APPEND_INT32(&child, _k, (int32_t)value);
    bson_append_document_end(&doc, &child);
  } else {
    bson_append_document_begin(&doc, "$unset", -1, &child);
    BSON_APPEND_INT32(&child, _k, 1);
    bson_append_document_end(&doc, &child);
  }
  free(_k);
  
  int ret = -1;
  
  if (!mongoc_collection_update(collection, MONGOC_UPDATE_MULTI_UPDATE, &query, &doc, NULL, NULL)) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error deleting origin information\n");
  } else {
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  bson_destroy(&doc);
  return ret;
}
  
static int mongo_list_realm_options(uint8_t *realm) {
  mongoc_collection_t * collection = mongo_get_collection("realm"); 

	if(!collection)
    return -1;
    
  bson_t query, child;
  bson_init(&query);
  bson_append_document_begin(&query, "$orderby", -1, &child);
  BSON_APPEND_INT32(&child, "realm", 1);
  bson_append_document_end(&query, &child);
  bson_append_document_begin(&query, "$query", -1, &child);
  if (realm && realm[0]) {
    BSON_APPEND_UTF8(&child, "realm", (const char *)realm);
  }
  bson_append_document_end(&query, &child);

  bson_t fields;
  bson_init(&fields);
  BSON_APPEND_INT32(&fields, "options", 1);
  BSON_APPEND_INT32(&fields, "realm", 1);
  
  mongoc_cursor_t * cursor;
  cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0, &query, &fields, NULL);

  int ret = -1;
  
  if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection 'realm'\n");
  } else {
    const bson_t * item;
    uint32_t length;
    bson_iter_t iter;

    while (mongoc_cursor_next(cursor, &item)) {
    	if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "realm") && BSON_ITER_HOLDS_UTF8(&iter)) {
        const char * _realm = bson_iter_utf8(&iter, &length);

        if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "options") && BSON_ITER_HOLDS_DOCUMENT(&iter)) {
          const uint8_t *docbuf = NULL;
          uint32_t doclen = 0;
          bson_t options;
          bson_iter_t options_iter;

          bson_iter_document(&iter, &doclen, &docbuf);
          bson_init_static(&options, docbuf, doclen);

          if (bson_iter_init(&options_iter, &options)) {
            while(bson_iter_next(&options_iter)) {
              const char * _k = bson_iter_key(&options_iter);
              if (BSON_ITER_HOLDS_DOUBLE(&options_iter)) {
                int32_t _v = (int32_t)bson_iter_double(&options_iter);
								printf("%s[%s]=%d\n", _k, _realm, _v);
              } else if (BSON_ITER_HOLDS_INT32(&options_iter)) {
                int32_t _v = bson_iter_int32(&options_iter);
								printf("%s[%s]=%d\n", _k, _realm, _v);
              } else if (BSON_ITER_HOLDS_INT64(&options_iter)) {
                int32_t _v = (int32_t)bson_iter_int64(&options_iter);
								printf("%s[%s]=%d\n", _k, _realm, _v);
              }
            }
          }
        }
      }
    }
    mongoc_cursor_destroy(cursor);
    ret = 0;
  }
  mongoc_collection_destroy(collection);
  bson_destroy(&query);
  bson_destroy(&fields);
  return ret;
}
  
static void mongo_auth_ping(void * rch) {
	UNUSED_ARG(rch);
  // NOOP
}

static int mongo_read_realms_ip_lists(const char *kind, ip_range_list_t * list)
{
	int ret = 0;

	char field_name[129];
	sprintf(field_name, "%s_peer_ip", kind);

	mongoc_collection_t * collection = mongo_get_collection("realm");

	if (!collection)
		return ret;

	bson_t query;
	bson_init(&query);

	bson_t fields;
	bson_init(&fields);
	BSON_APPEND_INT32(&fields, "realm", 1);
	BSON_APPEND_INT32(&fields, field_name, 1);

	mongoc_cursor_t * cursor;
	cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0,
			&query, &fields, NULL);

	if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
				"Error querying MongoDB collection 'realm'\n");
		ret = -1;
	} else {
		const bson_t * item;
		uint32_t length;
		bson_iter_t iter;
		char realm[513];

		while (mongoc_cursor_next(cursor, &item)) {

			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "realm")
					&& BSON_ITER_HOLDS_UTF8(&iter)) {

				STRCPY(realm,bson_iter_utf8(&iter, &length));

				if (bson_iter_init(&iter, item) && bson_iter_find(&iter,
						field_name) && BSON_ITER_HOLDS_ARRAY(&iter)) {
					const uint8_t *docbuf = NULL;
					uint32_t doclen = 0;
					bson_t ip_range_array;
					bson_iter_t ip_range_iter;

					bson_iter_array(&iter, &doclen, &docbuf);
					bson_init_static(&ip_range_array, docbuf, doclen);

					if (bson_iter_init(&ip_range_iter, &ip_range_array)) {
						while (bson_iter_next(&ip_range_iter)) {
							if (BSON_ITER_HOLDS_UTF8(&ip_range_iter)) {
								const char* ip_range = bson_iter_utf8(&ip_range_iter, &length);
								add_ip_list_range(ip_range, realm, list);
							}
						}
					}
				}
			}
		}
		mongoc_cursor_destroy(cursor);
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&fields);

	return ret;
}
  
static int mongo_get_ip_list(const char *kind, ip_range_list_t * list) {
	return mongo_read_realms_ip_lists(kind, list);
}
  

static void mongo_reread_realms(secrets_list_t * realms_list) {

	UNUSED_ARG(realms_list);

	mongoc_collection_t * collection = mongo_get_collection("realm");

	if (!collection)
		return;

	bson_t query;
	bson_init(&query);

	bson_t fields;
	bson_init(&fields);
	BSON_APPEND_INT32(&fields, "realm", 1);
	BSON_APPEND_INT32(&fields, "origin", 1);
	BSON_APPEND_INT32(&fields, "options", 1);

	mongoc_cursor_t * cursor;
	cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0,
			&query, &fields, NULL);

	if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
				"Error querying MongoDB collection 'realm'\n");
	} else {
		ur_string_map *o_to_realm_new = ur_string_map_create(free);

		const bson_t * item;
		uint32_t length;
		bson_iter_t iter;

		while (mongoc_cursor_next(cursor, &item)) {

			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "realm")
					&& BSON_ITER_HOLDS_UTF8(&iter)) {

				char * _realm = strdup(bson_iter_utf8(&iter, &length));

				get_realm(_realm);

				if (bson_iter_init(&iter, item) && bson_iter_find(&iter,
						"origin") && BSON_ITER_HOLDS_ARRAY(&iter)) {
					const uint8_t *docbuf = NULL;
					uint32_t doclen = 0;
					bson_t origin_array;
					bson_iter_t origin_iter;

					bson_iter_array(&iter, &doclen, &docbuf);
					bson_init_static(&origin_array, docbuf, doclen);

					if (bson_iter_init(&origin_iter, &origin_array)) {
						while (bson_iter_next(&origin_iter)) {
							if (BSON_ITER_HOLDS_UTF8(&origin_iter)) {
								char* _origin =	strdup(bson_iter_utf8(&origin_iter, &length));
								char *rval = strdup(_realm);
								ur_string_map_value_type value =
										(ur_string_map_value_type) (rval);
								ur_string_map_put(o_to_realm_new,
										(ur_string_map_key_type) _origin,
										value);
								free(_origin);
							}
						}
					}
				}

				realm_params_t* rp = get_realm(_realm);
				lock_realms();
				rp->options.perf_options.max_bps = turn_params.max_bps;
				rp->options.perf_options.total_quota = turn_params.total_quota;
				rp->options.perf_options.user_quota = turn_params.user_quota;
				unlock_realms();

				if (bson_iter_init(&iter, item) && bson_iter_find(&iter,
						"options") && BSON_ITER_HOLDS_DOCUMENT(&iter)) {
					const uint8_t *docbuf = NULL;
					uint32_t doclen = 0;
					bson_t options;
					bson_iter_t options_iter;

					bson_iter_document(&iter, &doclen, &docbuf);
					bson_init_static(&options, docbuf, doclen);

					if (bson_iter_init(&options_iter, &options)) {
						while (bson_iter_next(&options_iter)) {
							const char * _k = bson_iter_key(&options_iter);
							uint64_t _v = 0;
							if (BSON_ITER_HOLDS_DOUBLE(&options_iter)) {
								_v = (uint64_t) bson_iter_double(&options_iter);
							} else if (BSON_ITER_HOLDS_INT32(&options_iter)) {
								_v = (uint64_t)bson_iter_int32(&options_iter);
							} else if (BSON_ITER_HOLDS_INT64(&options_iter)) {
								_v = (uint64_t) bson_iter_int64(&options_iter);
							}
							if (_v) {
								if (!strcmp(_k, "max-bps"))
									rp->options.perf_options.max_bps = (band_limit_t) _v;
								else if (!strcmp(_k, "total-quota"))
									rp->options.perf_options.total_quota = (vint) _v;
								else if (!strcmp(_k, "user-quota"))
									rp->options.perf_options.user_quota = (vint) _v;
								else {
									TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
											"Unknown realm option: %s\n", _k);
								}
							}
						}
					}
				}
				free(_realm);
			}
		}
		update_o_to_realm(o_to_realm_new);
		mongoc_cursor_destroy(cursor);
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&fields);
}

/////////////////////////////////////////////////

static int mongo_get_admin_user(const uint8_t *usname, uint8_t *realm, password_t pwd)
{
	mongoc_collection_t * collection = mongo_get_collection("admin_user");

	if(!collection)
    return -1;

	realm[0]=0;
	pwd[0]=0;

	bson_t query;
	bson_init(&query);
	BSON_APPEND_UTF8(&query, "name", (const char *)usname);

	bson_t fields;
	bson_init(&fields);
	BSON_APPEND_INT32(&fields, "realm", 1);
	BSON_APPEND_INT32(&fields, "password", 1);

	mongoc_cursor_t * cursor;
	cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 1, 0, &query, &fields, NULL);

	int ret = -1;

	if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection 'admin_user'\n");
	} else {
		const bson_t * item;
		uint32_t length;
		bson_iter_t iter;
		if (mongoc_cursor_next(cursor, &item)) {
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "realm") && BSON_ITER_HOLDS_UTF8(&iter)) {
				strncpy((char*)realm,bson_iter_utf8(&iter, &length),STUN_MAX_REALM_SIZE);
				ret = 0;
			}
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "password") && BSON_ITER_HOLDS_UTF8(&iter)) {
				strncpy((char*)pwd,bson_iter_utf8(&iter, &length),STUN_MAX_PWD_SIZE);
				ret = 0;
			}
		}
		mongoc_cursor_destroy(cursor);
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&fields);
	return ret;
}

static int mongo_set_admin_user(const uint8_t *usname, const uint8_t *realm, const password_t pwd)
{
	mongoc_collection_t * collection = mongo_get_collection("admin_user");

	if(!collection)
    return -1;

	bson_t query;
	bson_init(&query);
	BSON_APPEND_UTF8(&query, "name", (const char *)usname);

	bson_t doc;
	bson_init(&doc);
	BSON_APPEND_UTF8(&doc, "name", (const char *)usname);
	BSON_APPEND_UTF8(&doc, "realm", (const char *)realm);
	BSON_APPEND_UTF8(&doc, "password", (const char *)pwd);

	int ret = -1;

	if (!mongoc_collection_update(collection, MONGOC_UPDATE_UPSERT, &query, &doc, NULL, NULL)) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting/updating admin user information\n");
	} else {
		ret = 0;
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&doc);
	bson_destroy(&query);
	return ret;
}

static int mongo_del_admin_user(const uint8_t *usname)
{
	mongoc_collection_t * collection = mongo_get_collection("admin_user");

	if(!collection)
		return -1;

	bson_t query;
	bson_init(&query);
	BSON_APPEND_UTF8(&query, "name", (const char *)usname);

	int ret = -1;

	if (!mongoc_collection_delete(collection, MONGOC_DELETE_SINGLE_REMOVE, &query, NULL, NULL)) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error deleting admin user information\n");
	} else {
		ret = 0;
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	return ret;
}

static int mongo_list_admin_users(int no_print)
{
	const char * collection_name = "admin_user";
	mongoc_collection_t * collection = mongo_get_collection(collection_name);

	if(!collection)
		return -1;

	bson_t query, child;
	bson_init(&query);
	bson_append_document_begin(&query, "$orderby", -1, &child);
	bson_append_int32(&child, "name", -1, 1);
	bson_append_document_end(&query, &child);
	bson_append_document_begin(&query, "$query", -1, &child);
	bson_append_document_end(&query, &child);

	bson_t fields;
	bson_init(&fields);
	BSON_APPEND_INT32(&fields, "name", 1);
	BSON_APPEND_INT32(&fields, "realm", 1);

	mongoc_cursor_t * cursor;
	cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 0, 0, &query, &fields, NULL);

	int ret = -1;

	if (!cursor) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error querying MongoDB collection '%s'\n", collection_name);
	} else {
		const bson_t * item;
		uint32_t length;
		bson_iter_t iter;
		bson_iter_t iter_realm;
		const char * value;
		ret = 0;
		while (mongoc_cursor_next(cursor, &item)) {
			if (bson_iter_init(&iter, item) && bson_iter_find(&iter, "name") && BSON_ITER_HOLDS_UTF8(&iter)) {
				value = bson_iter_utf8(&iter, &length);
				if (length) {
					const char *realm = "";
					if (bson_iter_init(&iter_realm, item) && bson_iter_find(&iter_realm, "realm") && BSON_ITER_HOLDS_UTF8(&iter_realm)) {
						realm = bson_iter_utf8(&iter_realm, &length);
					}
					++ret;
					if(!no_print) {
						if(realm && *realm) {
							printf("%s[%s]\n", value, realm);
						} else {
							printf("%s\n", value);
						}
					}
				}
			}
		}
		mongoc_cursor_destroy(cursor);
	}
	mongoc_collection_destroy(collection);
	bson_destroy(&query);
	bson_destroy(&fields);
	return ret;
}

static void mongo_disconnect(void) {
	MONGO * mongoconnection = (MONGO *) pthread_getspecific(connection_key);
	if (mongoconnection) {
		MongoFree(mongoconnection);
		mongoconnection = NULL;
	}
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "MongoDB connection was closed.\n");
}

//////////////////////////////////////////////////////////

static const turn_dbdriver_t driver = {
  &mongo_get_auth_secrets,
  &mongo_get_user_key,
  &mongo_set_user_key,
  &mongo_del_user,
  &mongo_list_users,
  &mongo_list_secrets,
  &mongo_del_secret,
  &mongo_set_secret,
  &mongo_add_origin,
  &mongo_del_origin,
  &mongo_list_origins,
  &mongo_set_realm_option_one,
  &mongo_list_realm_options,
  &mongo_auth_ping,
  &mongo_get_ip_list,
  &mongo_set_permission_ip,
  &mongo_reread_realms,
  &mongo_set_oauth_key,
  &mongo_get_oauth_key,
  &mongo_del_oauth_key,
  &mongo_list_oauth_keys,
  &mongo_get_admin_user,
  &mongo_set_admin_user,
  &mongo_del_admin_user,
  &mongo_list_admin_users,
  &mongo_disconnect
};

const turn_dbdriver_t * get_mongo_dbdriver(void) {
  return &driver;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////

#else

const turn_dbdriver_t * get_mongo_dbdriver(void) {
  return NULL;
}

#endif
