/*
 * 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 "mainrelay.h"

//////TURN PARAMS STRUCTURE DEFINITION //////

#define DEFAULT_GENERAL_RELAY_SERVERS_NUMBER (1)

turn_params_t turn_params = {

NULL, NULL,

#if defined(SSL_TXT_TLSV1_1)
	NULL,
#if defined(SSL_TXT_TLSV1_2)
	NULL,
#endif
#endif

NULL,

SHATYPE_SHA1, DH_1066, "", DEFAULT_EC_CURVE_NAME, "",
"turn_server_cert.pem","turn_server_pkey.pem", "", "",
0,0,0,0,0,
#if defined(TURN_NO_TLS)
1,
#else
0,
#endif

#if defined(TURN_NO_DTLS)
1,
#else
0,
#endif

TURN_VERBOSE_NONE,0,0,0,0,0,0,0,
"/var/run/turnserver.pid",
DEFAULT_STUN_PORT,DEFAULT_STUN_TLS_PORT,0,0,1,
0,0,0,0,
"",
#if !defined(TURN_NO_HIREDIS)
"",0,
#endif
{
  NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL
#if !defined(TURN_NO_HIREDIS)
,NULL
#endif
},
{NULL, NULL, 0},{NULL, NULL, 0},
NEV_UNKNOWN, 
{ "Unknown", "UDP listening socket per session", "UDP thread per network endpoint", "UDP thread per CPU core" },
//////////////// Relay servers //////////////////////////////////
0,LOW_DEFAULT_PORTS_BOUNDARY,HIGH_DEFAULT_PORTS_BOUNDARY,0,0,"",
0,NULL,0,NULL,0,DEFAULT_GENERAL_RELAY_SERVERS_NUMBER,0,0,
////////////// Auth server /////////////////////////////////////
{NULL,NULL,NULL,0},
/////////////// AUX SERVERS ////////////////
{NULL,0,{0,NULL}},0,
/////////////// ALTERNATE SERVERS ////////////////
{NULL,0,{0,NULL}},{NULL,0,{0,NULL}},
/////////////// USERS ////////////////////////////
{
  TURN_USERDB_TYPE_FILE,"\0",0,0,0,0,
    {TURN_CREDENTIALS_NONE,0,0,0,NULL,NULL,NULL},
    "\0",
    0,':',
    {NULL,0}
},
/////////////// stop server ////////////////
  0
};

//////////////// OpenSSL Init //////////////////////

static void openssl_setup(void);

/*
 * openssl genrsa -out pkey 2048
 * openssl req -new -key pkey -out cert.req
 * openssl x509 -req -days 365 -in cert.req -signkey pkey -out cert
 *
*/

//////////// Common static process params ////////

static gid_t procgroupid = 0;
static uid_t procuserid = 0;
static gid_t procgroupid_set = 0;
static uid_t procuserid_set = 0;
static char procusername[1025]="\0";
static char procgroupname[1025]="\0";

////////////// Configuration functionality ////////////////////////////////

static void read_config_file(int argc, char **argv, int pass);

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

static int make_local_listeners_list(void)
{
	int ret = 0;

	struct ifaddrs * ifs = NULL;
	struct ifaddrs * ifa = NULL;

	char saddr[INET6_ADDRSTRLEN] = "";

	if((getifaddrs(&ifs) == 0) && ifs) {

		for (ifa = ifs; ifa != NULL; ifa = ifa->ifa_next) {

			if(!(ifa->ifa_flags & IFF_UP))
				continue;

			if(!(ifa->ifa_addr))
				continue;

			if (ifa ->ifa_addr->sa_family == AF_INET) {
				if(!inet_ntop(AF_INET, &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr, saddr,
								INET_ADDRSTRLEN))
					continue;
				if(strstr(saddr,"169.254.") == saddr)
					continue;
				if(!strcmp(saddr,"0.0.0.0"))
				  continue;
			} else if (ifa->ifa_addr->sa_family == AF_INET6) {
				if(!inet_ntop(AF_INET6, &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr, saddr,
								INET6_ADDRSTRLEN))
					continue;
				if(strstr(saddr,"fe80") == saddr)
					continue;
				if(!strcmp(saddr,"::"))
				  continue;
			} else {
				continue;
			}

			add_listener_addr(saddr);

			if(!(ifa->ifa_flags & IFF_LOOPBACK))
				ret++;
		}
		freeifaddrs(ifs);
	}

	return ret;
}

static int make_local_relays_list(int allow_local, int family)
{
	struct ifaddrs * ifs = NULL;
	struct ifaddrs * ifa = NULL;

	char saddr[INET6_ADDRSTRLEN] = "";

	getifaddrs(&ifs);

	int counter = 0;

	if (ifs) {
		for (ifa = ifs; ifa != NULL; ifa = ifa->ifa_next) {

			if(!(ifa->ifa_flags & IFF_UP))
				continue;

			if(!(ifa->ifa_name))
				continue;
			if(!(ifa ->ifa_addr))
				continue;

			if(!allow_local && (ifa->ifa_flags & IFF_LOOPBACK))
				continue;

			if (ifa ->ifa_addr->sa_family == AF_INET) {

				if(family != AF_INET)
					continue;

				if(!inet_ntop(AF_INET, &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr, saddr,
								INET_ADDRSTRLEN))
					continue;
				if(strstr(saddr,"169.254.") == saddr)
					continue;
				if(!strcmp(saddr,"0.0.0.0"))
				  continue;
			} else if (ifa->ifa_addr->sa_family == AF_INET6) {

				if(family != AF_INET6)
					continue;

				if(!inet_ntop(AF_INET6, &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr, saddr,
								INET6_ADDRSTRLEN))
					continue;
				if(strstr(saddr,"fe80") == saddr)
					continue;
				if(!strcmp(saddr,"::"))
				  continue;
			} else
				continue;

			if(add_relay_addr(saddr)>0) {
				counter += 1;
			}
		}
		freeifaddrs(ifs);
	}

	return counter;
}

