1
0
mirror of https://github.com/coturn/coturn.git synced 2025-10-23 20:11:17 +02:00
coturn/src/apps/relay/turncli.c
2014-05-26 07:37:16 +00:00

1263 lines
36 KiB
C

/*
* 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 "libtelnet.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 <event2/listener.h>
#include "userdb.h"
#include "mainrelay.h"
#include "ns_turn_utils.h"
#include "ns_turn_server.h"
#include "ns_turn_maps.h"
#include "apputils.h"
#include "turncli.h"
///////////////////////////////
struct cli_server cliserver;
int use_cli = 1;
ioa_addr cli_addr;
int cli_addr_set = 0;
int cli_port = CLI_DEFAULT_PORT;
char cli_password[CLI_PASSWORD_LENGTH] = "";
int cli_max_output_sessions = DEFAULT_CLI_MAX_OUTPUT_SESSIONS;
///////////////////////////////
struct cli_session {
evutil_socket_t fd;
int auth_completed;
size_t cmds;
struct bufferevent *bev;
ioa_addr addr;
telnet_t *ts;
FILE* f;
char realm[STUN_MAX_REALM_SIZE+1];
char origin[STUN_MAX_ORIGIN_SIZE+1];
realm_params_t *rp;
};
///////////////////////////////
#define CLI_PASSWORD_TRY_NUMBER (5)
static const char *CLI_HELP_STR[] =
{"",
" ?, h, help - print this text",
"",
" quit, q, exit, bye - end CLI session",
"",
" stop, shutdown, halt - shutdown TURN Server",
"",
" pc - print configuration",
"",
" sr <realm> - set CLI session realm",
"",
" ur - unset CLI session realm",
"",
" so <origin> - set CLI session origin",
"",
" uo - unset CLI session origin",
"",
" tc <param-name> - toggle a configuration parameter",
" (see pc command output for togglable param names)",
"",
" cc <param-name> <param-value> - change a configuration parameter",
" (see pc command output for changeable param names)",
"",
" ps [username] - print sessions, with optional exact user match",
"",
" psp <usernamestr> - print sessions, with partial user string match",
"",
" psd <file-name> - dump ps command output into file on the TURN server system",
"",
" pu [udp|tcp|dtls|tls]- print current users",
"",
" aas ip[:port} - add an alternate server reference",
" das ip[:port] - delete an alternate server reference",
" atas ip[:port] - add a TLS alternate server reference",
" dtas ip[:port] - delete a TLS alternate server reference",
"",
NULL};
static const char *CLI_GREETING_STR[] = {
"TURN Server",
TURN_SOFTWARE,
NULL};
static char CLI_CURSOR[] = "> ";
static const telnet_telopt_t cli_telopts[] = {
{ TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DONT },
{ TELNET_TELOPT_TTYPE, TELNET_WONT, TELNET_DONT },
{ TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DONT },
{ TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DONT },
{ TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DONT },
{ TELNET_TELOPT_BINARY, TELNET_WONT, TELNET_DONT },
{ TELNET_TELOPT_NAWS, TELNET_WONT, TELNET_DONT },
{ -1, 0, 0 }
};
struct toggleable_command {
const char *cmd;
vintp data;
};
struct toggleable_command tcmds[] = {
{"stale-nonce",&turn_params.stale_nonce},
{"stun-only",&turn_params.stun_only},
{"no-stun",&turn_params.no_stun},
{"secure-stun",&turn_params.secure_stun},
{"no-udp-relay",&turn_params.no_udp_relay},
{"no-tcp-relay",&turn_params.no_tcp_relay},
{"no-multicast-peers",&turn_params.no_multicast_peers},
{"no-loopback-peers",&turn_params.no_loopback_peers},
{"mobility",&turn_params.mobility},
{NULL,NULL}
};
///////////////////////////////
static void myprintf(struct cli_session *cs, const char *format, ...)
{
if(cs && format) {
va_list args;
va_start (args, format);
if(cs->f) {
vfprintf(cs->f, format, args);
} else {
telnet_vprintf(cs->ts, format, args);
}
va_end (args);
}
}
static void print_str_array(struct cli_session* cs, const char** sa)
{
if(cs && sa) {
int i=0;
while(sa[i]) {
myprintf(cs,"%s\n",sa[i]);
i++;
}
}
}
static const char* get_flag(int val)
{
if(val)
return "ON";
return "OFF";
}
static void cli_print_flag(struct cli_session* cs, int flag, const char* name, int changeable)
{
if(cs && cs->ts && name) {
const char *sc="";
if(changeable)
sc=" (*)";
myprintf(cs," %s: %s%s\n",name,get_flag(flag),sc);
}
}
static void cli_print_uint(struct cli_session* cs, unsigned long value, const char* name, int changeable)
{
if(cs && cs->ts && name) {
const char *sc="";
if(changeable==1)
sc=" (*)";
else if(changeable==2)
sc=" (**)";
myprintf(cs," %s: %lu%s\n",name,value,sc);
}
}
static void cli_print_str(struct cli_session* cs, const char *value, const char* name, int changeable)
{
if(cs && cs->ts && name && value) {
if(value[0] == 0)
value="empty";
const char *sc="";
if(changeable==1)
sc=" (*)";
else if(changeable==2)
sc=" (**)";
myprintf(cs," %s: %s%s\n",name,value,sc);
}
}
static void cli_print_addr(struct cli_session* cs, ioa_addr *value, int use_port, const char* name, int changeable)
{
if(cs && cs->ts && name && value) {
const char *sc="";
if(changeable==1)
sc=" (*)";
else if(changeable==2)
sc=" (**)";
char s[256];
if(!use_port)
addr_to_string_no_port(value,(u08bits*)s);
else
addr_to_string(value,(u08bits*)s);
myprintf(cs," %s: %s%s\n",name,s,sc);
}
}
static void cli_print_addr_list(struct cli_session* cs, turn_server_addrs_list_t *value, int use_port, const char* name, int changeable)
{
if(cs && cs->ts && name && value && value->size && value->addrs) {
const char *sc="";
if(changeable==1)
sc=" (*)";
else if(changeable==2)
sc=" (**)";
char s[256];
size_t i;
for(i=0;i<value->size;i++) {
if(!use_port)
addr_to_string_no_port(&(value->addrs[i]),(u08bits*)s);
else
addr_to_string(&(value->addrs[i]),(u08bits*)s);
myprintf(cs," %s: %s%s\n",name,s,sc);
}
}
}
static void cli_print_str_array(struct cli_session* cs, char **value, size_t sz, const char* name, int changeable)
{
if(cs && cs->ts && name && value && sz) {
const char *sc="";
if(changeable==1)
sc=" (*)";
else if(changeable==2)
sc=" (**)";
size_t i;
for(i=0;i<sz;i++) {
if(value[i])
myprintf(cs," %s: %s%s\n",name,value[i],sc);
}
}
}
static void cli_print_ip_range_list(struct cli_session* cs, ip_range_list_t *value, const char* name, int changeable)
{
if(cs && cs->ts && name && value && value->ranges_number && value->ranges) {
const char *sc="";
if(changeable==1)
sc=" (*)";
else if(changeable==2)
sc=" (**)";
size_t i;
for(i=0;i<value->ranges_number;++i) {
if(value->ranges[i])
myprintf(cs," %s: %s%s\n",name,value->ranges[i],sc);
}
}
}
static void toggle_cli_param(struct cli_session* cs, const char* pn)
{
if(cs && cs->ts && pn) {
int i=0;
while(tcmds[i].cmd && tcmds[i].data) {
if(strcmp(tcmds[i].cmd,pn) == 0) {
*(tcmds[i].data) = !(*(tcmds[i].data));
cli_print_flag(cs,*(tcmds[i].data),tcmds[i].cmd,0);
return;
}
++i;
}
myprintf(cs, "\n");
myprintf(cs, " Error: unknown or constant parameter: %s.\n",pn);
myprintf(cs, " You can toggle only the following parameters:\n");
myprintf(cs, "\n");
i=0;
while(tcmds[i].cmd && tcmds[i].data) {
cli_print_flag(cs,*(tcmds[i].data),tcmds[i].cmd,0);
++i;
}
myprintf(cs,"\n");
}
}
static void change_cli_param(struct cli_session* cs, const char* pn)
{
if(cs && cs->ts && pn) {
if(strstr(pn,"total-quota")==pn) {
int new_quota = change_total_quota(cs->realm, atoi(pn+strlen("total-quota")));
cli_print_uint(cs,(unsigned long)new_quota,"total-quota",2);
return;
} else if(strstr(pn,"user-quota")==pn) {
int new_quota = change_user_quota(cs->realm, atoi(pn+strlen("user-quota")));
cli_print_uint(cs,(unsigned long)new_quota,"user-quota",2);
return;
} else if(strstr(pn,"cli-max-output-sessions")==pn) {
cli_max_output_sessions = atoi(pn+strlen("cli-max-output-sessions"));
cli_print_uint(cs,(unsigned long)cli_max_output_sessions,"cli-max-output-sessions",2);
return;
}
myprintf(cs, "\n");
myprintf(cs, " Error: unknown or constant parameter: %s.\n",pn);
myprintf(cs, "\n");
}
}
struct ps_arg {
struct cli_session* cs;
size_t counter;
turn_time_t ct;
const char *username;
const char *pname;
int exact_match;
ur_string_map* users;
size_t *user_counters;
char **user_names;
size_t users_number;
};
static const char* pname(SOCKET_TYPE st)
{
switch(st) {
case TCP_SOCKET:
return "TCP";
case UDP_SOCKET:
return "UDP";
case TLS_SOCKET:
return "TLS";
case DTLS_SOCKET:
return "DTLS";
case TENTATIVE_TCP_SOCKET:
return "TCP/TLS";
default:
;
};
return "UNKNOWN";
}
static int print_session(ur_map_key_type key, ur_map_value_type value, void *arg)
{
if(key && value && arg) {
struct ps_arg *csarg = (struct ps_arg*)arg;
struct cli_session* cs = csarg->cs;
struct turn_session_info *tsi = (struct turn_session_info *)value;
if(cs->realm[0] && strcmp(cs->realm,tsi->realm))
return 0;
if(cs->origin[0] && strcmp(cs->origin,tsi->origin))
return 0;
if(csarg->users) {
ur_string_map_value_type value;
if(!ur_string_map_get(csarg->users, (ur_string_map_key_type)(char*)tsi->username, &value)) {
const char *pn=csarg->pname;
if(pn[0]) {
if(!strcmp(pn,"TLS") || !strcmp(pn,"tls") || !strcmp(pn,"Tls")) {
if(tsi->client_protocol != TLS_SOCKET)
return 0;
} else if(!strcmp(pn,"DTLS") || !strcmp(pn,"dtls") || !strcmp(pn,"Dtls")) {
if(tsi->client_protocol != DTLS_SOCKET)
return 0;
} else if(!strcmp(pn,"TCP") || !strcmp(pn,"tcp") || !strcmp(pn,"Tcp")) {
if(tsi->client_protocol != TCP_SOCKET)
return 0;
} else if(!strcmp(pn,"UDP") || !strcmp(pn,"udp") || !strcmp(pn,"Udp")) {
if(tsi->client_protocol != UDP_SOCKET)
return 0;
} else {
return 0;
}
}
value = (ur_string_map_value_type)csarg->users_number;
csarg->users_number += 1;
csarg->user_counters = (size_t*)turn_realloc(csarg->user_counters,
(size_t)value * sizeof(size_t),
csarg->users_number * sizeof(size_t));
csarg->user_names = (char**)turn_realloc(csarg->user_names,
(size_t)value * sizeof(char*),
csarg->users_number * sizeof(char*));
csarg->user_names[(size_t)value] = strdup((char*)tsi->username);
csarg->user_counters[(size_t)value] = 0;
ur_string_map_put(csarg->users, (ur_string_map_key_type)(char*)tsi->username, value);
}
csarg->user_counters[(size_t)value] += 1;
} else {
if(csarg->username[0]) {
if(csarg->exact_match) {
if(strcmp((char*)tsi->username, csarg->username))
return 0;
} else {
if(!strstr((char*)tsi->username, csarg->username))
return 0;
}
}
if(cs->f || (unsigned long)csarg->counter<(unsigned long)cli_max_output_sessions) {
myprintf(cs, "\n");
myprintf(cs," %lu) id=%018llu, user <%s>:\n",
(unsigned long)(csarg->counter+1),
(unsigned long long)tsi->id,
tsi->username);
if(tsi->realm[0])
myprintf(cs," realm: %s\n",tsi->realm);
if(tsi->origin[0])
myprintf(cs," origin: %s\n",tsi->origin);
if(turn_time_before(csarg->ct, tsi->start_time)) {
myprintf(cs," started: undefined time\n");
} else {
myprintf(cs," started %lu secs ago\n",(unsigned long)(csarg->ct - tsi->start_time));
}
if(turn_time_before(tsi->expiration_time,csarg->ct)) {
myprintf(cs," expired\n");
} else {
myprintf(cs," expiring in %lu secs\n",(unsigned long)(tsi->expiration_time - csarg->ct));
}
myprintf(cs," client protocol %s, relay protocol %s\n",pname(tsi->client_protocol),pname(tsi->peer_protocol));
{
if(!tsi->local_addr_data.saddr[0])
addr_to_string(&(tsi->local_addr_data.addr),(u08bits*)tsi->local_addr_data.saddr);
if(!tsi->remote_addr_data.saddr[0])
addr_to_string(&(tsi->remote_addr_data.addr),(u08bits*)tsi->remote_addr_data.saddr);
if(!tsi->relay_addr_data.saddr[0])
addr_to_string(&(tsi->relay_addr_data.addr),(u08bits*)tsi->relay_addr_data.saddr);
myprintf(cs," client addr %s, server addr %s\n",
tsi->remote_addr_data.saddr,
tsi->local_addr_data.saddr);
myprintf(cs," relay addr %s\n",
tsi->relay_addr_data.saddr);
}
myprintf(cs," fingerprints enforced: %s\n",get_flag(tsi->enforce_fingerprints));
myprintf(cs," mobile: %s\n",get_flag(tsi->is_mobile));
if(tsi->tls_method[0]) {
myprintf(cs," TLS method: %s\n",tsi->tls_method);
myprintf(cs," TLS cipher: %s\n",tsi->tls_cipher);
}
myprintf(cs," usage: rp=%lu, rb=%lu, sp=%lu, sb=%lu\n",(unsigned long)(tsi->received_packets), (unsigned long)(tsi->received_bytes),(unsigned long)(tsi->sent_packets),(unsigned long)(tsi->sent_bytes));
myprintf(cs," rate: r=%lu, s=%lu, total=%lu (bytes per sec)\n",(unsigned long)(tsi->received_rate), (unsigned long)(tsi->sent_rate),(unsigned long)(tsi->total_rate));
if(tsi->main_peers_size) {
myprintf(cs," peers:\n");
size_t i;
for(i=0;i<tsi->main_peers_size;++i) {
if(!(tsi->main_peers_data[i].saddr[0]))
addr_to_string(&(tsi->main_peers_data[i].addr),(u08bits*)tsi->main_peers_data[i].saddr);
myprintf(cs," %s\n",tsi->main_peers_data[i].saddr);
}
if(tsi->extra_peers_size && tsi->extra_peers_data) {
for(i=0;i<tsi->extra_peers_size;++i) {
if(!(tsi->extra_peers_data[i].saddr[0]))
addr_to_string(&(tsi->extra_peers_data[i].addr),(u08bits*)tsi->extra_peers_data[i].saddr);
myprintf(cs," %s\n",tsi->extra_peers_data[i].saddr);
}
}
}
}
}
csarg->counter += 1;
}
return 0;
}
static void print_sessions(struct cli_session* cs, const char* pn, int exact_match, int print_users)
{
if(cs && cs->ts && pn) {
while(pn[0] == ' ') ++pn;
if(pn[0] == '*') ++pn;
const char *uname="";
if(!print_users) {
uname = pn;
pn = "";
}
struct ps_arg arg = {cs,0,0,uname,pn,exact_match,NULL,NULL,NULL,0};
arg.ct = turn_time();
if(print_users) {
arg.users = ur_string_map_create(NULL);
}
ur_map_foreach_arg(cliserver.sessions, (foreachcb_arg_type)print_session, &arg);
myprintf(cs,"\n");
if(!print_users && !(cs->f)) {
if((unsigned long)arg.counter > (unsigned long)cli_max_output_sessions) {
myprintf(cs,"...\n");
myprintf(cs,"\n");
}
} else if(arg.user_counters && arg.user_names) {
size_t i;
for(i=0;i<arg.users_number;++i) {
if(arg.user_names[i]) {
myprintf(cs," user: <%s>, %lu sessions\n",
arg.user_names[i],
(unsigned long)arg.user_counters[i]);
}
}
myprintf(cs,"\n");
}
{
char ts[1025];
snprintf(ts,sizeof(ts)," Total sessions");
if(cs->realm[0]) {
snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts)," for realm %s",cs->realm);
if(cs->origin[0])
snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts)," and for origin %s",cs->origin);
} else {
if(cs->origin[0])
snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts)," for origin %s",cs->origin);
}
snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts),": %lu", (unsigned long)arg.counter);
myprintf(cs,"%s\n", ts);
myprintf(cs,"\n");
}
if(!print_users && !(cs->f)) {
if((unsigned long)arg.counter > (unsigned long)cli_max_output_sessions) {
myprintf(cs," Warning: too many output sessions, more than the\n");
myprintf(cs," current value of cli-max-output-sessions CLI parameter.\n");
myprintf(cs," Refine your request or increase cli-max-output-sessions value.\n");
myprintf(cs,"\n");
}
}
if(arg.user_counters)
turn_free(arg.user_counters,sizeof(size_t)*arg.users_number);
if(arg.user_names) {
size_t i;
for(i=0;i<arg.users_number;++i) {
if(arg.user_names[i])
turn_free(arg.user_names[i],strlen(arg.user_names[i])+1);
}
turn_free(arg.user_names,sizeof(char*) * arg.users_number);
}
if(arg.users)
ur_string_map_free(&arg.users);
}
}
static void cli_print_configuration(struct cli_session* cs)
{
if(cs) {
myprintf(cs,"\n");
cli_print_flag(cs,turn_params.verbose,"verbose",0);
cli_print_flag(cs,turn_params.turn_daemon,"daemon process",0);
cli_print_flag(cs,turn_params.stale_nonce,"stale-nonce",1);
cli_print_flag(cs,turn_params.stun_only,"stun-only",1);
cli_print_flag(cs,turn_params.no_stun,"no-stun",1);
cli_print_flag(cs,turn_params.secure_stun,"secure-stun",1);
cli_print_flag(cs,turn_params.do_not_use_config_file,"do-not-use-config-file",0);
cli_print_flag(cs,turn_params.rfc5780,"RFC5780 support",0);
cli_print_uint(cs,(unsigned int)turn_params.net_engine_version,"net engine version",0);
cli_print_str(cs,turn_params.net_engine_version_txt[(int)turn_params.net_engine_version],"net engine",0);
cli_print_flag(cs,turn_params.fingerprint,"enforce fingerprints",0);
cli_print_flag(cs,turn_params.mobility,"mobility",1);
cli_print_flag(cs,turn_params.udp_self_balance,"udp-self-balance",0);
cli_print_str(cs,turn_params.pidfile,"pidfile",0);
cli_print_uint(cs,(unsigned long)getuid(),"process user ID",0);
cli_print_uint(cs,(unsigned long)getgid(),"process group ID",0);
{
char wd[1025];
if(getcwd(wd,sizeof(wd)-1)) {
cli_print_str(cs,wd,"process dir",0);
}
}
myprintf(cs,"\n");
if(turn_params.cipher_list[0])
cli_print_str(cs,turn_params.cipher_list,"cipher-list",0);
else
cli_print_str(cs,DEFAULT_CIPHER_LIST,"cipher-list",0);
cli_print_str(cs,turn_params.ec_curve_name,"ec-curve-name",0);
{
if(turn_params.dh_key_size == DH_CUSTOM)
cli_print_str(cs,turn_params.dh_file,"dh-file",0);
else {
unsigned int dh_key_length = 1066;
if(turn_params.dh_key_size == DH_566)
dh_key_length = 566;
else if(turn_params.dh_key_size == DH_2066)
dh_key_length = 2066;
cli_print_uint(cs,(unsigned long)dh_key_length,"DH-key-length",0);
}
}
cli_print_str(cs,turn_params.ca_cert_file,"Certificate Authority file",0);
cli_print_str(cs,turn_params.cert_file,"Certificate file",0);
cli_print_str(cs,turn_params.pkey_file,"Private Key file",0);
if(turn_params.shatype == SHATYPE_SHA256)
cli_print_str(cs,"SHA256","SHA type",0);
else
cli_print_str(cs,"SHA1","SHA type",0);
myprintf(cs,"\n");
cli_print_str_array(cs,turn_params.listener.addrs,turn_params.listener.addrs_number,"Listener addr",0);
if(turn_params.listener_ifname[0])
cli_print_str(cs,turn_params.listener_ifname,"listener-ifname",0);
cli_print_flag(cs,turn_params.no_udp,"no-udp",0);
cli_print_flag(cs,turn_params.no_tcp,"no-tcp",0);
cli_print_flag(cs,turn_params.no_dtls,"no-dtls",0);
cli_print_flag(cs,turn_params.no_tls,"no-tls",0);
#ifndef OPENSSL_NO_SSL2
cli_print_flag(cs,(!turn_params.no_sslv2 && !turn_params.no_tls),"SSLv2",0);
#else
cli_print_flag(cs,0,"SSLv2",0);
#endif
cli_print_flag(cs,(!turn_params.no_sslv3 && !turn_params.no_tls),"SSLv3",0);
cli_print_flag(cs,(!turn_params.no_tlsv1 && !turn_params.no_tls),"TLSv1.0",0);
cli_print_flag(cs,(!turn_params.no_tlsv1_1 && !turn_params.no_tls),"TLSv1.1",0);
cli_print_flag(cs,(!turn_params.no_tlsv1_2 && !turn_params.no_tls),"TLSv1.2",0);
cli_print_uint(cs,(unsigned long)turn_params.listener_port,"listener-port",0);
cli_print_uint(cs,(unsigned long)turn_params.tls_listener_port,"tls-listener-port",0);
cli_print_uint(cs,(unsigned long)turn_params.alt_listener_port,"alt-listener-port",0);
cli_print_uint(cs,(unsigned long)turn_params.alt_tls_listener_port,"alt-tls-listener-port",0);
cli_print_addr(cs,turn_params.external_ip,0,"External public IP",0);
myprintf(cs,"\n");
cli_print_addr_list(cs,&turn_params.aux_servers_list,1,"Aux server",0);
cli_print_addr_list(cs,&turn_params.alternate_servers_list,1,"Alternate server",0);
cli_print_addr_list(cs,&turn_params.tls_alternate_servers_list,1,"TLS alternate server",0);
myprintf(cs,"\n");
cli_print_str_array(cs,turn_params.relay_addrs,turn_params.relays_number,"Relay addr",0);
if(turn_params.relay_ifname[0])
cli_print_str(cs,turn_params.relay_ifname,"relay-ifname",0);
cli_print_flag(cs,turn_params.server_relay,"server-relay",0);
cli_print_flag(cs,turn_params.no_udp_relay,"no-udp-relay",1);
cli_print_flag(cs,turn_params.no_tcp_relay,"no-tcp-relay",1);
cli_print_uint(cs,(unsigned long)turn_params.min_port,"min-port",0);
cli_print_uint(cs,(unsigned long)turn_params.max_port,"max-port",0);
cli_print_uint(cs,(unsigned long)turn_params.bps_capacity,"bps_capacity",0);
cli_print_ip_range_list(cs,&turn_params.ip_whitelist,"Whitelist IP",0);
cli_print_ip_range_list(cs,&turn_params.ip_blacklist,"Blacklist IP",0);
cli_print_flag(cs,turn_params.no_multicast_peers,"no-multicast-peers",1);
cli_print_flag(cs,turn_params.no_loopback_peers,"no-loopback-peers",1);
myprintf(cs,"\n");
if(turn_params.default_users_db.persistent_users_db.userdb[0]) {
switch(turn_params.default_users_db.userdb_type) {
case TURN_USERDB_TYPE_FILE:
cli_print_str(cs,"file","DB type",0);
break;
#if !defined(TURN_NO_PQ)
case TURN_USERDB_TYPE_PQ:
cli_print_str(cs,"Postgres","DB type",0);
break;
#endif
#if !defined(TURN_NO_MYSQL)
case TURN_USERDB_TYPE_MYSQL:
cli_print_str(cs,"MySQL/MariaDB","DB type",0);
break;
#endif
#if !defined(TURN_NO_HIREDIS)
case TURN_USERDB_TYPE_REDIS:
cli_print_str(cs,"redis","DB type",0);
break;
#endif
default:
cli_print_str(cs,"unknown","DB type",0);
};
cli_print_str(cs,turn_params.default_users_db.persistent_users_db.userdb,"DB",0);
} else {
cli_print_str(cs,"none","DB type",0);
cli_print_str(cs,"none","DB",0);
}
#if !defined(TURN_NO_HIREDIS)
if(turn_params.use_redis_statsdb && turn_params.redis_statsdb[0])
cli_print_str(cs,turn_params.redis_statsdb,"Redis Statistics DB",0);
#endif
myprintf(cs,"\n");
{
char * rn = get_realm(NULL)->options.name;
if(rn[0])
cli_print_str(cs,rn,"Default realm",0);
}
if(cs->realm[0])
cli_print_str(cs,cs->realm,"CLI session realm",0);
else
cli_print_str(cs,get_realm(NULL)->options.name,"CLI session realm",0);
if(cs->origin[0])
cli_print_str(cs,cs->origin,"CLI session origin",0);
if(turn_params.ct == TURN_CREDENTIALS_LONG_TERM)
cli_print_flag(cs,1,"Long-term authorization mechanism",0);
else if(turn_params.ct == TURN_CREDENTIALS_SHORT_TERM)
cli_print_flag(cs,1,"Short-term authorization mechanism",0);
else
cli_print_flag(cs,1,"Anonymous credentials",0);
cli_print_flag(cs,turn_params.use_auth_secret_with_timestamp,"REST API support",0);
if(turn_params.use_auth_secret_with_timestamp && turn_params.rest_api_separator)
cli_print_uint(cs,turn_params.rest_api_separator,"REST API separator ASCII number",0);
myprintf(cs,"\n");
cli_print_uint(cs,(unsigned long)cs->rp->status.total_current_allocs,"total-current-allocs",0);
cli_print_uint(cs,(unsigned long)cs->rp->options.perf_options.total_quota,"total-quota",2);
cli_print_uint(cs,(unsigned long)cs->rp->options.perf_options.user_quota,"user-quota",2);
cli_print_uint(cs,(unsigned long)cs->rp->options.perf_options.max_bps,"max-bps",0);
myprintf(cs,"\n");
cli_print_uint(cs,(unsigned long)cli_max_output_sessions,"cli-max-output-sessions",2);
{
myprintf(cs,"\n");
const char *str=" (Note 1: parameters with (*) are toggleable)";
myprintf(cs,"%s\n",str);
myprintf(cs,"\n");
str=" (Note 2: parameters with (**) are changeable)";
myprintf(cs,"%s\n",str);
myprintf(cs,"\n");
}
}
}
static void close_cli_session(struct cli_session* cs);
static int run_cli_output(struct cli_session* cs, const char *buf, unsigned int len)
{
if(cs && buf && len) {
if(bufferevent_write(cs->bev, buf, len)< 0) {
return -1;
}
return 0;
}
return -1;
}
static void close_cli_session(struct cli_session* cs)
{
if(cs) {
addr_debug_print(cliserver.verbose, &(cs->addr),"CLI session disconnected from");
if(cs->ts) {
telnet_free(cs->ts);
cs->ts = NULL;
}
if(cs->bev) {
bufferevent_flush(cs->bev,EV_READ|EV_WRITE,BEV_FLUSH);
bufferevent_disable(cs->bev,EV_READ|EV_WRITE);
bufferevent_free(cs->bev);
cs->bev=NULL;
}
if(cs->fd>=0) {
close(cs->fd);
cs->fd = -1;
}
turn_free(cs,sizeof(struct cli_session));
}
}
static void type_cli_cursor(struct cli_session* cs)
{
if(cs && (cs->bev)) {
myprintf(cs, "%s", CLI_CURSOR);
}
}
static void cli_add_alternate_server(struct cli_session* cs, const char* pn)
{
if(cs && cs->ts && pn && *pn) {
add_alternate_server(pn);
}
}
static void cli_add_tls_alternate_server(struct cli_session* cs, const char* pn)
{
if(cs && cs->ts && pn && *pn) {
add_tls_alternate_server(pn);
}
}
static void cli_del_alternate_server(struct cli_session* cs, const char* pn)
{
if(cs && cs->ts && pn && *pn) {
del_alternate_server(pn);
}
}
static void cli_del_tls_alternate_server(struct cli_session* cs, const char* pn)
{
if(cs && cs->ts && pn && *pn) {
del_tls_alternate_server(pn);
}
}
static int run_cli_input(struct cli_session* cs, const char *buf0, unsigned int len)
{
int ret = 0;
if(cs && buf0 && cs->ts && cs->bev) {
char *buf = (char*)malloc(len+1);
ns_bcopy(buf0,buf,len);
buf[len]=0;
char *cmd = buf;
while((cmd[0]==' ') || (cmd[0]=='\t')) ++cmd;
size_t sl = strlen(cmd);
while(sl) {
char c = cmd[sl-1];
if((c==10)||(c==13)) {
cmd[sl-1]=0;
--sl;
} else {
break;
}
}
if(sl) {
cs->cmds += 1;
if(cli_password[0] && !(cs->auth_completed)) {
if(strcmp(cmd,cli_password)) {
if(cs->cmds>=CLI_PASSWORD_TRY_NUMBER) {
addr_debug_print(1, &(cs->addr),"CLI authentication error");
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"CLI authentication error\n");
close_cli_session(cs);
} else {
const char* ipwd="Enter password: ";
myprintf(cs,"%s\n",ipwd);
}
} else {
cs->auth_completed = 1;
addr_debug_print(1, &(cs->addr),"CLI authentication success");
type_cli_cursor(cs);
}
} else if((strcmp(cmd,"bye") == 0)||(strcmp(cmd,"quit") == 0)||(strcmp(cmd,"exit") == 0)||(strcmp(cmd,"q") == 0)) {
const char* str="Bye !";
myprintf(cs,"%s\n",str);
close_cli_session(cs);
ret = -1;
} else if((strcmp(cmd,"halt") == 0)||(strcmp(cmd,"shutdown") == 0)||(strcmp(cmd,"stop") == 0)) {
addr_debug_print(1, &(cs->addr),"Shutdown command received from CLI user");
const char* str="TURN server is shutting down";
myprintf(cs,"%s\n",str);
close_cli_session(cs);
turn_params.stop_turn_server = 1;
sleep(10);
exit(0);
} else if((strcmp(cmd,"?") == 0)||(strcmp(cmd,"h") == 0)||(strcmp(cmd,"help") == 0)) {
print_str_array(cs, CLI_GREETING_STR);
print_str_array(cs, CLI_HELP_STR);
type_cli_cursor(cs);
} else if(strcmp(cmd,"pc")==0) {
cli_print_configuration(cs);
type_cli_cursor(cs);
} else if(strstr(cmd,"tc ") == cmd) {
toggle_cli_param(cs,cmd+3);
} else if(strstr(cmd,"sr ") == cmd) {
STRCPY(cs->realm,cmd+3);
cs->rp = get_realm(cs->realm);
type_cli_cursor(cs);
} else if(strcmp(cmd,"ur") == 0) {
cs->realm[0]=0;
cs->rp = get_realm(NULL);
type_cli_cursor(cs);
} else if(strstr(cmd,"so ") == cmd) {
STRCPY(cs->origin,cmd+3);
type_cli_cursor(cs);
} else if(strcmp(cmd,"uo") == 0) {
cs->origin[0]=0;
type_cli_cursor(cs);
} else if(strstr(cmd,"tc") == cmd) {
toggle_cli_param(cs,cmd+2);
type_cli_cursor(cs);
} else if(strstr(cmd,"psp") == cmd) {
print_sessions(cs,cmd+3,0,0);
type_cli_cursor(cs);
} else if(strstr(cmd,"psd") == cmd) {
cmd += 3;
while(cmd[0]==' ') ++cmd;
if(!(cmd[0])) {
const char* str="You have to provide file name for ps dump\n";
myprintf(cs,"%s\n",str);
} else {
cs->f = fopen(cmd,"w");
if(!(cs->f)) {
const char* str="Cannot open file for writing\n";
myprintf(cs,"%s\n",str);
} else {
print_sessions(cs,"",1,0);
fclose(cs->f);
cs->f = NULL;
}
}
type_cli_cursor(cs);
} else if(strstr(cmd,"pu ") == cmd) {
print_sessions(cs,cmd+3,0,1);
type_cli_cursor(cs);
} else if(!strcmp(cmd,"pu")) {
print_sessions(cs,cmd+2,0,1);
type_cli_cursor(cs);
} else if(strstr(cmd,"ps") == cmd) {
print_sessions(cs,cmd+2,1,0);
type_cli_cursor(cs);
} else if(strstr(cmd,"cc ") == cmd) {
change_cli_param(cs,cmd+3);
type_cli_cursor(cs);
} else if(strstr(cmd,"cc") == cmd) {
change_cli_param(cs,cmd+2);
type_cli_cursor(cs);
} else if(strstr(cmd,"aas ") == cmd) {
cli_add_alternate_server(cs,cmd+4);
type_cli_cursor(cs);
} else if(strstr(cmd,"atas ") == cmd) {
cli_add_tls_alternate_server(cs,cmd+5);
type_cli_cursor(cs);
} else if(strstr(cmd,"das ") == cmd) {
cli_del_alternate_server(cs,cmd+4);
type_cli_cursor(cs);
} else if(strstr(cmd,"dtas ") == cmd) {
cli_del_tls_alternate_server(cs,cmd+5);
type_cli_cursor(cs);
} else {
const char* str="Unknown command\n";
myprintf(cs,"%s\n",str);
type_cli_cursor(cs);
}
} else {
type_cli_cursor(cs);
}
free(buf);
}
return ret;
}
static void cli_socket_input_handler_bev(struct bufferevent *bev, void* arg)
{
if (bev && arg) {
struct cli_session* cs = (struct cli_session*) arg;
if(!(cs->ts))
return;
stun_buffer buf;
if(cs->bev) {
int len = (int)bufferevent_read(cs->bev, buf.buf, STUN_BUFFER_SIZE-1);
if(len < 0) {
close_cli_session(cs);
return;
} else if(len == 0) {
return;
}
buf.len = len;
buf.offset = 0;
buf.buf[len]=0;
telnet_recv(cs->ts, (const char *)buf.buf, (unsigned int)(buf.len));
}
}
}
static void cli_eventcb_bev(struct bufferevent *bev, short events, void *arg)
{
UNUSED_ARG(bev);
if (events & BEV_EVENT_CONNECTED) {
// Connect okay
} else if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
if (arg) {
struct cli_session* cs = (struct cli_session*) arg;
close_cli_session(cs);
}
}
}
static void cli_telnet_event_handler(telnet_t *telnet, telnet_event_t *event, void *user_data)
{
if (user_data && telnet) {
struct cli_session *cs = (struct cli_session *) user_data;
switch (event->type){
case TELNET_EV_DATA:
run_cli_input(cs, event->data.buffer, event->data.size);
break;
case TELNET_EV_SEND:
run_cli_output(cs, event->data.buffer, event->data.size);
break;
case TELNET_EV_ERROR:
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TELNET error: %s", event->error.msg);
break;
default:
;
};
}
}
static void cliserver_input_handler(struct evconnlistener *l, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *arg)
{
UNUSED_ARG(l);
UNUSED_ARG(arg);
UNUSED_ARG(socklen);
addr_debug_print(cliserver.verbose, (ioa_addr*)sa,"CLI connected to");
struct cli_session *clisession = (struct cli_session*)turn_malloc(sizeof(struct cli_session));
ns_bzero(clisession,sizeof(struct cli_session));
clisession->rp = get_realm(NULL);
set_socket_options_fd(fd, 1, sa->sa_family);
clisession->fd = fd;
addr_cpy(&(clisession->addr),(ioa_addr*)sa);
clisession->bev = bufferevent_socket_new(cliserver.event_base,
fd,
BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS);
bufferevent_setcb(clisession->bev, cli_socket_input_handler_bev, NULL,
cli_eventcb_bev, clisession);
bufferevent_setwatermark(clisession->bev, EV_READ|EV_WRITE, 0, BUFFEREVENT_HIGH_WATERMARK);
bufferevent_enable(clisession->bev, EV_READ); /* Start reading. */
clisession->ts = telnet_init(cli_telopts, cli_telnet_event_handler, 0, clisession);
if(!(clisession->ts)) {
const char *str = "Cannot open telnet session\n";
addr_debug_print(cliserver.verbose, (ioa_addr*)sa,str);
close_cli_session(clisession);
} else {
print_str_array(clisession, CLI_GREETING_STR);
telnet_printf(clisession->ts,"\n");
telnet_printf(clisession->ts,"Type '?' for help\n");
if(cli_password[0]) {
const char* ipwd="Enter password: ";
telnet_printf(clisession->ts,"%s\n",ipwd);
} else {
type_cli_cursor(clisession);
}
}
}
void setup_cli_thread(void)
{
cliserver.event_base = turn_event_base_new();
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"IO method (cli thread): %s\n",event_base_get_method(cliserver.event_base));
struct bufferevent *pair[2];
int opts = BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS;
opts |= BEV_OPT_THREADSAFE;
bufferevent_pair_new(cliserver.event_base, opts, pair);
cliserver.in_buf = pair[0];
cliserver.out_buf = pair[1];
bufferevent_setcb(cliserver.in_buf, cli_server_receive_message, NULL, NULL, &cliserver);
bufferevent_enable(cliserver.in_buf, EV_READ);
if(!cli_addr_set) {
if(make_ioa_addr((const u08bits*)CLI_DEFAULT_IP,0,&cli_addr)<0) {
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot set cli address %s\n",CLI_DEFAULT_IP);
return;
}
}
addr_set_port(&cli_addr,cli_port);
cliserver.listen_fd = socket(cli_addr.ss.sa_family, SOCK_STREAM, 0);
if (cliserver.listen_fd < 0) {
perror("socket");
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot open CLI socket\n");
return;
}
if(addr_bind(cliserver.listen_fd,&cli_addr,1)<0) {
perror("Cannot bind CLI socket to addr");
char saddr[129];
addr_to_string(&cli_addr,(u08bits*)saddr);
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot bind CLI listener socket to addr %s\n",saddr);
socket_closesocket(cliserver.listen_fd);
return;
}
socket_tcp_set_keepalive(cliserver.listen_fd);
socket_set_nonblocking(cliserver.listen_fd);
cliserver.l = evconnlistener_new(cliserver.event_base,
cliserver_input_handler, &cliserver,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
1024, cliserver.listen_fd);
if(!(cliserver.l)) {
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot create CLI listener\n");
socket_closesocket(cliserver.listen_fd);
return;
}
cliserver.sessions = ur_map_create();
addr_debug_print(cliserver.verbose, &cli_addr,"CLI listener opened on ");
}
void cli_server_receive_message(struct bufferevent *bev, void *ptr)
{
UNUSED_ARG(ptr);
struct turn_session_info *tsi = (struct turn_session_info*)turn_malloc(sizeof(struct turn_session_info));
turn_session_info_init(tsi);
int n = 0;
struct evbuffer *input = bufferevent_get_input(bev);
while ((n = evbuffer_remove(input, tsi, sizeof(struct turn_session_info))) > 0) {
if (n != sizeof(struct turn_session_info)) {
fprintf(stderr,"%s: Weird CLI buffer error: size=%d\n",__FUNCTION__,n);
continue;
}
ur_map_value_type t = 0;
if (ur_map_get(cliserver.sessions, (ur_map_key_type)tsi->id, &t) && t) {
struct turn_session_info *old = (struct turn_session_info*)t;
turn_session_info_clean(old);
turn_free(old,sizeof(struct turn_session_info));
ur_map_del(cliserver.sessions, (ur_map_key_type)tsi->id, NULL);
}
if(tsi->valid) {
ur_map_put(cliserver.sessions, (ur_map_key_type)tsi->id, (ur_map_value_type)tsi);
tsi = (struct turn_session_info*)turn_malloc(sizeof(struct turn_session_info));
turn_session_info_init(tsi);
} else {
turn_session_info_clean(tsi);
}
}
if(tsi) {
turn_session_info_clean(tsi);
turn_free(tsi,sizeof(struct turn_session_info));
}
}
int send_turn_session_info(struct turn_session_info* tsi)
{
int ret = -1;
if(!use_cli)
return ret;
if(tsi) {
struct evbuffer *output = bufferevent_get_output(cliserver.out_buf);
if(output) {
if(evbuffer_add(output,tsi,sizeof(struct turn_session_info))>=0) {
ret = 0;
}
}
}
return ret;
}
///////////////////////////////