/*
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 *
 * 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
#include <ifaddrs.h>
#include <getopt.h>
#include <locale.h>
#include <libgen.h>

#include <pthread.h>

#include <signal.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/resource.h>

#include <event2/bufferevent.h>
#include <event2/buffer.h>

#include "userdb.h"
#include "dbdrivers/dbdriver.h"
#include "mainrelay.h"

#include "ns_turn_utils.h"

#include "ns_turn_server.h"
#include "ns_turn_maps.h"

#include "apputils.h"

//////////// REALM //////////////

static realm_params_t *default_realm_params_ptr = NULL;

static ur_string_map *realms = NULL;
static turn_mutex o_to_realm_mutex;
static ur_string_map *o_to_realm = NULL;
static secrets_list_t realms_list;

void lock_realms(void) {
	ur_string_map_lock(realms);
}

void unlock_realms(void) {
	ur_string_map_unlock(realms);
}

void update_o_to_realm(ur_string_map * o_to_realm_new) {
  TURN_MUTEX_LOCK(&o_to_realm_mutex);
  ur_string_map_free(&o_to_realm);
  o_to_realm = o_to_realm_new;
  TURN_MUTEX_UNLOCK(&o_to_realm_mutex);
}

void create_default_realm()
{
	if(default_realm_params_ptr) {
		return;
	}

	static realm_params_t _default_realm_params =
	{
	  1,
	  {
		"\0", /* name */
	    {0,0,0}
	  },
	  {0,NULL}
	};

	/* init everything: */
	TURN_MUTEX_INIT_RECURSIVE(&o_to_realm_mutex);
	init_secrets_list(&realms_list);
	o_to_realm = ur_string_map_create(free);
	default_realm_params_ptr = &_default_realm_params;
	realms = ur_string_map_create(NULL);
	lock_realms();
	default_realm_params_ptr->status.alloc_counters =  ur_string_map_create(NULL);
	unlock_realms();
}

void get_default_realm_options(realm_options_t* ro)
{
	if(ro) {
		lock_realms();
		bcopy(&(default_realm_params_ptr->options),ro,sizeof(realm_options_t));
		unlock_realms();
	}
}

void set_default_realm_name(char *realm) {
	lock_realms();
	ur_string_map_value_type value = (ur_string_map_value_type)default_realm_params_ptr;
	STRCPY(default_realm_params_ptr->options.name,realm);
	ur_string_map_put(realms, (ur_string_map_key_type)default_realm_params_ptr->options.name, value);
	add_to_secrets_list(&realms_list, realm);
	unlock_realms();
}

realm_params_t* get_realm(char* name)
{
	if(name && name[0]) {
		lock_realms();
		ur_string_map_value_type value = 0;
		ur_string_map_key_type key = (ur_string_map_key_type)name;
		if (ur_string_map_get(realms, key, &value)) {
			unlock_realms();
			return (realm_params_t*)value;
		} else {
			realm_params_t *ret = (realm_params_t*)malloc(sizeof(realm_params_t));
			bcopy(default_realm_params_ptr,ret,sizeof(realm_params_t));
			STRCPY(ret->options.name,name);
			value = (ur_string_map_value_type)ret;
			ur_string_map_put(realms, key, value);
			ret->status.alloc_counters =  ur_string_map_create(NULL);
			add_to_secrets_list(&realms_list, name);
			unlock_realms();
			return ret;
		}
	}

	return default_realm_params_ptr;
}

int get_realm_data(char* name, realm_params_t* rp)
{
	lock_realms();
	bcopy(get_realm(name),rp,sizeof(realm_params_t));
	unlock_realms();
	return 0;
}

int get_realm_options_by_origin(char *origin, realm_options_t* ro)
{
	ur_string_map_value_type value = 0;
	TURN_MUTEX_LOCK(&o_to_realm_mutex);
	if (ur_string_map_get(o_to_realm, (ur_string_map_key_type) origin, &value) && value) {
		char *realm = strdup((char*)value);
		TURN_MUTEX_UNLOCK(&o_to_realm_mutex);
		realm_params_t rp;
		get_realm_data(realm, &rp);
		bcopy(&(rp.options),ro,sizeof(realm_options_t));
		free(realm);
		return 1;
	} else {
		TURN_MUTEX_UNLOCK(&o_to_realm_mutex);
		get_default_realm_options(ro);
		return 0;
	}
}

void get_realm_options_by_name(char *realm, realm_options_t* ro)
{
	realm_params_t rp;
	get_realm_data(realm, &rp);
	bcopy(&(rp.options),ro,sizeof(realm_options_t));
}