int get_a_local_relay(int family, ioa_addr *relay_addr)
{
	struct ifaddrs * ifs = NULL;

	int allow_local = 0;

	int ret = -1;

	char saddr[INET6_ADDRSTRLEN] = "";

	getifaddrs(&ifs);

	if (ifs) {

		galr_start:

		{
			struct ifaddrs *ifa = NULL;

			for (ifa = ifs; ifa != NULL ; ifa = ifa->ifa_next) {

				if (!(ifa->ifa_flags & IFF_UP))
					continue;

				if (!(ifa->ifa_name))
					continue;
				if (!(ifa->ifa_addr))
					continue;

				if (!allow_local && (ifa->ifa_flags & IFF_LOOPBACK))
					continue;

				if (ifa->ifa_addr->sa_family == AF_INET) {

					if (family != AF_INET)
						continue;

					if (!inet_ntop(AF_INET,
							&((struct sockaddr_in *) ifa->ifa_addr)->sin_addr,
							saddr, INET_ADDRSTRLEN))
						continue;
					if (strstr(saddr, "169.254.") == saddr)
						continue;
					if (!strcmp(saddr, "0.0.0.0"))
						continue;
				} else if (ifa->ifa_addr->sa_family == AF_INET6) {

					if (family != AF_INET6)
						continue;

					if (!inet_ntop(AF_INET6,
							&((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr,
							saddr, INET6_ADDRSTRLEN))
						continue;
					if (strstr(saddr, "fe80") == saddr)
						continue;
					if (!strcmp(saddr, "::"))
						continue;
				} else
					continue;

				if (make_ioa_addr((const u08bits*) saddr, 0, relay_addr) < 0) {
					continue;
				} else {
					ret = 0;
					break;
				}
			}
		}

		if(ret<0 && !allow_local) {
			allow_local = 1;
			goto galr_start;
		}

		freeifaddrs(ifs);
	}

	return -1;
}

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

static char Usage[] = "Usage: turnserver [options]\n"
"Options:\n"
" -d, --listening-device	<device-name>		Listener interface device (NOT RECOMMENDED. Optional, Linux only).\n"
" -p, --listening-port		<port>		TURN listener port (Default: 3478).\n"
"						Note: actually, TLS & DTLS sessions can connect to the \"plain\" TCP & UDP port(s), too,\n"
"						if allowed by configuration.\n"
" --tls-listening-port		<port>		TURN listener port for TLS & DTLS listeners\n"
"						(Default: 5349).\n"
"						Note: actually, \"plain\" TCP & UDP sessions can connect to the TLS & DTLS port(s), too,\n"
"						if allowed by configuration. The TURN server\n"
"						\"automatically\" recognizes the type of traffic. Actually, two listening\n"
"						endpoints (the \"plain\" one and the \"tls\" one) are equivalent in terms of\n"
"						functionality; but we keep both endpoints to satisfy the RFC 5766 specs.\n"
"						For secure TCP connections, we currently support SSL version 3 and\n"
"						TLS versions 1.0, 1.1 and 1.2. For secure UDP connections, we support\n"
"						DTLS version 1.\n"
" --alt-listening-port<port>	<port>		Alternative listening port for STUN CHANGE_REQUEST (in RFC 5780 sense, \n"
"                                                or in old RFC 3489 sense, default is \"listening port plus one\").\n"
" --alt-tls-listening-port	<port>		Alternative listening port for TLS and DTLS,\n"
" 						the default is \"TLS/DTLS port plus one\".\n"
" -L, --listening-ip		<ip>		Listener IP address of relay server. Multiple listeners can be specified.\n"
" --aux-server			<ip:port>	Auxiliary STUN/TURN server listening endpoint.\n"
"						Auxiliary servers do not have alternative ports and\n"
"						they do not support RFC 5780 functionality (CHANGE REQUEST).\n"
"						Valid formats are 1.2.3.4:5555 for IPv4 and [1:2::3:4]:5555 for IPv6.\n"
" --udp-self-balance				(recommended for older Linuxes only) Automatically balance UDP traffic\n"
"						over auxiliary servers (if configured).\n"
"						The load balancing is using the ALTERNATE-SERVER mechanism.\n"
"						The TURN client must support 300 ALTERNATE-SERVER response for this functionality.\n"
" -i, --relay-device		<device-name>	Relay interface device for relay sockets (NOT RECOMMENDED. Optional, Linux only).\n"
" -E, --relay-ip		<ip>			Relay address (the local IP address that will be used to relay the\n"
"						packets to the peer).\n"
"						Multiple relay addresses may be used.\n"
"						The same IP(s) can be used as both listening IP(s) and relay IP(s).\n"
"						If no relay IP(s) specified, then the turnserver will apply the default\n"
"						policy: it will decide itself which relay addresses to be used, and it\n"
"						will always be using the client socket IP address as the relay IP address\n"
"						of the TURN session (if the requested relay address family is the same\n"
"						as the family of the client socket).\n"
" -X, --external-ip  <public-ip[/private-ip]>	TURN Server public/private address mapping, if the server is behind NAT.\n"
"						In that situation, if a -X is used in form \"-X ip\" then that ip will be reported\n"
"						as relay IP address of all allocations. This scenario works only in a simple case\n"
"						when one single relay address is be used, and no STUN CHANGE_REQUEST functionality is required.\n"
"						That single relay address must be mapped by NAT to the 'external' IP.\n"
"						For that 'external' IP, NAT must forward ports directly (relayed port 12345\n"
"						must be always mapped to the same 'external' port 12345).\n"
"						In more complex case when more than one IP address is involved,\n"
"						that option must be used several times in the command line, each entry must\n"
"						have form \"-X public-ip/private-ip\", to map all involved addresses.\n"
" --no-loopback-peers				Disallow peers on the loopback addresses (127.x.x.x and ::1).\n"
" --no-multicast-peers				Disallow peers on well-known broadcast addresses (224.0.0.0 and above, and FFXX:*).\n"
" -m, --relay-threads		<number>	Number of relay threads to handle the established connections\n"
"						(in addition to authentication thread and the listener thread).\n"
"						If set to 0 then application runs in single-threaded mode.\n"
"						The default thread number is the number of CPUs.\n"
"						In older systems (pre-Linux 3.9) the number of UDP relay threads always equals\n"
"						the number of listening endpoints (unless -m 0 is set).\n"
" --min-port			<port>		Lower bound of the UDP port range for relay endpoints allocation.\n"
"						Default value is 49152, according to RFC 5766.\n"
" --max-port			<port>		Upper bound of the UDP port range for relay endpoints allocation.\n"
"						Default value is 65535, according to RFC 5766.\n"
" -v, --verbose					'Moderate' verbose mode.\n"
" -V, --Verbose					Extra verbose mode, very annoying (for debug purposes only).\n"
" -o, --daemon					Start process as daemon (detach from current shell).\n"
" -f, --fingerprint				Use fingerprints in the TURN messages.\n"
" -a, --lt-cred-mech				Use the long-term credential mechanism. This option can be used with either\n"
"		                                flat file user database or PostgreSQL DB or MySQL DB for user keys storage.\n"
" -A, --st-cred-mech				Use the short-term credential mechanism. This option requires\n"
"		                                a PostgreSQL or MySQL DB for short term passwords storage.\n"
" -z, --no-auth					Do not use any credential mechanism, allow anonymous access.\n"
" -u, --user			<user:pwd>	User account, in form 'username:password', for long-term credentials.\n"
"						Cannot be used with TURN REST API or with short-term credentials.\n"
" -r, --realm			<realm>		Realm, for long-term credentials and for TURN REST API.\n"
" -q, --user-quota		<number>	Per-user allocation quota: how many concurrent allocations a user can create.\n"
" -Q, --total-quota		<number>	Total allocations quota: global limit on concurrent allocations.\n"
" -s, --max-bps			<number>	Max bytes-per-second bandwidth a TURN session is allowed to handle\n"
"						(input and output network streams are treated separately). Anything above\n"
"						that limit will be dropped or temporary suppressed\n"
"						(within the available buffer limits).\n"
" -c				<filename>	Configuration file name (default - turnserver.conf).\n"
" -b, --userdb			<filename>	User database file name (default - turnuserdb.conf) for long-term credentials only.\n"
#if !defined(TURN_NO_PQ)
" -e, --psql-userdb, --sql-userdb <conn-string>	PostgreSQL database connection string, if used (default - empty, no PostreSQL DB used).\n"
"		                                This database can be used for long-term and short-term credentials mechanisms,\n"
"		                                and it can store the secret value(s) for secret-based timed authentication in TURN RESP API.\n"
"						See http://www.postgresql.org/docs/8.4/static/libpq-connect.html for 8.x PostgreSQL\n"
"						versions format, see \n"
"						http://www.postgresql.org/docs/9.2/static/libpq-connect.html#LIBPQ-CONNSTRING\n"
"						for 9.x and newer connection string formats.\n"
#endif
#if !defined(TURN_NO_MYSQL)
" -M, --mysql-userdb	<connection-string>	MySQL database connection string, if used (default - empty, no MySQL DB used).\n"
"	                                	This database can be used for long-term and short-term credentials mechanisms,\n"
"		                                and it can store the secret value(s) for secret-based timed authentication in TURN RESP API.\n"
"						The connection string my be space-separated list of parameters:\n"
"	        	          		\"host=<ip-addr> dbname=<database-name> user=<database-user> \\\n								password=<database-user-password> port=<db-port> connect_timeout=<seconds>\".\n"
"	        	          		All parameters are optional.\n"
#endif
#if !defined(TURN_NO_HIREDIS)
" -N, --redis-userdb	<connection-string>	Redis user database connection string, if used (default - empty, no Redis DB used).\n"
"	                                	This database can be used for long-term and short-term credentials mechanisms,\n"
"		                                and it can store the secret value(s) for secret-based timed authentication in TURN RESP API.\n"
"						The connection string my be space-separated list of parameters:\n"
"	        	          		\"host=<ip-addr> dbname=<db-number> \\\n								password=<database-user-password> port=<db-port> connect_timeout=<seconds>\".\n"
"	        	          		All parameters are optional.\n"
" -O, --redis-statsdb	<connection-string>	Redis status and statistics database connection string, if used \n"
"						(default - empty, no Redis stats DB used).\n"
"	                                	This database keeps allocations status information, and it can be also used for publishing\n"
"		                                and delivering traffic and allocation event notifications.\n"
"						The connection string has the same parameters as redis-userdb connection string.\n"
#endif
" --use-auth-secret				TURN REST API flag.\n"
"						Flag that sets a special authorization option that is based upon authentication secret\n"
"						(TURN Server REST API, see TURNServerRESTAPI.pdf). This option is used with timestamp.\n"
" --static-auth-secret		<secret>	'Static' authentication secret value (a string) for TURN REST API only.\n"
"						If not set, then the turn server will try to use the 'dynamic' value\n"
"						in turn_secret table in user database (if present).\n"
"						That database value can be changed on-the-fly\n"
"						by a separate program, so this is why it is 'dynamic'.\n"
"						Multiple shared secrets can be used (both in the database and in the \"static\" fashion).\n"
" -n						Do not use configuration file, take all parameters from the command line only.\n"
" --cert			<filename>		Certificate file, PEM format. Same file search rules\n"
"						applied as for the configuration file.\n"
"						If both --no-tls and --no_dtls options\n"
"						are specified, then this parameter is not needed.\n"
" --pkey			<filename>		Private key file, PEM format. Same file search rules\n"
"						applied as for the configuration file.\n"
"						If both --no-tls and --no-dtls options\n"
" --pkey-pwd		<password>		If the private key file is encrypted, then this password to be used.\n"
" --cipher-list	<\"cipher-string\">		Allowed OpenSSL cipher list for TLS/DTLS connections.\n"
"						Default value is \"DEFAULT\".\n"
" --CA-file		<filename>		CA file in OpenSSL format.\n"
"						Forces TURN server to verify the client SSL certificates.\n"
"						By default, no CA is set and no client certificate check is performed.\n"
" --ec-curve-name	<curve-name>		Curve name for EC ciphers, if supported by OpenSSL library\n"
"						(TLS and DTLS). The default value is prime256v1.\n"
" --dh566					Use 566 bits predefined DH TLS key. Default size of the predefined key is 1066.\n"
" --dh2066					Use 2066 bits predefined DH TLS key. Default size of the predefined key is 1066.\n"
" --dh-file	<dh-file-name>			Use custom DH TLS key, stored in PEM format in the file.\n"
"						Flags --dh566 and --dh2066 are ignored when the DH key is taken from a file.\n"
" --no-sslv2					Do not allow SSLv2 protocol.\n"
" --no-sslv3					Do not allow SSLv3 protocol.\n"
" --no-tlsv1					Do not allow TLSv1 protocol.\n"
" --no-tlsv1_1					Do not allow TLSv1.1 protocol.\n"
" --no-tlsv1_2					Do not allow TLSv1.2 protocol.\n"
" --no-udp					Do not start UDP client listeners.\n"
" --no-tcp					Do not start TCP client listeners.\n"
" --no-tls					Do not start TLS client listeners.\n"
" --no-dtls					Do not start DTLS client listeners.\n"
" --no-udp-relay					Do not allow UDP relay endpoints, use only TCP relay option.\n"
" --no-tcp-relay					Do not allow TCP relay endpoints, use only UDP relay options.\n"
" -l, --log-file		<filename>		Option to set the full path name of the log file.\n"
"						By default, the turnserver tries to open a log file in\n"
"						/var/log/turnserver/, /var/log, /var/tmp, /tmp and . (current) directories\n"
"						(which open operation succeeds first that file will be used).\n"
"						With this option you can set the definite log file name.\n"
"						The special names are \"stdout\" and \"-\" - they will force everything\n"
"						to the stdout; and \"syslog\" name will force all output to the syslog.\n"
" --no-stdout-log				Flag to prevent stdout log messages.\n"
"						By default, all log messages are going to both stdout and to\n"
"						a log file. With this option everything will be going to the log file only\n"
"						(unless the log file itself is stdout).\n"
" --syslog					Output all log information into the system log (syslog), do not use the file output.\n"
" --stale-nonce					Use extra security with nonce value having limited lifetime (600 secs).\n"
" -S, --stun-only				Option to set standalone STUN operation only, all TURN requests will be ignored.\n"
"     --no-stun					Option to suppress STUN functionality, only TURN requests will be processed.\n"
" --alternate-server		<ip:port>	Set the TURN server to redirect the allocate requests (UDP and TCP services).\n"
"						Multiple alternate-server options can be set for load balancing purposes.\n"
"						See the docs for more information.\n"
" --tls-alternate-server	<ip:port>		Set the TURN server to redirect the allocate requests (DTLS and TLS services).\n"
"						Multiple alternate-server options can be set for load balancing purposes.\n"
"						See the docs for more information.\n"
" -C, --rest-api-separator	<SYMBOL>	This is the username/timestamp separator symbol (character) in TURN REST API.\n"
"						The default value is ':'.\n"
"     --max-allocate-timeout=<seconds>		Max time, in seconds, allowed for full allocation establishment. Default is 60.\n"
"     --allowed-peer-ip=<ip[-ip]> 		Specifies an ip or range of ips that are explicitly allowed to connect to the \n"
"						turn server. Multiple allowed-peer-ip can be set.\n"
"     --denied-peer-ip=<ip[-ip]> 		Specifies an ip or range of ips that are not allowed to connect to the turn server.\n"
"						Multiple denied-peer-ip can be set.\n"
" --pidfile <\"pid-file-name\">			File name to store the pid of the process.\n"
"						Default is /var/run/turnserver.pid (if superuser account is used) or\n"
"						/var/tmp/turnserver.pid .\n"
" --secure-stun					Require authentication of the STUN Binding request.\n"
"						By default, the clients are allowed anonymous access to the STUN Binding functionality.\n"
" --sha256					Require SHA256 digest function to be used for the message integrity.\n"
"						By default, the server SHA1 (as per TURN standard specs).\n"
"						With this option, the server\n"
"						requires the stronger SHA256 function. The client application must\n"
"						support SHA256 hash function if this option is used. If the server obtains\n"
"						a message from the client with a weaker (SHA1) hash function then the server\n"
"						returns error code 426.\n"
" --proc-user <user-name>			User name to run the turnserver process.\n"
"						After the initialization, the turnserver process\n"
"						will make an attempt to change the current user ID to that user.\n"
" --proc-group <group-name>			Group name to run the turnserver process.\n"
"						After the initialization, the turnserver process\n"
"						will make an attempt to change the current group ID to that group.\n"
" --mobility					Mobility with ICE (MICE) specs support.\n"
" --no-cli					Turn OFF the CLI support. By default it is always ON.\n"
" --cli-ip=<IP>					Local system IP address to be used for CLI server endpoint. Default value\n"
"						is 127.0.0.1.\n"
" --cli-port=<port>				CLI server port. Default is 5766.\n"
" --cli-password=<password>			CLI access password. Default is empty (no password).\n"
" --server-relay					Server relay. NON-STANDARD AND DANGEROUS OPTION. Only for those applications\n"
"						when we want to run server applications on the relay endpoints.\n"
"						This option eliminates the IP permissions check on the packets\n"
"						incoming to the relay endpoints.\n"
" --cli-max-output-sessions			Maximum number of output sessions in ps CLI command.\n"
"						This value can be changed on-the-fly in CLI. The default value is 256.\n"
" --ne=[1|2|3]					Set network engine type for the process (for internal purposes).\n"
" -h						Help\n"
"\n"
" For more information, see the wiki pages:\n"
"\n"
"	http://code.google.com/p/rfc5766-turn-server/w/list\n"
"\n";

static char AdminUsage[] = "Usage: turnadmin [command] [options]\n"
	"Commands:\n"
	"	-k, --key			generate long-term credential mechanism key for a user\n"
	"	-a, --add			add/update a long-term mechanism user\n"
	"	-A, --add-st			add/update a short-term mechanism user\n"
	"	-d, --delete			delete a long-term mechanism user\n"
	"	-D, --delete-st			delete a short-term mechanism user\n"
	"	-l, --list			list all long-term mechanism users\n"
	"	-L, --list-st			list all short-term mechanism users\n"
#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL)
	"	-s, --set-secret=<value>	Add shared secret for TURN RESP API\n"
	"	-S, --show-secret		Show stored shared secrets for TURN REST API\n"
	"	-X, --delete-secret=<value>	Delete a shared secret\n"
	"	    --delete-all-secrets	Delete all shared secrets for REST API\n"
#endif
	"Options:\n"
	"	-b, --userdb			User database file, if flat DB file is used.\n"
#if !defined(TURN_NO_PQ)
	"	-e, --psql-userdb, --sql-userdb	PostgreSQL user database connection string, if PostgreSQL DB is used.\n"
#endif
#if !defined(TURN_NO_MYSQL)
	"	-M, --mysql-userdb		MySQL user database connection string, if MySQL DB is used.\n"
#endif
#if !defined(TURN_NO_HIREDIS)
	"	-N, --redis-userdb		Redis user database connection string, if Redis DB is used.\n"
#endif
	"	-u, --user			Username\n"
	"	-r, --realm			Realm for long-term mechanism only\n"
	"	-p, --password			Password\n"
	"	-H, --sha256			Use SHA256 digest function to be used for the message integrity.\n"
	"					By default, the server SHA1 (as per TURN standard specs).\n"
	"	-h, --help			Help\n";

#define OPTIONS "c:d:p:L:E:X:i:m:l:r:u:b:e:M:N:O:q:Q:s:C:vVofhznaAS"

#define ADMIN_OPTIONS "HlLkaADSdb:e:M:N:u:r:p:s:X:h"

enum EXTRA_OPTS {
	NO_UDP_OPT=256,
	NO_TCP_OPT,
	NO_TLS_OPT,
	NO_DTLS_OPT,
	NO_UDP_RELAY_OPT,
	NO_TCP_RELAY_OPT,
	TLS_PORT_OPT,
	ALT_PORT_OPT,
	ALT_TLS_PORT_OPT,
	CERT_FILE_OPT,
	PKEY_FILE_OPT,
	PKEY_PWD_OPT,
	MIN_PORT_OPT,
	MAX_PORT_OPT,
	STALE_NONCE_OPT,
	AUTH_SECRET_OPT,
	DEL_ALL_AUTH_SECRETS_OPT,
	STATIC_AUTH_SECRET_VAL_OPT,
	AUTH_SECRET_TS_EXP, /* deprecated */
	NO_STDOUT_LOG_OPT,
	SYSLOG_OPT,
	AUX_SERVER_OPT,
	UDP_SELF_BALANCE_OPT,
	ALTERNATE_SERVER_OPT,
	TLS_ALTERNATE_SERVER_OPT,
	NO_MULTICAST_PEERS_OPT,
	NO_LOOPBACK_PEERS_OPT,
	MAX_ALLOCATE_TIMEOUT_OPT,
	ALLOWED_PEER_IPS,
	DENIED_PEER_IPS,
	CIPHER_LIST_OPT,
	PIDFILE_OPT,
	SECURE_STUN_OPT,
	CA_FILE_OPT,
	DH_FILE_OPT,
	SHA256_OPT,
	NO_STUN_OPT,
	PROC_USER_OPT,
	PROC_GROUP_OPT,
	MOBILITY_OPT,
	NO_CLI_OPT,
	CLI_IP_OPT,
	CLI_PORT_OPT,
	CLI_PASSWORD_OPT,
	SERVER_RELAY_OPT,
	CLI_MAX_SESSIONS_OPT,
	EC_CURVE_NAME_OPT,
	DH566_OPT,
	DH2066_OPT,
	NE_TYPE_OPT,
	NO_SSLV2_OPT,
	NO_SSLV3_OPT,
	NO_TLSV1_OPT,
	NO_TLSV1_1_OPT,
	NO_TLSV1_2_OPT
};

static struct option long_options[] = {
				{ "listening-device", required_argument, NULL, 'd' },
				{ "listening-port", required_argument, NULL, 'p' },
				{ "tls-listening-port", required_argument, NULL, TLS_PORT_OPT },
				{ "alt-listening-port", required_argument, NULL, ALT_PORT_OPT },
				{ "alt-tls-listening-port", required_argument, NULL, ALT_TLS_PORT_OPT },
				{ "listening-ip", required_argument, NULL, 'L' },
				{ "relay-device", required_argument, NULL, 'i' },
				{ "relay-ip", required_argument, NULL, 'E' },
				{ "external-ip", required_argument, NULL, 'X' },
				{ "relay-threads", required_argument, NULL, 'm' },
				{ "min-port", required_argument, NULL, MIN_PORT_OPT },
				{ "max-port", required_argument, NULL, MAX_PORT_OPT },
				{ "lt-cred-mech", optional_argument, NULL, 'a' },
				{ "st-cred-mech", optional_argument, NULL, 'A' },
				{ "no-auth", optional_argument, NULL, 'z' },
				{ "user", required_argument, NULL, 'u' },
				{ "userdb", required_argument, NULL, 'b' },
#if !defined(TURN_NO_PQ)
				{ "psql-userdb", required_argument, NULL, 'e' },
				{ "sql-userdb", required_argument, NULL, 'e' },
#endif
#if !defined(TURN_NO_MYSQL)
				{ "mysql-userdb", required_argument, NULL, 'M' },
#endif
#if !defined(TURN_NO_HIREDIS)
				{ "redis-userdb", required_argument, NULL, 'N' },
				{ "redis-statsdb", required_argument, NULL, 'O' },
#endif
				{ "use-auth-secret", optional_argument, NULL, AUTH_SECRET_OPT },
				{ "static-auth-secret", required_argument, NULL, STATIC_AUTH_SECRET_VAL_OPT },
/* deprecated: */		{ "secret-ts-exp-time", optional_argument, NULL, AUTH_SECRET_TS_EXP },
				{ "realm", required_argument, NULL, 'r' },
				{ "user-quota", required_argument, NULL, 'q' },
				{ "total-quota", required_argument, NULL, 'Q' },
				{ "max-bps", required_argument, NULL, 's' },
				{ "verbose", optional_argument, NULL, 'v' },
				{ "Verbose", optional_argument, NULL, 'V' },
				{ "daemon", optional_argument, NULL, 'o' },
				{ "fingerprint", optional_argument, NULL, 'f' },
				{ "no-udp", optional_argument, NULL, NO_UDP_OPT },
				{ "no-tcp", optional_argument, NULL, NO_TCP_OPT },
				{ "no-tls", optional_argument, NULL, NO_TLS_OPT },
				{ "no-dtls", optional_argument, NULL, NO_DTLS_OPT },
				{ "no-udp-relay", optional_argument, NULL, NO_UDP_RELAY_OPT },
				{ "no-tcp-relay", optional_argument, NULL, NO_TCP_RELAY_OPT },
				{ "stale-nonce", optional_argument, NULL, STALE_NONCE_OPT },
				{ "stun-only", optional_argument, NULL, 'S' },
				{ "no-stun", optional_argument, NULL, NO_STUN_OPT },
				{ "cert", required_argument, NULL, CERT_FILE_OPT },
				{ "pkey", required_argument, NULL, PKEY_FILE_OPT },
				{ "pkey-pwd", required_argument, NULL, PKEY_PWD_OPT },
				{ "log-file", required_argument, NULL, 'l' },
				{ "no-stdout-log", optional_argument, NULL, NO_STDOUT_LOG_OPT },
				{ "syslog", optional_argument, NULL, SYSLOG_OPT },
				{ "aux-server", required_argument, NULL, AUX_SERVER_OPT },
				{ "udp-self-balance", optional_argument, NULL, UDP_SELF_BALANCE_OPT },
				{ "alternate-server", required_argument, NULL, ALTERNATE_SERVER_OPT },
				{ "tls-alternate-server", required_argument, NULL, TLS_ALTERNATE_SERVER_OPT },
				{ "rest-api-separator", required_argument, NULL, 'C' },
				{ "max-allocate-timeout", required_argument, NULL, MAX_ALLOCATE_TIMEOUT_OPT },
				{ "no-multicast-peers", optional_argument, NULL, NO_MULTICAST_PEERS_OPT },
				{ "no-loopback-peers", optional_argument, NULL, NO_LOOPBACK_PEERS_OPT },
				{ "allowed-peer-ip", required_argument, NULL, ALLOWED_PEER_IPS },
				{ "denied-peer-ip", required_argument, NULL, DENIED_PEER_IPS },
				{ "cipher-list", required_argument, NULL, CIPHER_LIST_OPT },
				{ "pidfile", required_argument, NULL, PIDFILE_OPT },
				{ "secure-stun", optional_argument, NULL, SECURE_STUN_OPT },
				{ "CA-file", required_argument, NULL, CA_FILE_OPT },
				{ "dh-file", required_argument, NULL, DH_FILE_OPT },
				{ "sha256", optional_argument, NULL, SHA256_OPT },
				{ "proc-user", required_argument, NULL, PROC_USER_OPT },
				{ "proc-group", required_argument, NULL, PROC_GROUP_OPT },
				{ "mobility", optional_argument, NULL, MOBILITY_OPT },
				{ "no-cli", optional_argument, NULL, NO_CLI_OPT },
				{ "cli-ip", required_argument, NULL, CLI_IP_OPT },
				{ "cli-port", required_argument, NULL, CLI_PORT_OPT },
				{ "cli-password", required_argument, NULL, CLI_PASSWORD_OPT },
				{ "server-relay", optional_argument, NULL, SERVER_RELAY_OPT },
				{ "cli-max-output-sessions", required_argument, NULL, CLI_MAX_SESSIONS_OPT },
				{ "ec-curve-name", required_argument, NULL, EC_CURVE_NAME_OPT },
				{ "dh566", optional_argument, NULL, DH566_OPT },
				{ "dh2066", optional_argument, NULL, DH2066_OPT },
				{ "ne", required_argument, NULL, NE_TYPE_OPT },
				{ "no-sslv2", optional_argument, NULL, NO_SSLV2_OPT },
				{ "no-sslv3", optional_argument, NULL, NO_SSLV3_OPT },
				{ "no-tlsv1", optional_argument, NULL, NO_TLSV1_OPT },
				{ "no-tlsv1_1", optional_argument, NULL, NO_TLSV1_1_OPT },
				{ "no-tlsv1_2", optional_argument, NULL, NO_TLSV1_2_OPT },
				{ NULL, no_argument, NULL, 0 }
};

static struct option admin_long_options[] = {
				{ "key", no_argument, NULL, 'k' },
				{ "add", no_argument, NULL, 'a' },
				{ "delete", no_argument, NULL, 'd' },
				{ "list", no_argument, NULL, 'l' },
				{ "list-st", no_argument, NULL, 'L' },
#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL)
				{ "set-secret", required_argument, NULL, 's' },
				{ "show-secret", no_argument, NULL, 'S' },
				{ "delete-secret", required_argument, NULL, 'X' },
				{ "delete-all-secrets", no_argument, NULL, DEL_ALL_AUTH_SECRETS_OPT },
#endif
				{ "add-st", no_argument, NULL, 'A' },
				{ "delete-st", no_argument, NULL, 'D' },
				{ "userdb", required_argument, NULL, 'b' },
#if !defined(TURN_NO_PQ)
				{ "psql-userdb", required_argument, NULL, 'e' },
				{ "sql-userdb", required_argument, NULL, 'e' },
#endif
#if !defined(TURN_NO_MYSQL)
				{ "mysql-userdb", required_argument, NULL, 'M' },
#endif
#if !defined(TURN_NO_HIREDIS)
				{ "redis-userdb", required_argument, NULL, 'N' },
#endif
				{ "user", required_argument, NULL, 'u' },
				{ "realm", required_argument, NULL, 'r' },
				{ "password", required_argument, NULL, 'p' },
				{ "sha256", no_argument, NULL, 'H' },
				{ "help", no_argument, NULL, 'h' },
				{ NULL, no_argument, NULL, 0 }
};

static int get_bool_value(const char* s)
{
	if(!s || !(s[0])) return 1;
	if(s[0]=='0' || s[0]=='n' || s[0]=='N' || s[0]=='f' || s[0]=='F') return 0;
	if(s[0]=='y' || s[0]=='Y' || s[0]=='t' || s[0]=='T') return 1;
	if(s[0]>'0' && s[0]<='9') return 1;
	if(!strcmp(s,"off") || !strcmp(s,"OFF") || !strcmp(s,"Off")) return 0;
	if(!strcmp(s,"on") || !strcmp(s,"ON") || !strcmp(s,"On")) return 1;
	TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown boolean value: %s. You can use on/off, yes/no, 1/0, true/false.\n",s);
	exit(-1);
}

static void set_option(int c, char *value)
{
  if(value && value[0]=='=') {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: option -%c is possibly used incorrectly. The short form of the option must be used as this: -%c <value>, no \'equals\' sign may be used, that sign is used only with long form options (like --user=<username>).\n",(char)c,(char)c);
  }

  switch (c) {
  case NO_SSLV2_OPT:
	  turn_params.no_sslv2 = get_bool_value(value);
	  break;
  case NO_SSLV3_OPT:
	  turn_params.no_sslv3 = get_bool_value(value);
	  break;
  case NO_TLSV1_OPT:
	  turn_params.no_tlsv1 = get_bool_value(value);
	  break;
  case NO_TLSV1_1_OPT:
	  turn_params.no_tlsv1_1 = get_bool_value(value);
	  break;
  case NO_TLSV1_2_OPT:
	  turn_params.no_tlsv1_2 = get_bool_value(value);
	  break;
  case NE_TYPE_OPT:
  {
	  int ne = atoi(value);
	  if((ne<(int)NEV_MIN)||(ne>(int)NEV_MAX)) {
		  TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: wrong version of the network engine: %d\n",ne);
	  }
	  turn_params.net_engine_version = (NET_ENG_VERSION)ne;
  }
	  break;
  case DH566_OPT:
	  if(get_bool_value(value))
		  turn_params.dh_key_size = DH_566;
	  break;
  case DH2066_OPT:
	  if(get_bool_value(value))
		  turn_params.dh_key_size = DH_2066;
	  break;
  case EC_CURVE_NAME_OPT:
	  STRCPY(turn_params.ec_curve_name,value);
	  break;
  case CLI_MAX_SESSIONS_OPT:
	  cli_max_output_sessions = atoi(value);
	  break;
  case SERVER_RELAY_OPT:
	  turn_params.server_relay = get_bool_value(value);
	  break;
  case MOBILITY_OPT:
	  turn_params.mobility = get_bool_value(value);
	  break;
  case NO_CLI_OPT:
	  use_cli = !get_bool_value(value);
	  break;
  case CLI_IP_OPT:
	  if(make_ioa_addr((const u08bits*)value,0,&cli_addr)<0) {
		  TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot set cli address: %s\n",value);
	  } else{
		  cli_addr_set = 1;
	  }
	  break;
  case CLI_PORT_OPT:
	  cli_port = atoi(value);
	  break;
  case CLI_PASSWORD_OPT:
	  STRCPY(cli_password,value);
	  break;
  case PROC_USER_OPT: {
	  struct passwd* pwd = getpwnam(value);
	  if(!pwd) {
		  TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown user name: %s\n",value);
		  exit(-1);
	  } else {
		  procuserid = pwd->pw_uid;
		  procuserid_set = 1;
		  STRCPY(procusername,value);
	  }
  }
  break;
	case PROC_GROUP_OPT: {
		struct group* gr = getgrnam(value);
		if(!gr) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown group name: %s\n",value);
			exit(-1);
		} else {
			procgroupid = gr->gr_gid;
			procgroupid_set = 1;
			STRCPY(procgroupname,value);
		}
	}
	break;
	case 'i':
		STRCPY(turn_params.relay_ifname, value);
		break;
	case 'm':
#if defined(OPENSSL_THREADS)
		if(atoi(value)>MAX_NUMBER_OF_GENERAL_RELAY_SERVERS) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: max number of relay threads is 128.\n");
			turn_params.general_relay_servers_number = MAX_NUMBER_OF_GENERAL_RELAY_SERVERS;
		} else if(atoi(value)<=0) {
			turn_params.general_relay_servers_number = 0;
		} else {
			turn_params.general_relay_servers_number = atoi(value);
		}
#else
		TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: OpenSSL version is too old OR does not support threading,\n I am using single thread for relaying.\n");
#endif
		break;
	case 'd':
		STRCPY(turn_params.listener_ifname, value);
		break;
	case 'p':
		turn_params.listener_port = atoi(value);
		break;
	case TLS_PORT_OPT:
		turn_params.tls_listener_port = atoi(value);
		break;
	case ALT_PORT_OPT:
		turn_params.alt_listener_port = atoi(value);
		break;
	case ALT_TLS_PORT_OPT:
		turn_params.alt_tls_listener_port = atoi(value);
		break;
	case MIN_PORT_OPT:
		turn_params.min_port = atoi(value);
		break;
	case MAX_PORT_OPT:
		turn_params.max_port = atoi(value);
		break;
	case SECURE_STUN_OPT:
		turn_params.secure_stun = get_bool_value(value);
		break;
	case SHA256_OPT:
		if(get_bool_value(value))
			turn_params.shatype = SHATYPE_SHA256;
		else
			turn_params.shatype = SHATYPE_SHA1;
		break;
	case NO_MULTICAST_PEERS_OPT:
		turn_params.no_multicast_peers = get_bool_value(value);
		break;
	case NO_LOOPBACK_PEERS_OPT:
		turn_params.no_loopback_peers = get_bool_value(value);
		break;
	case STALE_NONCE_OPT:
		turn_params.stale_nonce = get_bool_value(value);
		break;
	case MAX_ALLOCATE_TIMEOUT_OPT:
		TURN_MAX_ALLOCATE_TIMEOUT = atoi(value);
		TURN_MAX_ALLOCATE_TIMEOUT_STUN_ONLY = atoi(value);
		break;
	case 'S':
		turn_params.stun_only = get_bool_value(value);
		break;
	case NO_STUN_OPT:
		turn_params.no_stun = get_bool_value(value);
		break;
	case 'L':
		add_listener_addr(value);
		break;
	case 'E':
		add_relay_addr(value);
		break;
	case 'X':
		if(value) {
			char *div = strchr(value,'/');
			if(div) {
				char *nval=strdup(value);
				div = strchr(nval,'/');
				div[0]=0;
				++div;
				ioa_addr apub,apriv;
				if(make_ioa_addr((const u08bits*)nval,0,&apub)<0) {
					TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"-X : Wrong address format: %s\n",nval);
				} else {
					if(make_ioa_addr((const u08bits*)div,0,&apriv)<0) {
						TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"-X : Wrong address format: %s\n",div);
					} else {
						ioa_addr_add_mapping(&apub,&apriv);
					}
				}
				turn_free(nval,strlen(nval)+1);
			} else {
				if(turn_params.external_ip) {
					TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "You cannot define external IP more than once in the configuration\n");
				} else {
					turn_params.external_ip = (ioa_addr*)allocate_super_memory_engine(turn_params.listener.ioa_eng, sizeof(ioa_addr));
					if(make_ioa_addr((const u08bits*)value,0,turn_params.external_ip)<0) {
						TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"-X : Wrong address format: %s\n",value);
						turn_free(turn_params.external_ip,sizeof(ioa_addr));
						turn_params.external_ip = NULL;
					}
				}
			}
		}
		break;
	case 'v':
		if(get_bool_value(value)) {
			turn_params.verbose = TURN_VERBOSE_NORMAL;
		} else {
			turn_params.verbose = TURN_VERBOSE_NONE;
		}
		break;
	case 'V':
		if(get_bool_value(value)) {
			turn_params.verbose = TURN_VERBOSE_EXTRA;
		}
		break;
	case 'o':
		turn_params.turn_daemon = get_bool_value(value);
		break;
	case 'a':
		if (get_bool_value(value)) {
			turn_params.users_params.users.ct = TURN_CREDENTIALS_LONG_TERM;
			turn_params.users_params.use_lt_credentials=1;
		} else {
			turn_params.users_params.users.ct = TURN_CREDENTIALS_UNDEFINED;
			turn_params.users_params.use_lt_credentials=0;
		}
		break;
	case 'A':
		if (get_bool_value(value)) {
			turn_params.users_params.users.ct = TURN_CREDENTIALS_SHORT_TERM;
			turn_params.users_params.use_st_credentials=1;
		} else {
			turn_params.users_params.users.ct = TURN_CREDENTIALS_UNDEFINED;
			turn_params.users_params.use_st_credentials=0;
		}
		break;
	case 'z':
		if (!get_bool_value(value)) {
			turn_params.users_params.users.ct = TURN_CREDENTIALS_UNDEFINED;
			turn_params.users_params.anon_credentials = 0;
		} else {
			turn_params.users_params.users.ct = TURN_CREDENTIALS_NONE;
			turn_params.users_params.anon_credentials = 1;
		}
		break;
	case 'f':
		turn_params.fingerprint = get_bool_value(value);
		break;
	case 'u':
		add_user_account(value,0);
		break;
	case 'b':
		STRCPY(turn_params.users_params.userdb, value);
		turn_params.users_params.userdb_type = TURN_USERDB_TYPE_FILE;
		break;