int change_total_quota(char *realm, int value)
{
	int ret = value;
	lock_realms();
	realm_params_t* rp = get_realm(realm);
	rp->options.perf_options.total_quota = value;
	unlock_realms();
	return ret;
}

int change_user_quota(char *realm, int value)
{
	int ret = value;
	lock_realms();
	realm_params_t* rp = get_realm(realm);
	rp->options.perf_options.user_quota = value;
	unlock_realms();
	return ret;
}

static void must_set_admin_realm(void *realm0)
{
	char* realm = (char*)realm0;
	if(!realm || !realm[0]) {
		fprintf(stderr, "The operation cannot be completed: the realm must be set.\n");
		exit(-1);
	}
}

static void must_set_admin_user(void *user0)
{
	char* user = (char*)user0;
	if(!user || !user[0]) {
		fprintf(stderr, "The operation cannot be completed: the user must be set.\n");
		exit(-1);
	}
}

static void must_set_admin_pwd(void *pwd0)
{
	char* pwd = (char*)pwd0;
	if(!pwd || !pwd[0]) {
		fprintf(stderr, "The operation cannot be completed: the password must be set.\n");
		exit(-1);
	}
}

static void must_set_admin_origin(void *origin0)
{
	char* origin = (char*)origin0;
	if(!origin || !origin[0]) {
		fprintf(stderr, "The operation cannot be completed: the origin must be set.\n");
		exit(-1);
	}
}

/////////// SHARED SECRETS /////////////////

void init_secrets_list(secrets_list_t *sl)
{
	if(sl) {
		bzero(sl,sizeof(secrets_list_t));
	}
}

void clean_secrets_list(secrets_list_t *sl)
{
	if(sl) {
		if(sl->secrets) {
			size_t i = 0;
			for(i = 0;i<sl->sz;++i) {
				if(sl->secrets[i]) {
					free(sl->secrets[i]);
				}
			}
			free(sl->secrets);
			sl->secrets = NULL;
			sl->sz = 0;
		}
	}
}

size_t get_secrets_list_size(secrets_list_t *sl)
{
	if(sl && sl->secrets) {
		return sl->sz;
	}
	return 0;
}

const char* get_secrets_list_elem(secrets_list_t *sl, size_t i)
{
	if(get_secrets_list_size(sl)>i) {
		return sl->secrets[i];
	}
	return NULL;
}

void add_to_secrets_list(secrets_list_t *sl, const char* elem)
{
	if(sl && elem) {
	  sl->secrets = (char**)realloc(sl->secrets,(sizeof(char*)*(sl->sz+1)));
	  sl->secrets[sl->sz] = strdup(elem);
	  sl->sz += 1;
	}
}

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

static int get_auth_secrets(secrets_list_t *sl, uint8_t *realm)
{
	int ret = -1;
  const turn_dbdriver_t * dbd = get_dbdriver();

	clean_secrets_list(sl);

	if(get_secrets_list_size(&turn_params.default_users_db.ram_db.static_auth_secrets)) {
		size_t i = 0;
		for(i=0;i<get_secrets_list_size(&turn_params.default_users_db.ram_db.static_auth_secrets);++i) {
			add_to_secrets_list(sl,get_secrets_list_elem(&turn_params.default_users_db.ram_db.static_auth_secrets,i));
		}
		ret=0;
	}
  
  if (dbd && dbd->get_auth_secrets) {
    ret = (*dbd->get_auth_secrets)(sl, realm);
  }

	return ret;
}

/*
 * Timestamp retrieval
 */
static turn_time_t get_rest_api_timestamp(char *usname)
{
	turn_time_t ts = 0;
	int ts_set = 0;

	char *col = strchr(usname,turn_params.rest_api_separator);

	if(col) {
		if(col == usname) {
			usname +=1;
		} else {
			char *ptr = usname;
			int found_non_figure = 0;
			while(ptr < col) {
				if(!(ptr[0]>='0' && ptr[0]<='9')) {
					found_non_figure=1;
					break;
				}
				++ptr;
			}
			if(found_non_figure) {
				ts = (turn_time_t)atol(col+1);
				ts_set = 1;
			} else {
				*col=0;
				ts = (turn_time_t)atol(usname);
				ts_set = 1;
				*col=turn_params.rest_api_separator;
			}
		}
	}

	if(!ts_set) {
		ts = (turn_time_t)atol(usname);
	}

	return ts;
}

static char *get_real_username(char *usname)
{
	if(usname[0] && turn_params.use_auth_secret_with_timestamp) {
		char *col=strchr(usname,turn_params.rest_api_separator);
		if(col) {
			if(col == usname) {
				usname +=1;
			} else {
				char *ptr = usname;
				int found_non_figure = 0;
				while(ptr < col) {
					if(!(ptr[0]>='0' && ptr[0]<='9')) {
						found_non_figure=1;
						break;
					}
					++ptr;
				}
				if(!found_non_figure) {
					usname = col+1;
				} else {
					*col=0;
					usname = strdup(usname);
					*col=turn_params.rest_api_separator;
					return usname;
				}
			}
		}
	}

	return strdup(usname);
}

/*
 * Password retrieval
 */
int get_user_key(int in_oauth, int *out_oauth, int *max_session_time, uint8_t *usname, uint8_t *realm, hmackey_t key, ioa_network_buffer_handle nbh)
{
	int ret = -1;

	if(max_session_time)
		*max_session_time = 0;

	if(in_oauth && out_oauth && usname && usname[0]) {

		stun_attr_ref sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(nbh),
								ioa_network_buffer_get_size(nbh),
								STUN_ATTRIBUTE_OAUTH_ACCESS_TOKEN);
		if(sar) {

			int len = stun_attr_get_len(sar);
			const uint8_t *value = stun_attr_get_value(sar);

			*out_oauth = 1;

			if(len>0 && value) {

				const turn_dbdriver_t * dbd = get_dbdriver();

				if (dbd && dbd->get_oauth_key) {

					oauth_key_data_raw rawKey;
					bzero(&rawKey,sizeof(rawKey));

					int gres = (*(dbd->get_oauth_key))(usname,&rawKey);
					if(gres<0)
						return ret;

					if(!rawKey.kid[0])
						return ret;

					if(rawKey.lifetime) {
						if(!turn_time_before(turn_time(),(turn_time_t)(rawKey.timestamp + rawKey.lifetime+OAUTH_TIME_DELTA))) {
							return ret;
						}
					}

					oauth_key_data okd;
					bzero(&okd,sizeof(okd));

					convert_oauth_key_data_raw(&rawKey, &okd);

					char err_msg[1025] = "\0";
					size_t err_msg_size = sizeof(err_msg) - 1;

					oauth_key okey;
					bzero(&okey,sizeof(okey));

					if (convert_oauth_key_data(&okd, &okey, err_msg, err_msg_size) < 0) {
						TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s\n", err_msg);
						return -1;
					}

					oauth_token dot;
					bzero((&dot),sizeof(dot));

					encoded_oauth_token etoken;
					bzero(&etoken,sizeof(etoken));

					if((size_t)len > sizeof(etoken.token)) {
						TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Encoded oAuth token is too large\n");
						return -1;
					}
					bcopy(value,etoken.token,(size_t)len);
					etoken.size = (size_t)len;

					const char* server_name = (char*)turn_params.oauth_server_name;
					if(!(server_name && server_name[0])) {
						server_name = (char*)realm;
						if(!(server_name && server_name[0])) {
							TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot determine oAuth server name");
							return -1;
						}
					}

					if (decode_oauth_token((const uint8_t *) server_name, &etoken,&okey, &dot) < 0) {
						TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot decode oauth token\n");
						return -1;
					}

					switch(dot.enc_block.key_length) {
					case SHA1SIZEBYTES:
						break;
					case SHA256SIZEBYTES:
					case SHA384SIZEBYTES:
					case SHA512SIZEBYTES:
					default:
						TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong size of the MAC key in oAuth token(3): %d\n",(int)dot.enc_block.key_length);
						return -1;
					};

					password_t pwdtmp;
					if(stun_check_message_integrity_by_key_str(TURN_CREDENTIALS_LONG_TERM,
								ioa_network_buffer_data(nbh),
								ioa_network_buffer_get_size(nbh),
								dot.enc_block.mac_key,
								pwdtmp,
								SHATYPE_DEFAULT)>0) {

						turn_time_t lifetime = (turn_time_t)(dot.enc_block.lifetime);
						if(lifetime) {
							turn_time_t ts = (turn_time_t)(dot.enc_block.timestamp >> 16);
							turn_time_t to = ts + lifetime + OAUTH_TIME_DELTA;
							turn_time_t ct = turn_time();
							if(!turn_time_before(ct,to)) {
								TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "oAuth token is too old\n");
								return -1;
							}
							if(max_session_time) {
								*max_session_time = to - ct;
							}
						}

						bcopy(dot.enc_block.mac_key,key,dot.enc_block.key_length);

						if(rawKey.realm[0]) {
							bcopy(rawKey.realm,realm,sizeof(rawKey.realm));
						}

						ret = 0;
					}
				}
			}
		}
	}

	if(out_oauth && *out_oauth) {
		return ret;
	}

	if(turn_params.use_auth_secret_with_timestamp) {

		turn_time_t ctime = (turn_time_t) time(NULL);
		turn_time_t ts = 0;
		secrets_list_t sl;
		size_t sll = 0;

		init_secrets_list(&sl);

		if(get_auth_secrets(&sl, realm)<0)
			return ret;

		ts = get_rest_api_timestamp((char*)usname);

		if(!turn_time_before(ts, ctime)) {

			uint8_t hmac[MAXSHASIZE];
			unsigned int hmac_len;
			password_t pwdtmp;

			hmac[0] = 0;

			stun_attr_ref sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(nbh),
							ioa_network_buffer_get_size(nbh),
							STUN_ATTRIBUTE_MESSAGE_INTEGRITY);
			if (!sar)
				return -1;

			int sarlen = stun_attr_get_len(sar);
			switch(sarlen) {
			case SHA1SIZEBYTES:
				hmac_len = SHA1SIZEBYTES;
				break;
			case SHA256SIZEBYTES:
			case SHA384SIZEBYTES:
			case SHA512SIZEBYTES:
			default:
				return -1;
			};

			for(sll=0;sll<get_secrets_list_size(&sl);++sll) {

				const char* secret = get_secrets_list_elem(&sl,sll);

				if(secret) {
					if(stun_calculate_hmac(usname, strlen((char*)usname), (const uint8_t*)secret, strlen(secret), hmac, &hmac_len, SHATYPE_DEFAULT)>=0) {
						size_t pwd_length = 0;
						char *pwd = base64_encode(hmac,hmac_len,&pwd_length);

						if(pwd) {
							if(pwd_length<1) {
								free(pwd);
							} else {
								if(stun_produce_integrity_key_str((uint8_t*)usname, realm, (uint8_t*)pwd, key, SHATYPE_DEFAULT)>=0) {

									if(stun_check_message_integrity_by_key_str(TURN_CREDENTIALS_LONG_TERM,
										ioa_network_buffer_data(nbh),
										ioa_network_buffer_get_size(nbh),
										key,
										pwdtmp,
										SHATYPE_DEFAULT)>0) {

										ret = 0;
									}
								}
								free(pwd);

								if(ret==0)
									break;
							}
						}
					}
				}
			}
		}

		clean_secrets_list(&sl);

		return ret;
	}

	ur_string_map_value_type ukey = NULL;
	ur_string_map_lock(turn_params.default_users_db.ram_db.static_accounts);
	if(ur_string_map_get(turn_params.default_users_db.ram_db.static_accounts, (ur_string_map_key_type)usname, &ukey)) {
		ret = 0;
	}
	ur_string_map_unlock(turn_params.default_users_db.ram_db.static_accounts);

	if(ret==0) {
		size_t sz = get_hmackey_size(SHATYPE_DEFAULT);
		bcopy(ukey,key,sz);
		return 0;
	}

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->get_user_key) {
		ret = (*(dbd->get_user_key))(usname, realm, key);
	}

	return ret;
}