#if !defined(TURN_NO_PQ)
	case 'e':
		STRCPY(turn_params.users_params.userdb, value);
		turn_params.users_params.userdb_type = TURN_USERDB_TYPE_PQ;
		break;
#endif
#if !defined(TURN_NO_MYSQL)
	case 'M':
		STRCPY(turn_params.users_params.userdb, value);
		turn_params.users_params.userdb_type = TURN_USERDB_TYPE_MYSQL;
		break;
#endif
#if !defined(TURN_NO_HIREDIS)
	case 'N':
		STRCPY(turn_params.users_params.userdb, value);
		turn_params.users_params.userdb_type = TURN_USERDB_TYPE_REDIS;
		break;
	case 'O':
		STRCPY(turn_params.redis_statsdb, value);
		turn_params.use_redis_statsdb = 1;
		break;
#endif
	case AUTH_SECRET_OPT:
		turn_params.users_params.use_auth_secret_with_timestamp = 1;
		break;
	case STATIC_AUTH_SECRET_VAL_OPT:
		add_to_secrets_list(&turn_params.users_params.static_auth_secrets,value);
		turn_params.users_params.use_auth_secret_with_timestamp = 1;
		break;
	case AUTH_SECRET_TS_EXP:
		TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: Option --secret-ts-exp-time deprecated and has no effect.\n");
		break;
	case 'r':
		STRCPY(turn_params.users_params.global_realm,value);
		break;
	case 'q':
		turn_params.users_params.users.user_quota = atoi(value);
		break;
	case 'Q':
		turn_params.users_params.users.total_quota = atoi(value);
		break;
	case 's':
		turn_params.max_bps = (band_limit_t)atol(value);
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%lu bytes per second allowed per session\n",(unsigned long)turn_params.max_bps);
		break;
	case NO_UDP_OPT:
		turn_params.no_udp = get_bool_value(value);
		break;
	case NO_TCP_OPT:
		turn_params.no_tcp = get_bool_value(value);
		break;
	case NO_UDP_RELAY_OPT:
		turn_params.no_udp_relay = get_bool_value(value);
		break;
	case NO_TCP_RELAY_OPT:
		turn_params.no_tcp_relay = get_bool_value(value);
		break;
	case NO_TLS_OPT:
#if defined(TURN_NO_TLS)
		turn_params.no_tls = 1;
#else
		turn_params.no_tls = get_bool_value(value);
#endif
		break;
	case NO_DTLS_OPT:
#if !defined(TURN_NO_DTLS)
		turn_params.no_dtls = get_bool_value(value);
#else
		turn_params.no_dtls = 1;
#endif
		break;
	case CERT_FILE_OPT:
		STRCPY(turn_params.cert_file,value);
		break;
	case CA_FILE_OPT:
		STRCPY(turn_params.ca_cert_file,value);
		break;
	case DH_FILE_OPT:
		STRCPY(turn_params.dh_file,value);
		break;
	case PKEY_FILE_OPT:
		STRCPY(turn_params.pkey_file,value);
		break;
	case PKEY_PWD_OPT:
		STRCPY(turn_params.tls_password,value);
		break;
	case ALTERNATE_SERVER_OPT:
		add_alternate_server(value);
		break;
	case AUX_SERVER_OPT:
		add_aux_server(value);
		break;
	case UDP_SELF_BALANCE_OPT:
		turn_params.udp_self_balance = get_bool_value(value);
		break;
	case TLS_ALTERNATE_SERVER_OPT:
		add_tls_alternate_server(value);
		break;
	case ALLOWED_PEER_IPS:
		if (add_ip_list_range(value, &turn_params.ip_whitelist) == 0) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "White listing: %s\n", value);
		break;
	case DENIED_PEER_IPS:
		if (add_ip_list_range(value, &turn_params.ip_blacklist) == 0) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Black listing: %s\n", value);
		break;
	case CIPHER_LIST_OPT:
		STRCPY(turn_params.cipher_list,value);
		break;
	case PIDFILE_OPT:
		STRCPY(turn_params.pidfile,value);
		break;
	case 'C':
		if(value && *value) {
			turn_params.users_params.rest_api_separator=*value;
		}
		break;
	/* these options have been already taken care of before: */
	case 'l':
	case NO_STDOUT_LOG_OPT:
	case SYSLOG_OPT:
	case 'c':
	case 'n':
	case 'h':
		break;
	default:
		fprintf(stderr,"\n%s\n", Usage);
		exit(-1);
  }
}

static int parse_arg_string(char *sarg, int *c, char **value)
{
	int i = 0;
	char *name = sarg;
	while(*sarg) {
		if((*sarg==' ') || (*sarg=='=') || (*sarg=='\t')) {
			*sarg=0;
			do {
				++sarg;
			} while((*sarg==' ') || (*sarg=='=') || (*sarg=='\t'));
			*value = sarg;
			break;
		}
		++sarg;
		*value=sarg;
	}


	if(value && *value && **value=='\"') {
		*value += 1;
		size_t len = strlen(*value);
		while(len>0 && (
				((*value)[len-1]=='\n') ||
				((*value)[len-1]=='\r') ||
				((*value)[len-1]==' ') ||
				((*value)[len-1]=='\t')
				) ) {
			(*value)[--len]=0;
		}
		if(len>0 && (*value)[len-1]=='\"') {
			(*value)[--len]=0;
		}
	}

	while(long_options[i].name) {
		if(strcmp(long_options[i].name,name)) {
			++i;
			continue;
		}
		*c=long_options[i].val;
		return 0;
	}

	return -1;
}

static void read_config_file(int argc, char **argv, int pass)
{
	static char config_file[1025] = DEFAULT_CONFIG_FILE;

	if(pass == 0) {

	  if (argv) {
	    int i = 0;
	    for (i = 0; i < argc; i++) {
	      if (!strcmp(argv[i], "-c")) {
		if (i < argc - 1) {
		  STRCPY(config_file, argv[i + 1]);
		} else {
		  TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "Wrong usage of -c option\n");
		}
	      } else if (!strcmp(argv[i], "-n")) {
		turn_params.do_not_use_config_file = 1;
		config_file[0]=0;
		return;
	      } else if (!strcmp(argv[i], "-h")) {
		printf("\n%s\n",Usage);
		exit(0);
	      }
	    }
	  }
	}

	if (!turn_params.do_not_use_config_file && config_file[0]) {

		FILE *f = NULL;
		char *full_path_to_config_file = NULL;

		full_path_to_config_file = find_config_file(config_file, 1);
		if (full_path_to_config_file)
			f = fopen(full_path_to_config_file, "r");

		if (f && full_path_to_config_file) {

			char sbuf[1025];
			char sarg[1035];

			for (;;) {
				char *s = fgets(sbuf, sizeof(sbuf) - 1, f);
				if (!s)
					break;
				s = skip_blanks(s);
				if (s[0] == '#')
					continue;
				if (!s[0])
					continue;
				size_t slen = strlen(s);
				while (slen && ((s[slen - 1] == 10) || (s[slen - 1] == 13)))
					s[--slen] = 0;
				if (slen) {
					int c = 0;
					char *value = NULL;
					STRCPY(sarg, s);
					if (parse_arg_string(sarg, &c, &value) < 0) {
						TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "Bad configuration format: %s\n",
							sarg);
					} else if((pass == 0) && (c == 'l')) {
						set_logfile(value);
					} else if((pass==0) && (c==NO_STDOUT_LOG_OPT)) {
						set_no_stdout_log(get_bool_value(value));
					} else if((pass==0) && (c==SYSLOG_OPT)) {
						set_log_to_syslog(get_bool_value(value));
					} else if((pass == 0) && (c != 'u')) {
					  set_option(c, value);
					} else if((pass > 0) && (c == 'u')) {
					  set_option(c, value);
					}
				}
			}

			fclose(f);

		} else
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: Cannot find config file: %s. Default and command-line settings will be used.\n",
				config_file);
	}
}

static int adminmain(int argc, char **argv)
{
	int c = 0;

	TURNADMIN_COMMAND_TYPE ct = TA_COMMAND_UNKNOWN;
	int is_st = 0;

	u08bits user[STUN_MAX_USERNAME_SIZE+1]="";
	u08bits realm[STUN_MAX_REALM_SIZE+1]="";
	u08bits pwd[STUN_MAX_PWD_SIZE+1]="";
	u08bits secret[AUTH_SECRET_SIZE+1]="";

	while (((c = getopt_long(argc, argv, ADMIN_OPTIONS, admin_long_options, NULL)) != -1)) {
		switch (c){
		case 'k':
			ct = TA_PRINT_KEY;
			break;
		case 'a':
			ct = TA_UPDATE_USER;
			break;
		case 'd':
			ct = TA_DELETE_USER;
			break;
		case 'A':
			ct = TA_UPDATE_USER;
			is_st = 1;
			break;
		case 'D':
			ct = TA_DELETE_USER;
			is_st = 1;
			break;
		case 'l':
			ct = TA_LIST_USERS;
			break;
		case 'L':
			ct = TA_LIST_USERS;
			is_st = 1;
			break;
#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL)
		case 's':
			ct = TA_SET_SECRET;
			STRCPY(secret,optarg);
			break;
		case 'S':
			ct = TA_SHOW_SECRET;
			break;
		case 'X':
			ct = TA_DEL_SECRET;
			if(optarg)
				STRCPY(secret,optarg);
			break;
		case DEL_ALL_AUTH_SECRETS_OPT:
			ct = TA_DEL_SECRET;
			break;
#endif
		case 'b':
		  STRCPY(turn_params.users_params.userdb,optarg);
		  turn_params.users_params.userdb_type = TURN_USERDB_TYPE_FILE;
		  break;
#if !defined(TURN_NO_PQ)
		case 'e':
		  STRCPY(turn_params.users_params.userdb,optarg);
		  turn_params.users_params.userdb_type = TURN_USERDB_TYPE_PQ;
		  break;
#endif
#if !defined(TURN_NO_MYSQL)
		case 'M':
		  STRCPY(turn_params.users_params.userdb,optarg);
		  turn_params.users_params.userdb_type = TURN_USERDB_TYPE_MYSQL;
		  break;
#endif
#if !defined(TURN_NO_HIREDIS)
		case 'N':
		  STRCPY(turn_params.users_params.userdb,optarg);
		  turn_params.users_params.userdb_type = TURN_USERDB_TYPE_REDIS;
		  break;
#endif
		case 'u':
			STRCPY(user,optarg);
			if(SASLprep((u08bits*)user)<0) {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong user name: %s\n",user);
				exit(-1);
			}
			break;
		case 'r':
			STRCPY(realm,optarg);
			if(SASLprep((u08bits*)realm)<0) {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong realm: %s\n",realm);
				exit(-1);
			}
			break;
		case 'p':
			STRCPY(pwd,optarg);
			if(SASLprep((u08bits*)pwd)<0) {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong password: %s\n",pwd);
				exit(-1);
			}
			break;
		case 'H':
			if(get_bool_value(optarg))
				turn_params.shatype = SHATYPE_SHA256;
			else
				turn_params.shatype = SHATYPE_SHA1;
			break;
		case 'h':
			printf("\n%s\n", AdminUsage);
			exit(0);
			break;
		default:
			fprintf(stderr,"\n%s\n", AdminUsage);
			exit(-1);
		}
	}

	if(is_st && (turn_params.users_params.userdb_type == TURN_USERDB_TYPE_FILE)) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: you have to use a PostgreSQL or MySQL database with short-term credentials\n");
		exit(-1);
	}

	if(!strlen(turn_params.users_params.userdb) && (turn_params.users_params.userdb_type == TURN_USERDB_TYPE_FILE))
		STRCPY(turn_params.users_params.userdb,DEFAULT_USERDB_FILE);

	if(ct == TA_COMMAND_UNKNOWN) {
		fprintf(stderr,"\n%s\n", AdminUsage);
		exit(-1);
	}

	argc -= optind;
	argv += optind;

	if(argc != 0) {
		fprintf(stderr,"\n%s\n", AdminUsage);
		exit(-1);
	}

	return adminuser(user, realm, pwd, secret, ct, is_st);
}