uint8_t *start_user_check(turnserver_id id, turn_credential_type ct, int in_oauth, int *out_oauth, uint8_t *usname, uint8_t *realm, get_username_resume_cb resume, ioa_net_data *in_buffer, uint64_t ctxkey, int *postpone_reply)
{
	*postpone_reply = 1;

	struct auth_message am;
	bzero(&am,sizeof(struct auth_message));
	am.id = id;
	am.ct = ct;
	am.in_oauth = in_oauth;
	am.out_oauth = *out_oauth;
	STRCPY(am.username,usname);
	STRCPY(am.realm,realm);
	am.resume_func = resume;
	memcpy(&(am.in_buffer),in_buffer,sizeof(ioa_net_data));
	in_buffer->nbh = NULL;
	am.ctxkey = ctxkey;

	send_auth_message_to_auth_server(&am);

	return NULL;
}

int check_new_allocation_quota(uint8_t *user, int oauth, uint8_t *realm)
{
	int ret = 0;
	if (user || oauth) {
		uint8_t *username = oauth ? (uint8_t*)strdup("") : (uint8_t*)get_real_username((char*)user);
		realm_params_t *rp = get_realm((char*)realm);
		ur_string_map_lock(rp->status.alloc_counters);
		if (rp->options.perf_options.total_quota && (rp->status.total_current_allocs >= rp->options.perf_options.total_quota)) {
			ret = -1;
		} else if(username[0]){
			ur_string_map_value_type value = 0;
			if (!ur_string_map_get(rp->status.alloc_counters, (ur_string_map_key_type) username, &value)) {
				value = (ur_string_map_value_type) 1;
				ur_string_map_put(rp->status.alloc_counters, (ur_string_map_key_type) username, value);
				++(rp->status.total_current_allocs);
			} else {
				if ((rp->options.perf_options.user_quota) && ((size_t) value >= (size_t)(rp->options.perf_options.user_quota))) {
					ret = -1;
				} else {
					value = (ur_string_map_value_type)(((size_t)value) + 1);
					ur_string_map_put(rp->status.alloc_counters, (ur_string_map_key_type) username, value);
					++(rp->status.total_current_allocs);
				}
			}
		} else {
			++(rp->status.total_current_allocs);
		}
		free(username);
		ur_string_map_unlock(rp->status.alloc_counters);
	}
	return ret;
}