static void print_features(unsigned long mfn)
{
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nRFC 3489/5389/5766/5780/6062/6156 STUN/TURN Server\nVersion %s\n",TURN_SOFTWARE);

	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nMax number of open files/sockets allowed for this process: %lu\n",mfn);
	if(turn_params.net_engine_version == 1)
		mfn = mfn/3;
	else
		mfn = mfn/2;
	mfn = ((unsigned long)(mfn/500))*500;
	if(mfn<500)
		mfn = 500;
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nDue to the open files/sockets limitation,\nmax supported number of TURN Sessions possible is: %lu (approximately)\n",mfn);

	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\n\n==== Show him the instruments, Practical Frost: ====\n\n");

#if defined(TURN_NO_TLS)
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TLS is not supported\n");
#else
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TLS supported\n");
#endif

#if defined(TURN_NO_DTLS)
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS is not supported\n");
#else
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS supported\n");
#endif

#if !defined(TURN_NO_HIREDIS)
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Redis supported\n");
#else
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Redis is not supported\n");
#endif

#if !defined(TURN_NO_PQ)
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "PostgreSQL supported\n");
#else
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "PostgreSQL is not supported\n");
#endif

#if !defined(TURN_NO_MYSQL)
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "MySQL supported\n");
#else
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "MySQL is not supported\n");
#endif

#if defined(OPENSSL_THREADS)
	//TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "OpenSSL multithreading supported\n");
#else
	TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "OpenSSL multithreading is not supported (?!)\n");
#endif

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "OpenSSL version: fresh enough\n");
#else
	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "OpenSSL version: antique\n");
#endif

	TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Default Net Engine version: %d (%s)\n\n=====================================================\n\n", (int)turn_params.net_engine_version, turn_params.net_engine_version_txt[(int)turn_params.net_engine_version]);

}

#if defined(__linux__) || defined(__LINUX__) || defined(__linux) || defined(linux__) || defined(LINUX) || defined(__LINUX) || defined(LINUX__)
#include <linux/version.h>
#endif

static void set_network_engine(void)
{
	if(turn_params.net_engine_version != NEV_UNKNOWN)
		return;
	turn_params.net_engine_version = NEV_UDP_SOCKET_PER_ENDPOINT;
#if defined(SO_REUSEPORT)
#if defined(__linux__) || defined(__LINUX__) || defined(__linux) || defined(linux__) || defined(LINUX) || defined(__LINUX) || defined(LINUX__)
	turn_params.net_engine_version = NEV_UDP_SOCKET_PER_THREAD;
#else /* BSD ? */
	turn_params.net_engine_version = NEV_UDP_SOCKET_PER_SESSION;
#endif /* Linux */
#else /* defined(SO_REUSEPORT) */
#if defined(__linux__) || defined(__LINUX__) || defined(__linux) || defined(linux__) || defined(LINUX) || defined(__LINUX) || defined(LINUX__)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
	//net_engine_version = NEV_UDP_SOCKET_PER_SESSION;
	turn_params.net_engine_version = NEV_UDP_SOCKET_PER_ENDPOINT;
#else
	turn_params.net_engine_version = NEV_UDP_SOCKET_PER_ENDPOINT;
#endif /* Linux version */
#endif /* Linux */
#endif /* defined(SO_REUSEPORT) */

}

static void drop_privileges(void)
{
	if(procgroupid_set) {
		if(getgid() != procgroupid) {
			if (setgid(procgroupid) != 0) {
				perror("setgid: Unable to change group privileges");
				exit(-1);
			} else {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "New GID: %s(%lu)\n", procgroupname, (unsigned long)procgroupid);
			}
		} else {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Keep GID: %s(%lu)\n", procgroupname, (unsigned long)procgroupid);
		}
	}

	if(procuserid_set) {
		if(procuserid != getuid()) {
			if (setuid(procuserid) != 0) {
				perror("setuid: Unable to change user privileges");
				exit(-1);
			} else {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "New UID: %s(%lu)\n", procusername, (unsigned long)procuserid);
			}
		} else {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Keep UID: %s(%lu)\n", procusername, (unsigned long)procuserid);
		}
	}
}

int main(int argc, char **argv)
{
	int c = 0;

	IS_TURN_SERVER = 1;

	set_execdir();

	init_super_memory();

	init_turn_server_addrs_list(&turn_params.alternate_servers_list);
	init_turn_server_addrs_list(&turn_params.tls_alternate_servers_list);
	init_turn_server_addrs_list(&turn_params.aux_servers_list);

	set_network_engine();

	init_listener();
	init_secrets_list(&turn_params.users_params.static_auth_secrets);
	init_dynamic_ip_lists();

	if (!strstr(argv[0], "turnadmin")) {
		while (((c = getopt_long(argc, argv, OPTIONS, long_options, NULL)) != -1)) {
			switch (c){
			case 'l':
				set_logfile(optarg);
				break;
			case NO_STDOUT_LOG_OPT:
				set_no_stdout_log(get_bool_value(optarg));
				break;
			case SYSLOG_OPT:
				set_log_to_syslog(get_bool_value(optarg));
				break;
			default:
				;
			}
		}
	}

	optind = 0;

#if defined(TURN_NO_TLS)
	turn_params.no_tls = 1;
#endif

#if defined(TURN_NO_DTLS)
	turn_params.no_dtls = 1;
#endif

#if defined(_SC_NPROCESSORS_ONLN)

	{
		 long cpus = (long)sysconf(_SC_NPROCESSORS_CONF);

		 if(cpus<1)
			 cpus = 1;
		 else if(cpus>MAX_NUMBER_OF_GENERAL_RELAY_SERVERS)
			 cpus = MAX_NUMBER_OF_GENERAL_RELAY_SERVERS;

		 turn_params.general_relay_servers_number = (turnserver_id)cpus;
	}

#endif

	ns_bzero(&turn_params.users_params.users,sizeof(turn_user_db));
	turn_params.users_params.users.ct = TURN_CREDENTIALS_NONE;
	turn_params.users_params.users.static_accounts = ur_string_map_create(free);
	turn_params.users_params.users.dynamic_accounts = ur_string_map_create(free);
	turn_params.users_params.users.alloc_counters = ur_string_map_create(NULL);

	if(strstr(argv[0],"turnadmin"))
		return adminmain(argc,argv);

	{
		unsigned long mfn = set_system_parameters(1);

		print_features(mfn);
	}

	read_config_file(argc,argv,0);

	while (((c = getopt_long(argc, argv, OPTIONS, long_options, NULL)) != -1)) {
		if(c != 'u')
			set_option(c,optarg);
	}

	read_config_file(argc,argv,1);

	optind = 0;

	while (((c = getopt_long(argc, argv, OPTIONS, long_options, NULL)) != -1)) {
	  if(c == 'u') {
	    set_option(c,optarg);
	  }
	}

	if(turn_params.no_udp_relay && turn_params.no_tcp_relay) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: --no-udp-relay and --no-tcp-relay options cannot be used together.\n");
		exit(-1);
	}

	if(turn_params.no_udp_relay) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nCONFIG: --no-udp-relay: UDP relay endpoints are not allowed.\n");
	}

	if(turn_params.no_tcp_relay) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nCONFIG: --no-tcp-relay: TCP relay endpoints are not allowed.\n");
	}

	if(turn_params.server_relay) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIG: WARNING: --server-relay: NON-STANDARD AND DANGEROUS OPTION.\n");
	}

	if(!strlen(turn_params.users_params.userdb) && (turn_params.users_params.userdb_type == TURN_USERDB_TYPE_FILE))
			STRCPY(turn_params.users_params.userdb,DEFAULT_USERDB_FILE);

	read_userdb_file(0);
	update_white_and_black_lists();

	argc -= optind;
	argv += optind;

	if(argc>0) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIGURATION ALERT: Unknown argument: %s\n",argv[argc-1]);
	}

	if(turn_params.users_params.use_lt_credentials && turn_params.users_params.anon_credentials) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: -a and -z options cannot be used together.\n");
		exit(-1);
	}

	if(turn_params.users_params.use_st_credentials && turn_params.users_params.anon_credentials) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: -A and -z options cannot be used together.\n");
		exit(-1);
	}

	if(turn_params.users_params.use_lt_credentials && turn_params.users_params.use_st_credentials) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: -a and -A options cannot be used together.\n");
		exit(-1);
	}

	if(!turn_params.users_params.use_lt_credentials && !turn_params.users_params.anon_credentials && !turn_params.users_params.use_st_credentials) {
		if(turn_params.users_params.users_number) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you specified long-term user accounts, (-u option) \n	but you did not specify the long-term credentials option\n	(-a or --lt-cred-mech option).\n 	I am turning --lt-cred-mech ON for you, but double-check your configuration.\n");
			turn_params.users_params.users.ct = TURN_CREDENTIALS_LONG_TERM;
			turn_params.users_params.use_lt_credentials=1;
		} else {
			turn_params.users_params.users.ct = TURN_CREDENTIALS_NONE;
			turn_params.users_params.use_lt_credentials=0;
		}
	}

	if(turn_params.users_params.use_lt_credentials) {
		if(!turn_params.users_params.users_number && (turn_params.users_params.userdb_type == TURN_USERDB_TYPE_FILE) && !turn_params.users_params.use_auth_secret_with_timestamp) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you did not specify any user account, (-u option) \n	but you did specified a long-term credentials mechanism option (-a option).\n	The TURN Server will be inaccessible.\n		Check your configuration.\n");
		} else if(!turn_params.users_params.global_realm[0]) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you did specify the long-term credentials usage\n but you did not specify the realm option (-r option).\n	The TURN Server will be inaccessible.\n		Check your configuration.\n");
		}
	}

	if(turn_params.users_params.anon_credentials) {
		if(turn_params.users_params.users_number) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you specified user accounts, (-u option) \n	but you also specified the anonymous user access option (-z or --no-auth option).\n 	User accounts will be ignored.\n");
			turn_params.users_params.users.ct = TURN_CREDENTIALS_NONE;
			turn_params.users_params.use_lt_credentials=0;
			turn_params.users_params.use_st_credentials=0;
		}
	}

	if(turn_params.users_params.use_auth_secret_with_timestamp && turn_params.users_params.use_st_credentials) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIGURATION ERROR: Authentication secret (REST API) cannot be used with short-term credentials mechanism.\n");
		exit(-1);
	}

	openssl_setup();

	int local_listeners = 0;
	if (!turn_params.listener.addrs_number) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "NO EXPLICIT LISTENER ADDRESS(ES) ARE CONFIGURED\n");
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "===========Discovering listener addresses: =========\n");
		int maddrs = make_local_listeners_list();
		if((maddrs<1) || !turn_params.listener.addrs_number) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot configure any meaningful IP listener address\n", __FUNCTION__);
			fprintf(stderr,"\n%s\n", Usage);
			exit(-1);
		}
		local_listeners = 1;
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "=====================================================\n");
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Total: %d 'real' addresses discovered\n",maddrs);
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "=====================================================\n");
	}

	if (!turn_params.relays_number) {
		if(!local_listeners && turn_params.listener.addrs_number && turn_params.listener.addrs) {
			size_t la = 0;
			for(la=0;la<turn_params.listener.addrs_number;la++) {
				if(turn_params.listener.addrs[la]) {
					add_relay_addr(turn_params.listener.addrs[la]);
				}
			}
		}
		if (!turn_params.relays_number) {
			turn_params.default_relays = 1;
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "NO EXPLICIT RELAY ADDRESS(ES) ARE CONFIGURED\n");
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "===========Discovering relay addresses: =============\n");
			if(make_local_relays_list(0,AF_INET)<1) {
				make_local_relays_list(1,AF_INET);
			}
			if(make_local_relays_list(0,AF_INET6)<1) {
				make_local_relays_list(1,AF_INET6);
			}
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "=====================================================\n");
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Total: %d relay addresses discovered\n",(int)turn_params.relays_number);
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "=====================================================\n");
		}

		if (!turn_params.relays_number) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "You must specify the relay address(es)\n",
							__FUNCTION__);
			fprintf(stderr,"\n%s\n", Usage);
			exit(-1);
		}
	}

	if(turn_params.turn_daemon) {
#if !defined(TURN_HAS_DAEMON)
		pid_t pid = fork();
		if(pid>0)
			exit(0);
		if(pid<0) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: Cannot start daemon process\n");
			exit(-1);
		}