void release_allocation_quota(uint8_t *user, int oauth, uint8_t *realm)
{
	if (user) {
		uint8_t *username = oauth ? (uint8_t*)strdup("") : (uint8_t*)get_real_username((char*)user);
		realm_params_t *rp = get_realm((char*)realm);
		ur_string_map_lock(rp->status.alloc_counters);
		if(username[0]) {
			ur_string_map_value_type value = 0;
			ur_string_map_get(rp->status.alloc_counters, (ur_string_map_key_type) username, &value);
			if (value) {
				value = (ur_string_map_value_type)(((size_t)value) - 1);
				ur_string_map_put(rp->status.alloc_counters, (ur_string_map_key_type) username, value);
			}
		}
		if (rp->status.total_current_allocs)
			--(rp->status.total_current_allocs);
		ur_string_map_unlock(rp->status.alloc_counters);
		free(username);
	}
}

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

int add_static_user_account(char *user)
{
	/* Realm is either default or empty for users taken from file or command-line */
	if(user && !turn_params.use_auth_secret_with_timestamp) {
		char *s = strstr(user, ":");
		if(!s || (s==user) || (strlen(s)<2)) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong user account: %s\n",user);
		} else {
			size_t ulen = s-user;
			char *usname = (char*)malloc(sizeof(char)*(ulen+1));
			strncpy(usname,user,ulen);
			usname[ulen]=0;
			if(SASLprep((uint8_t*)usname)<0) {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong user name: %s\n",user);
				free(usname);
				return -1;
			}
			s = skip_blanks(s+1);
			hmackey_t *key = (hmackey_t*)malloc(sizeof(hmackey_t));
			if(strstr(s,"0x")==s) {
				char *keysource = s + 2;
				size_t sz = get_hmackey_size(SHATYPE_DEFAULT);
				if(strlen(keysource)<sz*2) {
					TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong key format: %s\n",s);
				} if(convert_string_key_to_binary(keysource, *key, sz)<0) {
					TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong key: %s\n",s);
					free(usname);
					free(key);
					return -1;
				}
			} else {
				//this is only for default realm
				stun_produce_integrity_key_str((uint8_t*)usname, (uint8_t*)get_realm(NULL)->options.name, (uint8_t*)s, *key, SHATYPE_DEFAULT);
			}
			{
				ur_string_map_lock(turn_params.default_users_db.ram_db.static_accounts);
				ur_string_map_put(turn_params.default_users_db.ram_db.static_accounts, (ur_string_map_key_type)usname, (ur_string_map_value_type)*key);
				ur_string_map_unlock(turn_params.default_users_db.ram_db.static_accounts);
			}
			turn_params.default_users_db.ram_db.users_number++;
			free(usname);
			return 0;
		}
	}

	return -1;
}

////////////////// Admin /////////////////////////

static int list_users(uint8_t *realm, int is_admin)
{
  const turn_dbdriver_t * dbd = get_dbdriver();
  if (dbd) {
	  if(is_admin) {
		  if(dbd->list_admin_users) {
		  	(*dbd->list_admin_users)(0);
		  }
	  } else {
		  if(dbd->list_users) {
			  (*dbd->list_users)(realm,NULL,NULL);
		  }
	  }
  }

  return 0;
}

static int show_secret(uint8_t *realm)
{
  const turn_dbdriver_t * dbd = get_dbdriver();
  if (dbd && dbd->list_secrets) {
    (*dbd->list_secrets)(realm,NULL,NULL);
  }

  return 0;
}

static int del_secret(uint8_t *secret, uint8_t *realm) {

	must_set_admin_realm(realm);

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->del_secret) {
		(*dbd->del_secret)(secret, realm);
	}

	return 0;
}

static int set_secret(uint8_t *secret, uint8_t *realm) {

	if(!secret || (secret[0]==0))
		return 0;

	must_set_admin_realm(realm);

	del_secret(secret, realm);

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->set_secret) {
		(*dbd->set_secret)(secret, realm);
	}

	return 0;
}

static int add_origin(uint8_t *origin0, uint8_t *realm)
{
	uint8_t origin[STUN_MAX_ORIGIN_SIZE+1];

	get_canonic_origin((const char *)origin0, (char *)origin, sizeof(origin)-1);

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->add_origin) {
		(*dbd->add_origin)(origin, realm);
	}

	return 0;
}