#else
		if(daemon(1,0)<0) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: Cannot start daemon process\n");
			exit(-1);
		}
		reset_rtpprintf();
#endif
	}

	if(turn_params.pidfile[0]) {

		char s[2049];
		FILE *f = fopen(turn_params.pidfile,"w");
		if(f) {
			STRCPY(s,turn_params.pidfile);
		} else {
		  snprintf(s,sizeof(s),"Cannot create pid file: %s",turn_params.pidfile);
			perror(s);
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "%s\n", s);

			{
				const char *pfs[] = {"/var/run/turnserver.pid",
						"/var/spool/turnserver.pid",
						"/var/turnserver.pid",
						"/var/tmp/turnserver.pid",
						"/tmp/turnserver.pid",
						"turnserver.pid",
						NULL};
				const char **ppfs = pfs;
				while(*ppfs) {
					f = fopen(*ppfs,"w");
					if(f) {
						STRCPY(s,*ppfs);
						break;
					} else {
						++ppfs;
					}
				}
			}
		}

		if(f) {
			fprintf(f,"%lu\n",(unsigned long)getpid());
			fclose(f);
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "pid file created: %s\n", s);
		}
	}

	setup_server();

	drop_privileges();

	run_listener_server(turn_params.listener.event_base);

	return 0;
}

////////// OpenSSL locking ////////////////////////////////////////

#if defined(OPENSSL_THREADS)

static pthread_mutex_t* mutex_buf = NULL;

static void locking_function(int mode, int n, const char *file, int line) {
  UNUSED_ARG(file);
  UNUSED_ARG(line);
  if (mode & CRYPTO_LOCK)
    pthread_mutex_lock(&mutex_buf[n]);
  else
    pthread_mutex_unlock(&mutex_buf[n]);
}

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
static void id_function(CRYPTO_THREADID *ctid)
{
    CRYPTO_THREADID_set_numeric(ctid, (unsigned long)pthread_self());
}
#else
static unsigned long id_function(void)
{
    return (unsigned long)pthread_self();
}
#endif

#endif

static int THREAD_setup(void) {

#if defined(OPENSSL_THREADS)

	int i;

	mutex_buf = (pthread_mutex_t*) turn_malloc(CRYPTO_num_locks()
			* sizeof(pthread_mutex_t));
	if (!mutex_buf)
		return 0;
	for (i = 0; i < CRYPTO_num_locks(); i++)
		pthread_mutex_init(&mutex_buf[i], NULL);

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	CRYPTO_THREADID_set_callback(id_function);
#else
	CRYPTO_set_id_callback(id_function);
#endif

	CRYPTO_set_locking_callback(locking_function);
#endif

	return 1;
}

int THREAD_cleanup(void);
int THREAD_cleanup(void) {

#if defined(OPENSSL_THREADS)

  int i;

  if (!mutex_buf)
    return 0;

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	CRYPTO_THREADID_set_callback(NULL);
#else
	CRYPTO_set_id_callback(NULL);
#endif

  CRYPTO_set_locking_callback(NULL);
  for (i = 0; i < CRYPTO_num_locks(); i++)
    pthread_mutex_destroy(&mutex_buf[i]);
  turn_free(mutex_buf,sizeof(pthread_mutex_t));
  mutex_buf = NULL;

#endif

  return 1;
}

static void adjust_key_file_name(char *fn, const char* file_title, int critical)
{
	char *full_path_to_file = NULL;

	if(!fn[0]) {
	  TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"\nERROR: you must set the %s file parameter\n",file_title);
	  goto keyerr;
	} else {

	  full_path_to_file = find_config_file(fn, 1);
	  FILE *f = full_path_to_file ? fopen(full_path_to_file,"r") : NULL;
	  if(!f) {
	    TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"WARNING: cannot find %s file: %s (1)\n",file_title,fn);
	    goto keyerr;
	  }

	  if(!full_path_to_file) {
	    TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"WARNING: cannot find %s file: %s (2)\n",file_title,fn);
	    goto keyerr;
	  }

	  strncpy(fn,full_path_to_file,sizeof(turn_params.cert_file)-1);
	  fn[sizeof(turn_params.cert_file)-1]=0;

	  if(full_path_to_file)
	    turn_free(full_path_to_file,strlen(full_path_to_file)+1);
	  return;
	}

	keyerr:
	{
		if(critical) {
			turn_params.no_tls = 1;
			turn_params.no_dtls = 1;
			  TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"WARNING: cannot start TLS and DTLS listeners because %s file is not set properly\n",file_title);
		}
		if(full_path_to_file)
			turn_free(full_path_to_file,strlen(full_path_to_file)+1);
		return;
	}
}

static void adjust_key_file_names(void)
{
	if(turn_params.ca_cert_file[0])
		adjust_key_file_name(turn_params.ca_cert_file,"CA",1);
	adjust_key_file_name(turn_params.cert_file,"certificate",1);
	adjust_key_file_name(turn_params.pkey_file,"private key",1);
	if(turn_params.dh_file[0])
		adjust_key_file_name(turn_params.dh_file,"DH key",0);
}

static DH *get_dh566(void) {

	unsigned char dh566_p[] = {
					0x36,0x53,0xA8,0x9C,0x3C,0xF1,0xD1,0x1B,0x2D,0xA2,0x64,0xDE,
					0x59,0x3B,0xE3,0x8C,0x27,0x74,0xC2,0xBE,0x9B,0x6D,0x56,0xE7,
					0xDF,0xFF,0x67,0x6A,0xD2,0x0C,0xE8,0x9E,0x52,0x00,0x05,0xB3,
					0x53,0xF7,0x1C,0x41,0xB2,0xAC,0x38,0x16,0x32,0x3A,0x8E,0x90,
					0x6C,0x7E,0xD1,0x44,0xCB,0xF9,0x2D,0x1E,0x4A,0x9A,0x32,0x81,
					0x58,0xE1,0xE1,0x17,0xC1,0x9C,0xF1,0x1E,0x96,0x2D,0x5F
	};

//	-----BEGIN DH PARAMETERS-----
//MEwCRzZTqJw88dEbLaJk3lk744wndMK+m21W59//Z2rSDOieUgAFs1P3HEGyrDgW
//MjqOkGx+0UTL+S0eSpoygVjh4RfBnPEeli1fAgEF
//	-----END DH PARAMETERS-----

	unsigned char dh566_g[] = { 0x05 };
	DH *dh;

	if ((dh = DH_new()) == NULL )
		return (NULL );
	dh->p = BN_bin2bn(dh566_p, sizeof(dh566_p), NULL );
	dh->g = BN_bin2bn(dh566_g, sizeof(dh566_g), NULL );
	if ((dh->p == NULL )|| (dh->g == NULL)){ DH_free(dh); return(NULL);}
	return (dh);
}

static DH *get_dh1066(void) {

	unsigned char dh1066_p[] = {
					0x02,0x0E,0x26,0x6F,0xAA,0x9F,0xA8,0xE5,0x3F,0x70,0x88,0xF1,
					0xA9,0x29,0xAE,0x1A,0x2B,0xA8,0x2F,0xE8,0xE5,0x0E,0x81,0x78,
					0xD7,0x12,0x41,0xDC,0xE2,0xD5,0x10,0x6F,0x8A,0x35,0x23,0xCE,
					0x66,0x93,0x67,0x14,0xEA,0x0A,0x61,0xD4,0x43,0x63,0x5C,0xDF,
					0xDE,0xF5,0xB9,0xC6,0xB4,0x8C,0xBA,0x1A,0x25,0x9F,0x73,0x0F,
					0x1E,0x1A,0x97,0x42,0x2E,0x60,0x9E,0x4C,0x3C,0x70,0x6A,0xFB,
					0xDD,0xAA,0x7A,0x48,0xA5,0x1E,0x87,0xC8,0xA3,0x5E,0x26,0x40,
					0x1B,0xDE,0x08,0x5E,0xA2,0xB8,0xE8,0x76,0x43,0xE8,0xF1,0x4B,
					0x35,0x4C,0x38,0x92,0xB9,0xFF,0x61,0xE6,0x6C,0xBA,0xF9,0x16,
					0x36,0x3C,0x69,0x2D,0x57,0x90,0x62,0x8A,0xD0,0xD4,0xFB,0xB2,
					0x5A,0x61,0x99,0xA9,0xE8,0x93,0x80,0xA2,0xB7,0xDC,0xB1,0x6A,
					0xAF,0xE3
	};

//	-----BEGIN DH PARAMETERS-----
//	MIGMAoGGAg4mb6qfqOU/cIjxqSmuGiuoL+jlDoF41xJB3OLVEG+KNSPOZpNnFOoK
//	YdRDY1zf3vW5xrSMuholn3MPHhqXQi5gnkw8cGr73ap6SKUeh8ijXiZAG94IXqK4
//	6HZD6PFLNUw4krn/YeZsuvkWNjxpLVeQYorQ1PuyWmGZqeiTgKK33LFqr+MCAQI=
//	-----END DH PARAMETERS-----

	unsigned char dh1066_g[] = { 0x02 };
	DH *dh;

	if ((dh = DH_new()) == NULL )
		return (NULL );
	dh->p = BN_bin2bn(dh1066_p, sizeof(dh1066_p), NULL );
	dh->g = BN_bin2bn(dh1066_g, sizeof(dh1066_g), NULL );
	if ((dh->p == NULL )|| (dh->g == NULL)){ DH_free(dh); return(NULL);}
	return (dh);
}