static int del_origin(uint8_t *origin0)
{
	uint8_t origin[STUN_MAX_ORIGIN_SIZE+1];

	get_canonic_origin((const char *)origin0, (char *)origin, sizeof(origin)-1);

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->del_origin) {
		(*dbd->del_origin)(origin);
	}

	return 0;
}

static int list_origins(uint8_t *realm)
{
  const turn_dbdriver_t * dbd = get_dbdriver();
  if (dbd && dbd->list_origins) {
    (*dbd->list_origins)(realm,NULL,NULL);
  }

  return 0;
}

static int set_realm_option_one(uint8_t *realm, unsigned long value, const char* opt)
{
	if(value == (unsigned long)-1)
		return 0;

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->set_realm_option_one) {
		(*dbd->set_realm_option_one)(realm, value, opt);
	}

	return 0;
}

static int set_realm_option(uint8_t *realm, perf_options_t *po)
{
	set_realm_option_one(realm,(unsigned long)po->max_bps,"max-bps");
	set_realm_option_one(realm,(unsigned long)po->user_quota,"user-quota");
	set_realm_option_one(realm,(unsigned long)po->total_quota,"total-quota");
	return 0;
}

static int list_realm_options(uint8_t *realm)
{
  const turn_dbdriver_t * dbd = get_dbdriver();
  if (dbd && dbd->list_realm_options) {
    (*dbd->list_realm_options)(realm);
	}

	return 0;
}

int adminuser(uint8_t *user, uint8_t *realm, uint8_t *pwd, uint8_t *secret, uint8_t *origin, TURNADMIN_COMMAND_TYPE ct, perf_options_t *po, int is_admin)
{
	hmackey_t key;
	char skey[sizeof(hmackey_t) * 2 + 1];

	if (ct == TA_LIST_USERS) {
		return list_users(realm, is_admin);
	}

	if (ct == TA_LIST_ORIGINS) {
		return list_origins(realm);
	}

	if (ct == TA_SHOW_SECRET) {
		return show_secret(realm);
	}

	if (ct == TA_SET_SECRET) {
		return set_secret(secret, realm);
	}

	if (ct == TA_DEL_SECRET) {
		return del_secret(secret, realm);
	}

	if (ct == TA_ADD_ORIGIN) {
		must_set_admin_origin(origin);
		must_set_admin_realm(realm);
		return add_origin(origin, realm);
	}

	if (ct == TA_DEL_ORIGIN) {
		must_set_admin_origin(origin);
		return del_origin(origin);
	}

	if (ct == TA_SET_REALM_OPTION) {
		must_set_admin_realm(realm);
		if (!(po && (po->max_bps != ((band_limit_t) -1) || po->total_quota >= 0 || po->user_quota >= 0))) {
			fprintf(stderr, "The operation cannot be completed: a realm option must be set.\n");
			exit(-1);
		}
		return set_realm_option(realm, po);
	}

	if (ct == TA_LIST_REALM_OPTIONS) {
		return list_realm_options(realm);
	}

	must_set_admin_user(user);

	if (ct != TA_DELETE_USER && !is_admin) {

		must_set_admin_pwd(pwd);

		{
			stun_produce_integrity_key_str(user, realm, pwd, key, SHATYPE_DEFAULT);
			size_t i = 0;
			size_t sz = get_hmackey_size(SHATYPE_DEFAULT);
			int maxsz = (int) (sz * 2) + 1;
			char *s = skey;
			for (i = 0; (i < sz) && (maxsz > 2); i++) {
				snprintf(s, (size_t) (sz * 2), "%02x", (unsigned int) key[i]);
				maxsz -= 2;
				s += 2;
			}
			skey[sz * 2] = 0;
		}
	}

	const turn_dbdriver_t * dbd = get_dbdriver();

	if (ct == TA_PRINT_KEY) {

		printf("0x%s\n", skey);

	} else if (dbd) {

		if(!is_admin)
			must_set_admin_realm(realm);

		if (ct == TA_DELETE_USER) {
			if(is_admin) {
				if (dbd->del_admin_user)
					(*dbd->del_admin_user)(user);
			} else {
				if (dbd->del_user)
					(*dbd->del_user)(user, realm);
			}
		} else if (ct == TA_UPDATE_USER) {
			if(is_admin) {
				must_set_admin_pwd(pwd);
				if (dbd->set_admin_user) {
					password_t password;
					generate_new_enc_password((char*)pwd,(char*)password);
					(*dbd->set_admin_user)(user, realm, password);
				}
			} else {
				if (dbd->set_user_key)
					(*dbd->set_user_key)(user, realm, skey);
			}
		}

	}

	return 0;
}

/////////// PING //////////////

void auth_ping(redis_context_handle rch)
{
  const turn_dbdriver_t * dbd = get_dbdriver();
  if (dbd && dbd->auth_ping) {
    (*dbd->auth_ping)(rch);
  }
}

///////////////// TEST /////////////////

#if defined(DB_TEST)

void run_db_test(void)
{
	turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd) {

		printf("DB TEST 1:\n");
		dbd->list_oauth_keys();

		printf("DB TEST 2:\n");
		oauth_key_data_raw key_;
		oauth_key_data_raw *key=&key_;
		dbd->get_oauth_key((const uint8_t*)"north",key);
		printf("  kid=%s, ikm_key=%s, timestamp=%llu, lifetime=%lu, as_rs_alg=%s\n",
		    		key->kid, key->ikm_key, (unsigned long long)key->timestamp, (unsigned long)key->lifetime, key->as_rs_alg);

		printf("DB TEST 3:\n");

		STRCPY(key->as_rs_alg,"as_rs_alg");
		STRCPY(key->ikm_key,"ikm_key");
		STRCPY(key->kid,"kid");
		key->timestamp = 123;
		key->lifetime = 456;
		dbd->del_oauth_key((const uint8_t*)"kid");
		dbd->set_oauth_key(key);
		dbd->list_oauth_keys();

		printf("DB TEST 4:\n");
		dbd->get_oauth_key((const uint8_t*)"kid",key);
		printf("  kid=%s, ikm_key=%s, timestamp=%llu, lifetime=%lu, as_rs_alg=%s\n",
		    		key->kid, key->ikm_key, (unsigned long long)key->timestamp, (unsigned long)key->lifetime, key->as_rs_alg);

		printf("DB TEST 5:\n");
		dbd->del_oauth_key((const uint8_t*)"kid");
		dbd->list_oauth_keys();

		printf("DB TEST 6:\n");

		dbd->get_oauth_key((const uint8_t*)"north",key);

		oauth_key_data oakd;
		convert_oauth_key_data_raw(key, &oakd);
		printf("  kid=%s, ikm_key=%s, timestamp=%llu, lifetime=%lu, as_rs_alg=%s\n",
				    		oakd.kid, oakd.ikm_key, (unsigned long long)oakd.timestamp, (unsigned long)oakd.lifetime, oakd.as_rs_alg);

		oauth_key oak;
		char err_msg[1025];
		err_msg[0]=0;
		if(convert_oauth_key_data(&oakd,&oak,err_msg,sizeof(err_msg)-1)<0) {
			printf("  ERROR: %s\n",err_msg);
		} else {
			printf("  OK!\n");
		}
		printf("DB TEST END\n");
	}
}

#endif

///////////////// WHITE/BLACK IP LISTS ///////////////////

#if !defined(TURN_NO_RWLOCK)
static pthread_rwlock_t* whitelist_rwlock = NULL;
static pthread_rwlock_t* blacklist_rwlock = NULL;
#else
static turn_mutex whitelist_mutex;
static turn_mutex blacklist_mutex;
#endif

static ip_range_list_t* ipwhitelist = NULL;
static ip_range_list_t* ipblacklist = NULL;

void init_dynamic_ip_lists(void)
{
#if !defined(TURN_NO_RWLOCK)
	whitelist_rwlock = (pthread_rwlock_t*) malloc(sizeof(pthread_rwlock_t));
	pthread_rwlock_init(whitelist_rwlock, NULL);

	blacklist_rwlock = (pthread_rwlock_t*) malloc(sizeof(pthread_rwlock_t));
	pthread_rwlock_init(blacklist_rwlock, NULL);
#else
	turn_mutex_init(&whitelist_mutex);
	turn_mutex_init(&blacklist_mutex);
#endif
}

void ioa_lock_whitelist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
#if !defined(TURN_NO_RWLOCK)
	pthread_rwlock_rdlock(whitelist_rwlock);
#else
	turn_mutex_lock(&whitelist_mutex);
#endif
}
void ioa_unlock_whitelist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
#if !defined(TURN_NO_RWLOCK)
	pthread_rwlock_unlock(whitelist_rwlock);
#else
	turn_mutex_unlock(&whitelist_mutex);
#endif
}
static void ioa_wrlock_whitelist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
#if !defined(TURN_NO_RWLOCK)
	pthread_rwlock_wrlock(whitelist_rwlock);
#else
	turn_mutex_lock(&whitelist_mutex);
#endif
}
const ip_range_list_t* ioa_get_whitelist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
	return ipwhitelist;
}