static DH *get_dh2066(void) {

	unsigned char dh2066_p[] = {
					0x03,0x31,0x77,0x20,0x58,0xA6,0x69,0xA3,0x9D,0x2D,0x5E,0xE0,
					0x5C,0x46,0x82,0x0F,0x9E,0x80,0xF0,0x00,0x2A,0xF9,0x0F,0x62,
					0x1F,0x89,0xCE,0x7D,0x2A,0xFD,0xC5,0x9A,0x7C,0x6A,0x60,0x2C,
					0xF1,0xDD,0xD4,0x4D,0x6B,0xCD,0xE9,0x95,0xDB,0x42,0x97,0xBA,
					0xE4,0xAF,0x41,0x38,0x8F,0x57,0x31,0xA4,0x39,0xDD,0x31,0xC3,
					0x6F,0x98,0x0E,0xE3,0xB1,0x43,0xD1,0x36,0xB0,0x01,0x28,0x42,
					0x71,0xD3,0xB0,0x36,0xA0,0x47,0x99,0x25,0x9B,0x32,0xF5,0x86,
					0xB1,0x13,0x5C,0x24,0x8D,0x8D,0x7F,0xE2,0x7F,0x9A,0xC1,0x52,
					0x58,0xC0,0x63,0xAA,0x00,0x7C,0x1F,0x11,0xBD,0xAC,0x4C,0x2D,
					0xE0,0xA2,0x9D,0x4E,0x21,0xE4,0x0B,0xCD,0x24,0x92,0xD2,0x37,
					0x27,0x84,0x59,0x90,0x46,0x2F,0xD5,0xB9,0x27,0x93,0x18,0x88,
					0xBD,0x91,0x5B,0x87,0x55,0x56,0xD8,0x1B,0xE4,0xCF,0x1C,0xAA,
					0xBC,0xCF,0x80,0x1E,0x35,0x2D,0xB1,0xBC,0x35,0x31,0x92,0x62,
					0x3C,0x91,0x8D,0x62,0xDA,0xCF,0x83,0x63,0x12,0x4B,0x30,0x80,
					0xEE,0x82,0x3C,0x2C,0xD2,0x17,0x13,0x1F,0xF9,0x62,0x33,0x5C,
					0x63,0xD8,0x75,0x5B,0xAA,0x16,0x5A,0x36,0x49,0x17,0x77,0xB7,
					0x74,0xBD,0x3E,0x3F,0x98,0x20,0x59,0x5E,0xC7,0x72,0xE8,0xA3,
					0x89,0x21,0xB4,0x3C,0x25,0xF4,0xF4,0x21,0x96,0x5A,0xA6,0x77,
					0xFF,0x2C,0x3A,0xFC,0x98,0x5F,0xC1,0xBF,0x2A,0xCF,0xB8,0x62,
					0x67,0x23,0xE8,0x2F,0xCC,0x7B,0x32,0x1B,0x6B,0x33,0x67,0x0A,
					0xCB,0xD0,0x1F,0x65,0xD7,0x84,0x54,0xF6,0xF1,0x88,0xB5,0xBB,
					0x0C,0x63,0x65,0x34,0xE4,0x66,0x4B
	};

//	-----BEGIN DH PARAMETERS-----
//MIIBCgKCAQMDMXcgWKZpo50tXuBcRoIPnoDwACr5D2Ific59Kv3FmnxqYCzx3dRN
//a83pldtCl7rkr0E4j1cxpDndMcNvmA7jsUPRNrABKEJx07A2oEeZJZsy9YaxE1wk
//jY1/4n+awVJYwGOqAHwfEb2sTC3gop1OIeQLzSSS0jcnhFmQRi/VuSeTGIi9kVuH
//VVbYG+TPHKq8z4AeNS2xvDUxkmI8kY1i2s+DYxJLMIDugjws0hcTH/liM1xj2HVb
//qhZaNkkXd7d0vT4/mCBZXsdy6KOJIbQ8JfT0IZZapnf/LDr8mF/BvyrPuGJnI+gv
//zHsyG2szZwrL0B9l14RU9vGItbsMY2U05GZLAgEF
//	-----END DH PARAMETERS-----

	unsigned char dh2066_g[] = { 0x05 };
	DH *dh;

	if ((dh = DH_new()) == NULL )
		return (NULL );
	dh->p = BN_bin2bn(dh2066_p, sizeof(dh2066_p), NULL );
	dh->g = BN_bin2bn(dh2066_g, sizeof(dh2066_g), NULL );
	if ((dh->p == NULL )|| (dh->g == NULL)){ DH_free(dh); return(NULL);}
	return (dh);
}

static int pem_password_func(char *buf, int size, int rwflag, void *password)
{
	UNUSED_ARG(rwflag);

	strncpy(buf, (char * )(password), size);
	buf[size - 1] = 0;
	return (strlen(buf));
}

static void set_ctx(SSL_CTX* ctx, const char *protocol)
{
	SSL_CTX_set_default_passwd_cb_userdata(ctx, turn_params.tls_password);

	SSL_CTX_set_default_passwd_cb(ctx, pem_password_func);

	if(!(turn_params.cipher_list[0]))
		STRCPY(turn_params.cipher_list,DEFAULT_CIPHER_LIST);

	SSL_CTX_set_cipher_list(ctx, turn_params.cipher_list);
	SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);

	if (!SSL_CTX_use_certificate_chain_file(ctx, turn_params.cert_file)) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: no certificate found\n", protocol);
	} else {
		print_abs_file_name(protocol, ": Certificate", turn_params.cert_file);
	}

	if (!SSL_CTX_use_PrivateKey_file(ctx, turn_params.pkey_file, SSL_FILETYPE_PEM)) {
		if (!SSL_CTX_use_RSAPrivateKey_file(ctx, turn_params.pkey_file, SSL_FILETYPE_PEM)) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: no valid private key found, or invalid private key password provided\n", protocol);
		} else {
			print_abs_file_name(protocol, ": Private RSA key", turn_params.pkey_file);
		}
	} else {
		print_abs_file_name(protocol, ": Private key", turn_params.pkey_file);
	}

	if (!SSL_CTX_check_private_key(ctx)) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: invalid private key\n", protocol);
	}

	if(turn_params.ca_cert_file[0]) {

		if (!SSL_CTX_load_verify_locations(ctx, turn_params.ca_cert_file, NULL )) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot load CA from file: %s\n", turn_params.ca_cert_file);
		}

		SSL_CTX_set_client_CA_list(ctx,SSL_load_client_CA_file(turn_params.ca_cert_file));

		/* Set to require peer (client) certificate verification */
		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE, NULL);

		/* Set the verification depth to 9 */
		SSL_CTX_set_verify_depth(ctx, 9);

	} else {
		SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
	}

#if !defined(OPENSSL_NO_EC) && defined(OPENSSL_EC_NAMED_CURVE)
	{ //Elliptic curve algorithms:
		int nid = NID_X9_62_prime256v1;

		if (turn_params.ec_curve_name[0]) {
			nid = OBJ_sn2nid(turn_params.ec_curve_name);
			if (nid == 0) {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"unknown curve name (%s), using NID_X9_62_prime256v1\n",turn_params.ec_curve_name);
				nid = NID_X9_62_prime256v1;
			}
		}

		EC_KEY *ecdh = EC_KEY_new_by_curve_name(nid);
		if (!ecdh) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
					"%s: ERROR: allocate EC suite\n");
		} else {
			SSL_CTX_set_tmp_ecdh(ctx, ecdh);
			EC_KEY_free(ecdh);
		}
	}
#endif

	{//DH algorithms:

		DH *dh = NULL;
		if(turn_params.dh_file[0]) {
			FILE *paramfile = fopen(turn_params.dh_file, "r");
			 if (!paramfile) {
				 perror("Cannot open DH file");
			 } else {
			   dh = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
			   fclose(paramfile);
			   if(dh) {
				   turn_params.dh_key_size = DH_CUSTOM;
			   }
			 }
		}

		if(!dh) {
			if(turn_params.dh_key_size == DH_566)
				dh = get_dh566();
			else if(turn_params.dh_key_size == DH_2066)
				dh = get_dh2066();
			else
				dh = get_dh1066();
		}

		if(!dh) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: cannot allocate DH suite\n");
		} else {
			if (1 != SSL_CTX_set_tmp_dh (ctx, dh)) {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: cannot set DH\n");
			}
			DH_free (dh);
		}
	}

	{
		int op = 0;

#if defined(SSL_OP_NO_SSLv2)
		if(turn_params.no_sslv2)
			op |= SSL_OP_NO_SSLv2;
#endif
		if(turn_params.no_sslv3)
			op |= SSL_OP_NO_SSLv3;

		if(turn_params.no_tlsv1)
			op |= SSL_OP_NO_TLSv1;

#if defined(SSL_OP_NO_TLSv1_1)
		if(turn_params.no_tlsv1_1)
			op |= SSL_OP_NO_TLSv1_1;
#endif
#if defined(SSL_OP_NO_TLSv1_2)
		if(turn_params.no_tlsv1_2)
			op |= SSL_OP_NO_TLSv1_2;
#endif

#if defined(SSL_OP_CIPHER_SERVER_PREFERENCE)
		op |= SSL_OP_CIPHER_SERVER_PREFERENCE;
#endif

#if defined(SSL_OP_SINGLE_DH_USE)
		op |= SSL_OP_SINGLE_DH_USE;
#endif

#if defined(SSL_OP_SINGLE_ECDH_USE)
		op |= SSL_OP_SINGLE_ECDH_USE;
#endif

		SSL_CTX_set_options(ctx, op);
	}
}

static void openssl_setup(void)
{
	THREAD_setup();
	SSL_load_error_strings();
	OpenSSL_add_ssl_algorithms();

#if defined(TURN_NO_TLS)
	if(!turn_params.no_tls) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "WARNING: TLS is not supported\n");
		turn_params.no_tls = 1;
	}
#endif

	if(!(turn_params.no_tls && turn_params.no_dtls) && !turn_params.cert_file[0]) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"\nWARNING: certificate file is not specified, I cannot start TLS/DTLS services.\nOnly 'plain' UDP/TCP listeners can be started.\n");
		turn_params.no_tls = 1;
		turn_params.no_dtls = 1;
	}

	if(!(turn_params.no_tls && turn_params.no_dtls) && !turn_params.pkey_file[0]) {
		TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"\nWARNING: private key file is not specified, I cannot start TLS/DTLS services.\nOnly 'plain' UDP/TCP listeners can be started.\n");
		turn_params.no_tls = 1;
		turn_params.no_dtls = 1;
	}

	if(!(turn_params.no_tls && turn_params.no_dtls)) {
		adjust_key_file_names();
	}

	if(!turn_params.no_tls) {
		turn_params.tls_ctx_ssl23 = SSL_CTX_new(SSLv23_server_method()); /*compatibility mode */
		set_ctx(turn_params.tls_ctx_ssl23,"SSL23");
		if(!turn_params.no_tlsv1) {
			turn_params.tls_ctx_v1_0 = SSL_CTX_new(TLSv1_server_method());
			set_ctx(turn_params.tls_ctx_v1_0,"TLS1.0");
		}
#if defined(SSL_TXT_TLSV1_1)
		if(!turn_params.no_tlsv1_1) {
			turn_params.tls_ctx_v1_1 = SSL_CTX_new(TLSv1_1_server_method());
			set_ctx(turn_params.tls_ctx_v1_1,"TLS1.1");
		}
#if defined(SSL_TXT_TLSV1_2)
		if(!turn_params.no_tlsv1_2) {
			turn_params.tls_ctx_v1_2 = SSL_CTX_new(TLSv1_2_server_method());
			set_ctx(turn_params.tls_ctx_v1_2,"TLS1.2");
		}
#endif
#endif
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TLS cipher suite: %s\n",turn_params.cipher_list);
	}

	if(!turn_params.no_dtls) {
#if defined(TURN_NO_DTLS)
		TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: DTLS is not supported.\n");
#else
		if(OPENSSL_VERSION_NUMBER < 0x10000000L) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: TURN Server was compiled with rather old OpenSSL version, DTLS may not be working correctly.\n");
		}
		turn_params.dtls_ctx = SSL_CTX_new(DTLSv1_server_method());
		set_ctx(turn_params.dtls_ctx,"DTLS");
		SSL_CTX_set_read_ahead(turn_params.dtls_ctx, 1);
		TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS cipher suite: %s\n",turn_params.cipher_list);
#endif
	}
}

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