void ioa_lock_blacklist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
#if !defined(TURN_NO_RWLOCK)
	pthread_rwlock_rdlock(blacklist_rwlock);
#else
	turn_mutex_lock(&blacklist_mutex);
#endif
}
void ioa_unlock_blacklist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
#if !defined(TURN_NO_RWLOCK)
	pthread_rwlock_unlock(blacklist_rwlock);
#else
	turn_mutex_unlock(&blacklist_mutex);
#endif
}
static void ioa_wrlock_blacklist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
#if !defined(TURN_NO_RWLOCK)
	pthread_rwlock_wrlock(blacklist_rwlock);
#else
	turn_mutex_lock(&blacklist_mutex);
#endif
}
const ip_range_list_t* ioa_get_blacklist(ioa_engine_handle e)
{
	UNUSED_ARG(e);
	return ipblacklist;
}

ip_range_list_t* get_ip_list(const char *kind)
{
	ip_range_list_t *ret = (ip_range_list_t*) malloc(sizeof(ip_range_list_t));
	bzero(ret,sizeof(ip_range_list_t));

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->get_ip_list && !turn_params.no_dynamic_ip_list) {
		(*dbd->get_ip_list)(kind, ret);
	}

	return ret;
}

void ip_list_free(ip_range_list_t *l)
{
	if(l) {
		if(l->rs)
		  free(l->rs);
		free(l);
	}
}

void update_white_and_black_lists(void)
{
	{
		ip_range_list_t *wl = get_ip_list("allowed");
		ip_range_list_t *owl = NULL;
		ioa_wrlock_whitelist(NULL);
		owl = ipwhitelist;
		ipwhitelist = wl;
		ioa_unlock_whitelist(NULL);
		ip_list_free(owl);
	}
	{
		ip_range_list_t *bl = get_ip_list("denied");
		ip_range_list_t *obl = NULL;
		ioa_wrlock_blacklist(NULL);
		obl = ipblacklist;
		ipblacklist = bl;
		ioa_unlock_blacklist(NULL);
		ip_list_free(obl);
	}
}

/////////////// add ACL record ///////////////////

int add_ip_list_range(const char * range0, const char * realm, ip_range_list_t * list)
{
	char *range = strdup(range0);

	char* separator = strchr(range, '-');

	if (separator) {
		*separator = '\0';
	}

	ioa_addr min, max;

	if (make_ioa_addr((const uint8_t*) range, 0, &min) < 0) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address format: %s\n", range);
		free(range);
		return -1;
	}

	if (separator) {
		if (make_ioa_addr((const uint8_t*) separator + 1, 0, &max) < 0) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address format: %s\n", separator + 1);
			free(range);
			return -1;
		}
	} else {
		// Doesn't have a '-' character in it, so assume that this is a single address
		addr_cpy(&max, &min);
	}

	if (separator)
		*separator = '-';

	++(list->ranges_number);
	list->rs = (ip_range_t*) realloc(list->rs, sizeof(ip_range_t) * list->ranges_number);
	STRCPY(list->rs[list->ranges_number - 1].str,range);
	if(realm)
		STRCPY(list->rs[list->ranges_number - 1].realm,realm);
	else
		list->rs[list->ranges_number - 1].realm[0]=0;
	free(range);
	ioa_addr_range_set(&(list->rs[list->ranges_number - 1].enc), &min, &max);

	return 0;
}

int check_ip_list_range(const char * range0)
{
	char *range = strdup(range0);

	char* separator = strchr(range, '-');

	if (separator) {
		*separator = '\0';
	}

	ioa_addr min, max;

	if (make_ioa_addr((const uint8_t*) range, 0, &min) < 0) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address range format: %s\n", range);
		free(range);
		return -1;
	}

	if (separator) {
		if (make_ioa_addr((const uint8_t*) separator + 1, 0, &max) < 0) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address range format: %s\n", separator + 1);
			free(range);
			return -1;
		}
	} else {
		// Doesn't have a '-' character in it, so assume that this is a single address
		addr_cpy(&max, &min);
	}

	if (separator)
		*separator = '-';

	free(range);

	return 0;
}

/////////// REALM //////////////

void reread_realms(void)
{
	{
		realm_params_t* defrp = get_realm(NULL);
		lock_realms();
		defrp->options.perf_options.max_bps = turn_params.max_bps;
		defrp->options.perf_options.total_quota = turn_params.total_quota;
		defrp->options.perf_options.user_quota = turn_params.user_quota;
		unlock_realms();
	}

	const turn_dbdriver_t * dbd = get_dbdriver();
	if (dbd && dbd->reread_realms && !turn_params.no_dynamic_realms) {
		(*dbd->reread_realms)(&realms_list);
	}
}

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