/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * https://opensource.org/license/bsd-3-clause
 *
 * 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if defined(_MSC_VER)
#include <direct.h>
#else
#include <unistd.h>
#endif

#if defined(__unix__) || defined(unix) || defined(__APPLE__)
#include <ifaddrs.h>
#include <libgen.h>
#include <sys/resource.h>
#include <sys/time.h>
#endif

#include <limits.h>

#include <getopt.h>
#include <locale.h>

#include <signal.h>

#include "libtelnet.h"

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

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

#include "mainrelay.h"
#include "userdb.h"

#include "ns_ioalib_impl.h" // for ioa_socket, ip_range_t, ioa_network_...
#include "ns_sm.h"          // for new_super_memory_region, super_memory_t
#include "ns_turn_defs.h"   // for uint8_t, STRCPY, UNUSED_ARG, getcwd
#include "ns_turn_maps.h"
#include "ns_turn_msg.h" // for check_password, band_limit_t, conver...
#include "ns_turn_server.h"
#include "ns_turn_session.h" // for turn_session_info, addr_data, _realm...
#include "ns_turn_utils.h"

#include "apputils.h"
#include "stun_buffer.h" // for stun_buffer

#include "turn_admin_server.h"

#include "http_server.h"

#include "dbdrivers/dbdriver.h"

#include "tls_listener.h"

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

struct bufferevent;
struct evconnlistener;
struct str_buffer;

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

struct admin_server adminserver;

/* Guards the HTTPS admin logon brute-force throttle table (defined lower in this
 * file). Declared here because setup_admin_thread() initializes it. */
static TURN_MUTEX_DECLARE(admin_logon_throttle_mutex)

    bool use_cli = false;

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;

int use_web_admin = 0;

ioa_addr web_admin_addr;
int web_admin_addr_set = 0;

int web_admin_port = WEB_ADMIN_DEFAULT_PORT;

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

struct cli_session {
  evutil_socket_t fd;
  int auth_completed;
  size_t cmds;
  struct bufferevent *bev;
  ioa_addr addr;
  telnet_t *ts;
  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",
                                     "",
                                     "  drain - drain TURN Server, then shutdown",
                                     "     (wait for all allocations to go away, reject new)",
                                     "",
                                     "  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",
                                     "",
                                     "  pu [udp|tcp|dtls|tls]- print current users",
                                     "",
                                     "  lr - log reset",
                                     "",
                                     "  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",
                                     "",
                                     "  cs <session-id> - cancel session, forcefully",
                                     "",
                                     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;
  bool *data;
};

struct toggleable_command tcmds[] = {
    //{"stale-nonce", &turn_params.stale_nonce}, // TODO re-enable this option by separating from rest of bools
    {"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},
    {"allow-loopback-peers", &turn_params.allow_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);
    telnet_vprintf(cs->ts, format, args);
    va_end(args);
  }
}

static void log_reset(struct cli_session *cs) {
  if (cs) {
    reset_rtpprintf();
    myprintf(cs, "  log reset done\n");
  }
}

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[MAX_IOA_ADDR_STRING];
    if (!use_port) {
      addr_to_string_no_port(value, s);
    } else {
      addr_to_string(value, 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[MAX_IOA_ADDR_STRING];
    size_t i;
    for (i = 0; i < value->size; i++) {
      if (!use_port) {
        addr_to_string_no_port(&(value->addrs[i]), s);
      } else {
        addr_to_string(&(value->addrs[i]), 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->rs) {
    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->rs[i].realm[0]) {
        if (cs->realm[0] && strcmp(cs->realm, value->rs[i].realm) != 0) {
          continue;
        } else {
          myprintf(cs, "  %s: %s (%s)%s\n", name, value->rs[i].str, value->rs[i].realm, sc);
        }
      } else {
        myprintf(cs, "  %s: %s%s\n", name, value->rs[i].str, 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) {
      turn_params.total_quota = atoi(pn + strlen("total-quota"));
      cli_print_uint(cs, (unsigned long)turn_params.total_quota, "total-quota", 2);
      return;
    } else if (strstr(pn, "user-quota") == pn) {
      turn_params.user_quota = atoi(pn + strlen("user-quota"));
      cli_print_uint(cs, (unsigned long)turn_params.user_quota, "user-quota", 2);
      return;
    } else if (strstr(pn, "max-bps") == pn) {
      set_max_bps((band_limit_t)strtoul(pn + strlen("max-bps"), NULL, 10));
      cli_print_uint(cs, (unsigned long)get_max_bps(), "max-bps", 2);
      return;
    } else if (strstr(pn, "bps-capacity") == pn) {
      set_bps_capacity((band_limit_t)strtoul(pn + strlen("bps-capacity"), NULL, 10));
      cli_print_uint(cs, (unsigned long)get_bps_capacity(), "bps-capacity", 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 bool 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) != 0) {
      return false;
    }

    if (cs->origin[0] && strcmp(cs->origin, tsi->origin) != 0) {
      return false;
    }

    if (csarg->users) {

      const char *pn = csarg->pname;
      if (pn[0]) {
        if (!strcmp(pn, "TLS") || !strcmp(pn, "tls") || !strcmp(pn, "Tls")) {
          if ((tsi->client_protocol != TLS_SOCKET) && (tsi->client_protocol != TLS_SCTP_SOCKET)) {
            return false;
          }
        } else if (!strcmp(pn, "DTLS") || !strcmp(pn, "dtls") || !strcmp(pn, "Dtls")) {
          if (tsi->client_protocol != DTLS_SOCKET) {
            return false;
          }
        } else if (!strcmp(pn, "TCP") || !strcmp(pn, "tcp") || !strcmp(pn, "Tcp")) {
          if ((tsi->client_protocol != TCP_SOCKET) && (tsi->client_protocol != SCTP_SOCKET)) {
            return false;
          }
        } else if (!strcmp(pn, "UDP") || !strcmp(pn, "udp") || !strcmp(pn, "Udp")) {
          if (tsi->client_protocol != UDP_SOCKET) {
            return false;
          }
        } else {
          return false;
        }
      }

      ur_string_map_value_type value;
      if (!ur_string_map_get(csarg->users, (ur_string_map_key_type)(char *)tsi->username, &value)) {
        value = (ur_string_map_value_type)csarg->users_number;
        csarg->users_number += 1;
        csarg->user_counters = (size_t *)realloc(csarg->user_counters, csarg->users_number * sizeof(size_t));
        csarg->user_names = (char **)realloc(csarg->user_names, 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) != 0) {
            return false;
          }
        } else {
          if (!strstr((char *)tsi->username, csarg->username)) {
            return false;
          }
        }
      }
      if ((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", socket_type_name(tsi->client_protocol),
                 socket_type_name(tsi->peer_protocol));
        {
          if (!tsi->local_addr_data.saddr[0]) {
            addr_to_string(&(tsi->local_addr_data.addr), tsi->local_addr_data.saddr);
          }
          if (!tsi->remote_addr_data.saddr[0]) {
            addr_to_string(&(tsi->remote_addr_data.addr), tsi->remote_addr_data.saddr);
          }
          if (!tsi->relay_addr_data_ipv4.saddr[0]) {
            addr_to_string(&(tsi->relay_addr_data_ipv4.addr), tsi->relay_addr_data_ipv4.saddr);
          }
          if (!tsi->relay_addr_data_ipv6.saddr[0]) {
            addr_to_string(&(tsi->relay_addr_data_ipv6.addr), tsi->relay_addr_data_ipv6.saddr);
          }
          myprintf(cs, "      client addr %s, server addr %s\n", tsi->remote_addr_data.saddr,
                   tsi->local_addr_data.saddr);
          if (tsi->relay_addr_data_ipv4.saddr[0]) {
            myprintf(cs, "      relay addr %s\n", tsi->relay_addr_data_ipv4.saddr);
          }
          if (tsi->relay_addr_data_ipv6.saddr[0]) {
            myprintf(cs, "      relay addr %s\n", tsi->relay_addr_data_ipv6.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);
        }
        if (tsi->bps) {
          myprintf(cs, "      Max throughput: %lu bytes per second\n", (unsigned long)tsi->bps);
        }
        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), 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), tsi->extra_peers_data[i].saddr);
              }
              myprintf(cs, "          %s\n", tsi->extra_peers_data[i].saddr);
            }
          }
        }
      }
    }

    csarg->counter += 1;
  }
  return false;
}

static void cancel_session(struct cli_session *cs, const char *ssid) {
  if (cs && cs->ts && ssid && *ssid) {
    const turnsession_id sid = strtoull(ssid, NULL, 10);
    send_session_cancellation_to_relay(sid);
  }
}

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(adminserver.sessions, (foreachcb_arg_type)print_session, &arg);

    myprintf(cs, "\n");

    if (!print_users) {
      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) {
      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) {
      free(arg.user_counters);
    }
    if (arg.user_names) {
      size_t i;
      for (i = 0; i < arg.users_number; ++i) {
        if (arg.user_names[i]) {
          free(arg.user_names[i]);
        }
      }
      free(arg.user_names);
    }
    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, 3, "net engine version", 0);
    cli_print_str(cs, "UDP thread per CPU core", "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);
#if defined(__linux__)
    cli_print_flag(cs, turn_params.udp_recvmmsg, "udp-recvmmsg", 0);
    cli_print_flag(cs, turn_params.udp_recvmmsg_log, "udp-recvmmsg-log", 0);
    cli_print_flag(cs, turn_params.multiplex_peer, "multiplex-peer", 0);
    cli_print_flag(cs, turn_params.udp_sendmmsg, "udp-sendmmsg (derived)", 0);
    cli_print_flag(cs, turn_params.udp_gso, "udp-gso", 0);
#endif
    cli_print_str(cs, turn_params.pidfile, "pidfile", 0);
#if defined(WINDOWS)
    // TODO: implement it!!!
#else
    cli_print_uint(cs, (unsigned long)getuid(), "process user ID", 0);
    cli_print_uint(cs, (unsigned long)getgid(), "process group ID", 0);
#endif

    {
      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);

    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);

    cli_print_flag(cs, (turn_params.enable_tlsv1 && !turn_params.no_tls), "TLSv1.0", 0);
    cli_print_flag(cs, (turn_params.enable_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_ip_range_list(cs, &turn_params.ip_whitelist, "Whitelist IP (static)", 0);
    {
      ip_range_list_t *l = get_ip_list("allowed");
      cli_print_ip_range_list(cs, l, "Whitelist IP (dynamic)", 0);
      ip_list_free(l);
    }

    cli_print_ip_range_list(cs, &turn_params.ip_blacklist, "Blacklist IP (static)", 0);
    {
      ip_range_list_t *l = get_ip_list("denied");
      cli_print_ip_range_list(cs, l, "Blacklist IP (dynamic)", 0);
      ip_list_free(l);
    }

    cli_print_flag(cs, turn_params.no_multicast_peers, "no-multicast-peers", 1);
    cli_print_flag(cs, turn_params.allow_loopback_peers, "allow-loopback-peers", 1);

    myprintf(cs, "\n");

    if (turn_params.default_users_db.persistent_users_db.userdb[0]) {
      cli_print_str(cs, userdb_type_to_string(turn_params.default_users_db.userdb_type), "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.connection_string[0]) {
      cli_print_str(cs, turn_params.redis_statsdb.connection_string, "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 {
      cli_print_flag(cs, 1, "Anonymous credentials", 0);
    }
    cli_print_flag(cs, turn_params.use_auth_secret_with_timestamp, "TURN 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, "TURN 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);

    myprintf(cs, "\n");

    cli_print_uint(cs, (unsigned long)turn_params.total_quota, "Default total-quota", 2);
    cli_print_uint(cs, (unsigned long)turn_params.user_quota, "Default user-quota", 2);
    cli_print_uint(cs, (unsigned long)get_bps_capacity(), "Total server bps-capacity", 2);
    cli_print_uint(cs, (unsigned long)get_bps_capacity_allocated(), "Allocated bps-capacity", 0);
    cli_print_uint(cs, (unsigned long)get_max_bps(), "Default max-bps", 2);

    myprintf(cs, "\n");

    cli_print_uint(cs, (unsigned long)cs->rp->options.perf_options.total_quota, "current realm total-quota", 0);
    cli_print_uint(cs, (unsigned long)cs->rp->options.perf_options.user_quota, "current realm user-quota", 0);
    cli_print_uint(cs, (unsigned long)cs->rp->options.perf_options.max_bps, "current realm 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(adminserver.verbose, &(cs->addr), "CLI session disconnected from");

    if (cs->ts) {
      telnet_free(cs->ts);
      cs->ts = NULL;
    }

    BUFFEREVENT_FREE(cs->bev);

    if (cs->fd >= 0) {
      socket_closesocket(cs->fd);
      cs->fd = -1;
    }

    free(cs);
  }
}

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);
    memcpy(buf, buf0, len);
    buf[len] = 0;

    char *cmd = buf;

    while ((cmd[0] == ' ') || (cmd[0] == '\t')) {
      ++cmd;
    }

    size_t sl = strlen(cmd);

    while (sl) {
      const 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 (!check_password_equal(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, "drain") == 0) {
        addr_debug_print(1, &(cs->addr), "Drain command received from CLI user");
        const char *str = "TURN server is draining then shutting down";
        myprintf(cs, "%s\n", str);
        close_cli_session(cs);
        enable_drain_mode();
      } 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 = true;
        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, "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, "cs ") == cmd) {
        cancel_session(cs, cmd + 3);
        type_cli_cursor(cs);
      } else if (strstr(cmd, "lr") == cmd) {
        log_reset(cs);
        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) {

      const 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(adminserver.verbose, (ioa_addr *)sa, "CLI connected to");

  struct cli_session *clisession = (struct cli_session *)calloc(1, sizeof(struct cli_session));
  if (clisession == NULL) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: failure in call to calloc \n", __FUNCTION__);
    return;
  }

  if (clisession) {
    clisession->rp = get_realm(NULL);

    set_socket_options_fd(fd, TCP_SOCKET, sa->sa_family);

    clisession->fd = fd;

    addr_cpy(&(clisession->addr), (ioa_addr *)sa);

    clisession->bev = bufferevent_socket_new(adminserver.event_base, fd, TURN_BUFFEREVENTS_OPTIONS);
    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(adminserver.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);
    }
  }
}

static void web_admin_input_handler(ioa_socket_handle s, int event_type, ioa_net_data *in_buffer, void *arg,
                                    int can_resume) {
  UNUSED_ARG(event_type);
  UNUSED_ARG(can_resume);
  UNUSED_ARG(arg);

  int to_be_closed = 0;

  const int buffer_size = (int)ioa_network_buffer_get_size(in_buffer->nbh);
  if (buffer_size >= UDP_STUN_BUFFER_SIZE) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "%s: request is too big: %d\n", __FUNCTION__, buffer_size);
    to_be_closed = 1;
  } else if (buffer_size > 0) {

    const SOCKET_TYPE st = get_ioa_socket_type(s);

    if (is_stream_socket(st)) {
      if (is_http((char *)ioa_network_buffer_data(in_buffer->nbh), buffer_size)) {
        const char *proto = "HTTP";
        ioa_network_buffer_data(in_buffer->nbh)[buffer_size] = 0;
        if (st == TLS_SOCKET) {
          proto = "HTTPS";
          set_ioa_socket_app_type(s, HTTPS_CLIENT_SOCKET);

          /* Suppress logging of the raw request when it carries credentials
           * (the logon POST body contains "pwd="), mirroring handle_https(). */
          if (strstr((char *)ioa_network_buffer_data(in_buffer->nbh), "pwd")) {
            TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s (%s %s) request (body redacted: contains credentials)\n",
                          __FUNCTION__, proto, get_ioa_socket_cipher(s), get_ioa_socket_ssl_method(s));
          } else {
            TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s (%s %s) request: %s\n", __FUNCTION__, proto,
                          get_ioa_socket_cipher(s), get_ioa_socket_ssl_method(s),
                          (char *)ioa_network_buffer_data(in_buffer->nbh));
          }

          TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s socket to be detached: %p, st=%d, sat=%d\n", __FUNCTION__, s,
                        get_ioa_socket_type(s), get_ioa_socket_app_type(s));

          ioa_socket_handle new_s = detach_ioa_socket(s);
          if (new_s) {
            TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s new detached socket: %p, st=%d, sat=%d\n", __FUNCTION__, new_s,
                          get_ioa_socket_type(new_s), get_ioa_socket_app_type(new_s));

            send_https_socket(new_s);
          }
          to_be_closed = 1;

        } else {
          set_ioa_socket_app_type(s, HTTP_CLIENT_SOCKET);
          if (adminserver.verbose) {
            TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: %s request: %s\n", __FUNCTION__, proto,
                          (char *)ioa_network_buffer_data(in_buffer->nbh));
          }
          handle_http_echo(s);
        }
      }
    }
  }

  if (to_be_closed) {
    if (adminserver.verbose) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: web-admin socket to be closed in client handler: s=%p\n", __FUNCTION__,
                    s);
    }
    set_ioa_socket_tobeclosed(s);
  }
}

static int send_socket_to_admin_server(ioa_engine_handle e, struct message_to_relay *sm) {
  // sm->relay_server is null for us.

  sm->t = RMT_SOCKET;

  if (sm->m.sm.s->defer_nbh) {
    if (!sm->m.sm.nd.nbh) {
      sm->m.sm.nd.nbh = sm->m.sm.s->defer_nbh;
      sm->m.sm.s->defer_nbh = NULL;
    } else {
      ioa_network_buffer_delete(e, sm->m.sm.s->defer_nbh);
      sm->m.sm.s->defer_nbh = NULL;
    }
  }

  ioa_socket_handle s = sm->m.sm.s;

  if (!s) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: web-admin socket EMPTY\n", __FUNCTION__);

  } else if (s->read_event || s->bev) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: web-admin socket wrongly preset: %p : %p\n", __FUNCTION__, s->read_event,
                  s->bev);

    IOA_CLOSE_SOCKET(s);
    sm->m.sm.s = NULL;
  } else {
    s->e = e;

    struct socket_message *msg = &(sm->m.sm);

    if (register_callback_on_ioa_socket(e, msg->s, IOA_EV_READ, web_admin_input_handler, NULL, 0) < 0) {

      TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: Failed to register callback on web-admin ioa socket\n", __FUNCTION__);
      IOA_CLOSE_SOCKET(s);
      sm->m.sm.s = NULL;

    } else {

      if (msg->nd.nbh) {
        web_admin_input_handler(msg->s, IOA_EV_READ, &(msg->nd), NULL, msg->can_resume);
        ioa_network_buffer_delete(e, msg->nd.nbh);
        msg->nd.nbh = NULL;
      }
    }
  }

  ioa_network_buffer_delete(e, sm->m.sm.nd.nbh);
  sm->m.sm.nd.nbh = NULL;

  return 0;
}

void setup_admin_thread(void) {
  TURN_MUTEX_INIT(&admin_logon_throttle_mutex);
  adminserver.event_base = turn_event_base_new();
  super_memory_t *sm = new_super_memory_region();
  adminserver.e = create_ioa_engine(sm, adminserver.event_base, turn_params.listener.tp, turn_params.relay_ifname,
                                    turn_params.relays_number, turn_params.relay_addrs, turn_params.default_relays,
                                    turn_params.verbose
#if !defined(TURN_NO_HIREDIS)
                                    ,
                                    &turn_params.redis_statsdb
#endif
  );

  if (use_web_admin) {
    // Support encryption on this ioa engine
    // because the web-admin needs HTTPS
    set_ssl_ctx(adminserver.e, &turn_params);
  }

  {
    struct bufferevent *pair[2];

    bufferevent_pair_new(adminserver.event_base, TURN_BUFFEREVENTS_OPTIONS, pair);

    adminserver.in_buf = pair[0];
    adminserver.out_buf = pair[1];

    bufferevent_setcb(adminserver.in_buf, admin_server_receive_message, NULL, NULL, &adminserver);
    bufferevent_enable(adminserver.in_buf, EV_READ);
  }

  {
    struct bufferevent *pair[2];

    bufferevent_pair_new(adminserver.event_base, TURN_BUFFEREVENTS_OPTIONS, pair);

    adminserver.https_in_buf = pair[0];
    adminserver.https_out_buf = pair[1];

    bufferevent_setcb(adminserver.https_in_buf, https_admin_server_receive_message, NULL, NULL, &adminserver);
    bufferevent_enable(adminserver.https_in_buf, EV_READ);
  }

  // Setup the web-admin server
  if (use_web_admin) {
    if (!web_admin_addr_set) {
      if (make_ioa_addr((const uint8_t *)WEB_ADMIN_DEFAULT_IP, 0, &web_admin_addr) < 0) {
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot set web-admin address %s\n", WEB_ADMIN_DEFAULT_IP);
        return;
      }
    }

    addr_set_port(&web_admin_addr, web_admin_port);

    char saddr[MAX_IOA_ADDR_STRING];
    addr_to_string_no_port(&web_admin_addr, saddr);

    tls_listener_relay_server_type *tls_service =
        create_tls_listener_server(turn_params.listener_ifname, saddr, web_admin_port, turn_params.verbose,
                                   adminserver.e, send_socket_to_admin_server, NULL);

    if (tls_service == NULL) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot create web-admin listener\n");
      return;
    }

    addr_debug_print(adminserver.verbose, &web_admin_addr, "web-admin listener opened on ");
  }

  if (use_cli) {
    if (!cli_addr_set) {
      if (make_ioa_addr((const uint8_t *)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);

    adminserver.listen_fd = socket(cli_addr.ss.sa_family, ADMIN_STREAM_SOCKET_TYPE, ADMIN_STREAM_SOCKET_PROTOCOL);
    if (adminserver.listen_fd < 0) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open CLI socket. Error: %s\n", strerror(errno));
      return;
    }

    if (addr_bind(adminserver.listen_fd, &cli_addr, 1, 1, TCP_SOCKET) < 0) {
      char saddr[MAX_IOA_ADDR_STRING];
      addr_to_string(&cli_addr, saddr);
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot bind CLI listener socket to addr %s. Error: %s\n", saddr,
                    strerror(errno));
      socket_closesocket(adminserver.listen_fd);
      return;
    }

    socket_tcp_set_keepalive(adminserver.listen_fd, TCP_SOCKET);

    socket_set_nonblocking(adminserver.listen_fd);

    adminserver.l = evconnlistener_new(adminserver.event_base, cliserver_input_handler, &adminserver,
                                       LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 1024, adminserver.listen_fd);

    if (!(adminserver.l)) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot create CLI listener\n");
      socket_closesocket(adminserver.listen_fd);
      return;
    }

    addr_debug_print(adminserver.verbose, &cli_addr, "CLI listener opened on ");
  }

  adminserver.sessions = ur_map_create();
}

void admin_server_receive_message(struct bufferevent *bev, void *ptr) {
  UNUSED_ARG(ptr);

  struct turn_session_info *tsi = (struct turn_session_info *)calloc(1, sizeof(struct turn_session_info));
  if (tsi == NULL) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: failure in call to calloc \n", __FUNCTION__);
    return;
  }

  int n = 0;
  struct evbuffer *input = bufferevent_get_input(bev);

  while (tsi && (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(adminserver.sessions, (ur_map_key_type)tsi->id, &t) && t) {
      struct turn_session_info *old = (struct turn_session_info *)t;
      turn_session_info_clean(old);
      free(old);
      ur_map_del(adminserver.sessions, (ur_map_key_type)tsi->id, NULL);
    }

    if (tsi->valid) {
      ur_map_put(adminserver.sessions, (ur_map_key_type)tsi->id, (ur_map_value_type)tsi);
      tsi = (struct turn_session_info *)calloc(1, sizeof(struct turn_session_info));
    } else {
      turn_session_info_clean(tsi);
    }
  }

  if (tsi) {
    turn_session_info_clean(tsi);
    free(tsi);
  }
}

int send_turn_session_info(struct turn_session_info *tsi) {
  int ret = -1;

  if (tsi) {
    struct evbuffer *output = bufferevent_get_output(adminserver.out_buf);
    if (output) {
      if (evbuffer_add(output, tsi, sizeof(struct turn_session_info)) >= 0) {
        ret = 0;
      }
    }
  }

  return ret;
}

/////////// HTTPS /////////////

enum _AS_FORM {
  AS_FORM_LOGON,
  AS_FORM_LOGOUT,
  AS_FORM_PC,
  AS_FORM_HOME,
  AS_FORM_TOGGLE,
  AS_FORM_UPDATE,
  AS_FORM_PS,
  AS_FORM_USERS,
  AS_FORM_SS,
  AS_FORM_OS,
  AS_FORM_OAUTH,
  AS_FORM_OAUTH_SHOW_KEYS,
  AS_FORM_UNKNOWN
};

typedef enum _AS_FORM AS_FORM;

#define HR_USERNAME "uname"
#define HR_PASSWORD "pwd"
#define HR_PASSWORD1 "pwd1"
#define HR_REALM "realm"
#define HR_ADD_USER "add_user"
#define HR_ADD_REALM "add_user_realm"
#define HR_ADD_SECRET "add_secret"
#define HR_ADD_ORIGIN "add_origin"
#define HR_CLIENT_PROTOCOL "cprotocol"
#define HR_USER_PATTERN "puser"
#define HR_MAX_SESSIONS "maxsess"
#define HR_CANCEL_SESSION "cs"
#define HR_DELETE_USER "du"
#define HR_DELETE_REALM "dr"
#define HR_DELETE_SECRET "ds"
#define HR_DELETE_ORIGIN "do"
#define HR_DELETE_IP "dip"
#define HR_DELETE_IP_REALM "dipr"
#define HR_DELETE_IP_KIND "dipk"
#define HR_ADD_IP "aip"
#define HR_ADD_IP_REALM "aipr"
#define HR_ADD_IP_KIND "aipk"
#define HR_UPDATE_PARAMETER "togglepar"
#define HR_ADD_OAUTH_KID "oauth_kid"
#define HR_ADD_OAUTH_REALM "oauth_realm"
#define HR_ADD_OAUTH_TS "oauth_ts"
#define HR_ADD_OAUTH_LT "oauth_lt"
#define HR_ADD_OAUTH_IKM "oauth_ikm"
#define HR_ADD_OAUTH_TEA "oauth_tea"
#define HR_DELETE_OAUTH_KID "oauth_kid_del"
#define HR_OAUTH_KID "kid"

struct form_name {
  AS_FORM form;
  const char *name;
};

static struct form_name form_names[] = {
    {AS_FORM_LOGON, "/logon"}, {AS_FORM_LOGOUT, "/logout"}, {AS_FORM_PC, "/pc"},
    {AS_FORM_HOME, "/home"},   {AS_FORM_TOGGLE, "/toggle"}, {AS_FORM_UPDATE, "/update"},
    {AS_FORM_PS, "/ps"},       {AS_FORM_USERS, "/us"},      {AS_FORM_SS, "/ss"},
    {AS_FORM_OS, "/os"},       {AS_FORM_OAUTH, "/oauth"},   {AS_FORM_OAUTH_SHOW_KEYS, "/oauth_show_keys"},
    {AS_FORM_UNKNOWN, NULL}};

#define admin_title "TURN Server (https admin connection)"
#define __bold_admin_title "<b>TURN Server</b><br><i>https admin connection</i><br>\r\n"
#define bold_admin_title get_bold_admin_title()

static ioa_socket_handle current_socket = NULL;

static char *get_bold_admin_title(void) {
  static char sbat[1025];
  strncpy(sbat, __bold_admin_title, sizeof(sbat) - 1);
  sbat[sizeof(sbat) - 1] = '\0';

  if (current_socket && current_socket->special_session) {
    struct admin_session *as = (struct admin_session *)current_socket->special_session;
    if (as && as->as_ok) {
      if (as->as_login[0]) {
        char *dst = sbat + strlen(sbat);
        snprintf(dst, ADMIN_USER_MAX_LENGTH * 2 + 2, " admin user: <b><i>%s</i></b><br>\r\n", as->as_login);
      }
      if (as->as_realm[0]) {
        char *dst = sbat + strlen(sbat);
        snprintf(dst, STUN_MAX_REALM_SIZE * 2, " admin session realm: <b><i>%s</i></b><br>\r\n", as->as_realm);
      } else if (as->as_eff_realm[0]) {
        char *dst = sbat + strlen(sbat);
        snprintf(dst, STUN_MAX_REALM_SIZE * 2, " admin session realm: <b><i>%s</i></b><br>\r\n", as->as_eff_realm);
      }
    }
  }
  return sbat;
}

static int wrong_html_name(const char *s) {
  int ret = 0;
  if (s) {
    char *v = evhttp_encode_uri(s);
    ret = strcmp(v, s);
    free(v);
  }
  return ret;
}

static int is_as_ok(ioa_socket_handle s) {
  return (s && s->special_session && ((struct admin_session *)s->special_session)->as_ok);
}

static int is_superuser(void) {
  return (is_as_ok(current_socket) && (!((struct admin_session *)current_socket->special_session)->as_realm[0]));
}

static char *current_realm(void) {
  if (current_socket && current_socket->special_session &&
      ((struct admin_session *)current_socket->special_session)->as_ok) {
    return ((struct admin_session *)current_socket->special_session)->as_realm;
  } else {
    static char bad_realm[1025] = "_ERROR:UNKNOWN_REALM__";
    return bad_realm;
  }
}

static char *current_eff_realm(void) {
  char *r = current_realm();
  if (r && r[0]) {
    return r;
  } else if (current_socket && current_socket->special_session &&
             ((struct admin_session *)current_socket->special_session)->as_ok) {
    return ((struct admin_session *)current_socket->special_session)->as_eff_realm;
  } else {
    static char bad_eff_realm[1025] = "_ERROR:UNKNOWN_REALM__";
    return bad_eff_realm;
  }
}

static size_t current_max_output_sessions(void) {
  if (current_socket && current_socket->special_session &&
      ((struct admin_session *)current_socket->special_session)->as_ok) {
    return ((struct admin_session *)current_socket->special_session)->number_of_user_sessions;
  }
  return DEFAULT_CLI_MAX_OUTPUT_SESSIONS;
}

static void set_current_max_output_sessions(size_t value) {
  if (current_socket && current_socket->special_session &&
      ((struct admin_session *)current_socket->special_session)->as_ok) {
    ((struct admin_session *)current_socket->special_session)->number_of_user_sessions = value;
  }
}

static void https_cancel_session(const char *ssid) {
  if (ssid && *ssid) {
    const turnsession_id sid = (turnsession_id)strtoull(ssid, NULL, 10);
    send_session_cancellation_to_relay(sid);
  }
}

static void https_print_top_page_header(struct str_buffer *sb) {
  str_buffer_append(sb, "<!DOCTYPE html>\r\n<html>\r\n  <head>\r\n    <title>");
  str_buffer_append(sb, admin_title);
  str_buffer_append(
      sb, "</title>\r\n <style> table, th, td { border: 1px solid black; border-collapse: collapse; text-align: left; "
          "padding: 5px;} table#msg th { color: red; background-color: white; } </style> </head>\r\n  <body>\r\n    ");
  str_buffer_append(sb, bold_admin_title);
}

static void https_print_page_header(struct str_buffer *sb) {
  https_print_top_page_header(sb);
  str_buffer_append(sb, "<br><a href=\"/home?");
  str_buffer_append(sb, HR_REALM);
  str_buffer_append(sb, "=");
  str_buffer_append_uri_escaped(sb, current_eff_realm());
  str_buffer_append(sb, "\">home page</a><br>\r\n<br><a href=\"/logout\">logout</a><br>\r\n");
  str_buffer_append(sb, "<br>\r\n");
}

static void https_finish_page(struct str_buffer *sb, ioa_socket_handle s, int cclose) {
  str_buffer_append(sb, "</body>\r\n</html>\r\n");

  send_str_from_ioa_socket_tcp(s, "HTTP/1.1 200 OK\r\nServer: ");
  if (turn_params.software_attribute) {
    send_str_from_ioa_socket_tcp(s, TURN_SOFTWARE);
  }
  send_str_from_ioa_socket_tcp(s, "\r\n");
  send_str_from_ioa_socket_tcp(s, get_http_date_header());
  if (cclose) {
    send_str_from_ioa_socket_tcp(s, "Connection: close\r\n");
  }
  send_str_from_ioa_socket_tcp(s, "Content-Type: text/html; charset=UTF-8\r\nContent-Length: ");

  send_ulong_from_ioa_socket_tcp(s, str_buffer_get_str_len(sb));

  send_str_from_ioa_socket_tcp(s, "\r\n\r\n");
  send_str_from_ioa_socket_tcp(s, str_buffer_get_str(sb));

  str_buffer_free(sb);
}

static AS_FORM get_form(const char *path) {
  if (path) {
    size_t i = 0;
    while (form_names[i].name) {
      if (!strcmp(form_names[i].name, path)) {
        return form_names[i].form;
      }
      ++i;
    }
  }
  return AS_FORM_UNKNOWN;
}

static void write_https_logon_page(ioa_socket_handle s) {
  if (s && !ioa_socket_tobeclosed(s)) {

    struct str_buffer *sb = str_buffer_new();

    https_print_top_page_header(sb);

    int we_have_admin_users = 0;
    const turn_dbdriver_t *dbd = get_dbdriver();
    if (dbd && dbd->list_admin_users) {
      const int ausers = dbd->list_admin_users(1);
      if (ausers > 0) {
        we_have_admin_users = 1;
      }
    }

    if (!we_have_admin_users) {
      str_buffer_append(sb, "<br>To use the HTTPS admin connection, you have to set the database table "
                            "<b><i>admin_user</i></b> with the admin user accounts.<br>\r\n");
    } else {
      str_buffer_append(sb, "<br><br>\r\n");
      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_LOGON].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(
          sb,
          "  <fieldset><legend>Admin user information:</legend>  user name:<br><input required type=\"text\" name=\"");
      str_buffer_append(sb, HR_USERNAME);
      str_buffer_append(sb, "\" value=\"\"><br>password:<br><input required type=\"password\" name=\"");
      str_buffer_append(sb, HR_PASSWORD);
      str_buffer_append(sb, "\" value=\"\"><br><br><input type=\"submit\" value=\"Login\"></fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");
    }

    https_finish_page(sb, s, !we_have_admin_users);
  }
}

static void write_https_home_page(ioa_socket_handle s) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_HOME].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(sb, "  <fieldset><legend>Actions:</legend>\r\n");

      str_buffer_append(sb, "  Realm name: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\"");
      if (!is_superuser()) {
        str_buffer_append(sb, " disabled >");
      } else {
        str_buffer_append(sb, "> <input type=\"submit\" value=\"Set Admin Session Realm\" >");
      }

      str_buffer_append(sb, "<br>");

      str_buffer_append(sb, "<br><a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_PC].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\">Configuration Parameters</a>");

      str_buffer_append(sb, "<br><a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_PS].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "&");
      str_buffer_append(sb, HR_MAX_SESSIONS);
      str_buffer_append(sb, "=");
      str_buffer_append_sz(sb, current_max_output_sessions());
      str_buffer_append(sb, "\">TURN Sessions</a>");

      str_buffer_append(sb, "<br><a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_USERS].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\">Users</a>");

      str_buffer_append(sb, "<br><a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_SS].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\">Shared Secrets (for TURN REST API)</a>");

      str_buffer_append(sb, "<br><a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_OS].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\">Origins</a>");

      if (is_superuser()) {
        if (ENC_ALG_NUM > 0) {
          str_buffer_append(sb, "<br><a href=\"");
          str_buffer_append(sb, form_names[AS_FORM_OAUTH].name);
          str_buffer_append(sb, "?");
          str_buffer_append(sb, HR_REALM);
          str_buffer_append(sb, "=");
          str_buffer_append_uri_escaped(sb, current_eff_realm());
          str_buffer_append(sb, "\">oAuth keys</a>");
        }
      }

      str_buffer_append(sb, "</fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");

      https_finish_page(sb, s, 0);
    }
  }
}

static void sbprintf(struct str_buffer *sb, const char *format, ...) {
  if (sb && format) {
    va_list args;
    va_start(args, format);
    char s[1025] = "\0";
    vsnprintf(s, sizeof(s) - 1, format, args);
    str_buffer_append(sb, s);
    va_end(args);
  }
}

static void https_print_flag(struct str_buffer *sb, int flag, const char *name, const char *param_name) {
  if (sb && name) {
    if (!is_superuser()) {
      param_name = 0;
    }
    if (!param_name) {
      sbprintf(sb, "<tr><td>%s</td><td>%s</td></tr>\r\n", name, get_flag(flag));
    } else {
      sbprintf(sb, "<tr><td>%s</td><td><a href=\"/toggle?%s=%s\">%s</a></td></tr>\r\n", name, HR_UPDATE_PARAMETER,
               param_name, get_flag(flag));
    }
  }
}

static void https_print_uint(struct str_buffer *sb, unsigned long value, const char *name, const char *param_name) {
  if (sb && name) {
    if (!is_superuser()) {
      param_name = 0;
    }
    if (!param_name) {
      if (value) {
        sbprintf(sb, "<tr><td>%s</td><td>%lu</td></tr>\r\n", name, value);
      } else {
        sbprintf(sb, "<tr><td>%s</td><td> </td></tr>\r\n", name);
      }
    } else {
      if (value) {
        sbprintf(sb,
                 "<tr><td>%s</td><td> <form action=\"%s?%s=%s\" method=\"POST\"><input type=\"text\" name=\"%s\" "
                 "value=\"%lu\"><input type=\"submit\" value=\"Update\"></form> </td></tr>\r\n",
                 name, form_names[AS_FORM_UPDATE].name, HR_UPDATE_PARAMETER, param_name, param_name, value);
      } else {
        sbprintf(sb,
                 "<tr><td>%s</td><td> <form action=\"%s?%s=%s\" method=\"POST\"><input type=\"text\" name=\"%s\" "
                 "value=\"\"><input type=\"submit\" value=\"Update\"></form> </td></tr>\r\n",
                 name, form_names[AS_FORM_UPDATE].name, HR_UPDATE_PARAMETER, param_name, param_name);
      }
    }
  }
}

static void https_print_str(struct str_buffer *sb, const char *value, const char *name, const char *param_name) {
  if (sb && name && value) {
    if (!is_superuser()) {
      param_name = 0;
    }
    if (!param_name) {
      sbprintf(sb, "<tr><td>%s</td><td>%s</td></tr>\r\n", name, value);
    } else {
      sbprintf(sb,
               "<tr><td>%s</td><td> <form action=\"%s?%s=%s\" method=\"POST\"><input type=\"text\" name=\"%s\" "
               "value=\"%s\"><input type=\"submit\" value=\"Update\"></form> </td></tr>\r\n",
               name, form_names[AS_FORM_UPDATE].name, HR_UPDATE_PARAMETER, param_name, param_name, value);
    }
  }
}

static void https_print_str_array(struct str_buffer *sb, char **value, size_t sz, const char *name) {
  if (sb && name && value && sz) {
    size_t i;
    for (i = 0; i < sz; i++) {
      if (value[i]) {
        sbprintf(sb, "<tr><td>  %s</td><td> %s</td></tr>\r\n", name, value[i]);
      }
    }
  }
}

static void https_print_addr(struct str_buffer *sb, ioa_addr *value, int use_port, const char *name) {
  if (sb && name && value) {
    char s[MAX_IOA_ADDR_STRING];
    if (!use_port) {
      addr_to_string_no_port(value, s);
    } else {
      addr_to_string(value, s);
    }
    sbprintf(sb, "<tr><td>  %s</td><td> %s</td></tr>\r\n", name, s);
  }
}

static size_t https_print_addr_list(struct str_buffer *sb, turn_server_addrs_list_t *value, int use_port,
                                    const char *name) {
  if (sb && name && value && value->size && value->addrs) {
    char s[MAX_IOA_ADDR_STRING];
    size_t i;
    for (i = 0; i < value->size; i++) {
      if (!use_port) {
        addr_to_string_no_port(&(value->addrs[i]), s);
      } else {
        addr_to_string(&(value->addrs[i]), s);
      }
      sbprintf(sb, "</tr><td>  %s</td><td> %s</td></tr>\r\n", name, s);
    }
    return i;
  }
  return 0;
}

static const char *change_ip_addr_html(int dynamic, const char *kind, const char *ip, const char *realm, char *buffer,
                                       size_t sz) {
  if (!buffer || !sz) {
    return "";
  } else {
    buffer[0] = 0;
    if (dynamic && kind && ip) {

      if (!realm) {
        realm = "";
      }

      if (current_realm()[0] && strcmp(current_realm(), realm) != 0) {
        // delete forbidden
      } else {
        char *eip = evhttp_encode_uri(ip);
        snprintf(buffer, sz - 1, "<a href=\"%s?%s=%s&%s=%s&%s=%s\">delete</a>", form_names[AS_FORM_UPDATE].name,
                 HR_DELETE_IP_KIND, kind, HR_DELETE_IP_REALM, realm, HR_DELETE_IP, eip);
        free(eip);
      }
    }
    return buffer;
  }
}

static void https_print_ip_range_list(struct str_buffer *sb, ip_range_list_t *value, const char *name, const char *kind,
                                      int dynamic) {
  if (sb && name) {
    if (value && value->rs) {
      size_t i;
      char buffer[1025];
      for (i = 0; i < value->ranges_number; ++i) {
        if (value->rs[i].realm[0]) {
          if (current_eff_realm()[0] && strcmp(current_eff_realm(), value->rs[i].realm) != 0) {
            continue;
          } else {
            sbprintf(sb, "<tr><td>  %s</td><td> %s [%s] %s</td></tr>\r\n", name, value->rs[i].str, value->rs[i].realm,
                     change_ip_addr_html(dynamic, kind, value->rs[i].str, value->rs[i].realm, buffer, sizeof(buffer)));
          }
        } else {
          sbprintf(sb, "<tr><td>  %s</td><td> %s %s</td></tr>\r\n", name, value->rs[i].str,
                   change_ip_addr_html(dynamic, kind, value->rs[i].str, value->rs[i].realm, buffer, sizeof(buffer)));
        }
      }
    }

    if (dynamic) {
      sbprintf(sb, "<tr><td> Add %s</td><td>", name);
      sbprintf(
          sb,
          "<form action=\"%s?%s=%s\" method=\"POST\">IP range:<input required type=\"text\" name=\"%s\" value=\"\" >",
          form_names[AS_FORM_UPDATE].name, HR_ADD_IP_KIND, kind, HR_ADD_IP);
      sbprintf(sb, "Realm: <input type=\"text\" name=\"%s\" value=\"%s\" ", HR_ADD_IP_REALM, current_eff_realm());
      if (!is_superuser()) {
        sbprintf(sb, " disabled ");
      }
      sbprintf(sb, ">");
      sbprintf(sb, "<input type=\"submit\" value=\"Add IP\"></form> </td></tr>\r\n");
    }
  }
}

static void toggle_param(const char *pn) {
  if (is_superuser()) {
    if (pn) {
      int i = 0;
      while (tcmds[i].cmd && tcmds[i].data) {
        if (strcmp(tcmds[i].cmd, pn) == 0) {
          *(tcmds[i].data) = !(*(tcmds[i].data));
          return;
        }
        ++i;
      }
    }
  }
}

static void update_param(const char *pn, const char *value) {
  if (pn) {
    if (!value) {
      value = "0";
    }
    if (is_superuser()) {
      if (strstr(pn, "total-quota") == pn) {
        turn_params.total_quota = atoi(value);
      } else if (strstr(pn, "user-quota") == pn) {
        turn_params.user_quota = atoi(value);
      } else if (strstr(pn, "max-bps") == pn) {
        set_max_bps((band_limit_t)strtoul(value, NULL, 10));
      } else if (strstr(pn, "bps-capacity") == pn) {
        set_bps_capacity((band_limit_t)strtoul(value, NULL, 10));
      }
    }
    {
      realm_params_t *rp = get_realm(current_eff_realm());
      if (!rp) {
        rp = get_realm(NULL);
      }

      const turn_dbdriver_t *dbd = get_dbdriver();
      if (dbd && dbd->set_realm_option_one) {
        if (strstr(pn, "cr-total-quota") == pn) {
          rp->options.perf_options.total_quota = atoi(value);
          dbd->set_realm_option_one((uint8_t *)rp->options.name, rp->options.perf_options.total_quota, "total-quota");
        } else if (strstr(pn, "cr-user-quota") == pn) {
          rp->options.perf_options.user_quota = atoi(value);
          dbd->set_realm_option_one((uint8_t *)rp->options.name, rp->options.perf_options.user_quota, "user-quota");
        } else if (strstr(pn, "cr-max-bps") == pn) {
          rp->options.perf_options.max_bps = (band_limit_t)strtoul(value, NULL, 10);
          dbd->set_realm_option_one((uint8_t *)rp->options.name, rp->options.perf_options.max_bps, "max-bps");
        }
      }
    }
  }
}

static void https_print_empty_row(struct str_buffer *sb, size_t span) {
  str_buffer_append(sb, "<tr><td colspan=");
  str_buffer_append_sz(sb, span);
  str_buffer_append(sb, "><br></td></tr>");
}

static void write_pc_page(ioa_socket_handle s) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      str_buffer_append(sb, "<br>\r\n");
      str_buffer_append(sb, "<b>Configuration Parameters:</b><br><br><table  style=\"width:100%\">\r\n");
      str_buffer_append(sb, "<tr><th>Parameter</th><th>Value</th></tr>\r\n");

      {
        https_print_flag(sb, turn_params.verbose, "verbose", 0);
        https_print_flag(sb, turn_params.turn_daemon, "daemon process", 0);
        https_print_flag(sb, turn_params.stale_nonce, "stale-nonce", "stale-nonce");
        https_print_flag(sb, turn_params.stun_only, "stun-only", "stun-only");
        https_print_flag(sb, turn_params.no_stun, "no-stun", "no-stun");
        https_print_flag(sb, turn_params.secure_stun, "secure-stun", "secure-stun");
        https_print_flag(sb, turn_params.do_not_use_config_file, "do-not-use-config-file", 0);
        https_print_flag(sb, turn_params.rfc5780, "RFC5780 support", 0);
        https_print_uint(sb, 3, "net engine version", 0);
        https_print_str(sb, "UDP thread per CPU core", "net engine", 0);
        https_print_flag(sb, turn_params.fingerprint, "enforce fingerprints", 0);
        https_print_flag(sb, turn_params.mobility, "mobility", "mobility");
        https_print_flag(sb, turn_params.udp_self_balance, "udp-self-balance", 0);
        https_print_str(sb, turn_params.pidfile, "pidfile", 0);
#if defined(WINDOWS)
        // TODO: implement it!!!
#else
        https_print_uint(sb, (unsigned long)getuid(), "process user ID", 0);
        https_print_uint(sb, (unsigned long)getgid(), "process group ID", 0);
#endif
        {
          char wd[1025];
          if (getcwd(wd, sizeof(wd) - 1)) {
            https_print_str(sb, wd, "process dir", 0);
          }
        }

        https_print_empty_row(sb, 2);

        if (turn_params.cipher_list[0]) {
          https_print_str(sb, turn_params.cipher_list, "cipher-list", 0);
        } else {
          https_print_str(sb, DEFAULT_CIPHER_LIST, "cipher-list", 0);
        }

        https_print_str(sb, turn_params.ec_curve_name, "ec-curve-name", 0);
        {
          if (turn_params.dh_key_size == DH_CUSTOM) {
            https_print_str(sb, 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;
            }
            https_print_uint(sb, (unsigned long)dh_key_length, "DH-key-length", 0);
          }
        }

        https_print_str(sb, turn_params.ca_cert_file, "Certificate Authority file", 0);
        https_print_str(sb, turn_params.cert_file, "Certificate file", 0);
        https_print_str(sb, turn_params.pkey_file, "Private Key file", 0);

        https_print_empty_row(sb, 2);

        https_print_str_array(sb, turn_params.listener.addrs, turn_params.listener.addrs_number, "Listener addr");

        if (turn_params.listener_ifname[0]) {
          https_print_str(sb, turn_params.listener_ifname, "listener-ifname", 0);
        }

        https_print_flag(sb, turn_params.no_udp, "no-udp", 0);
        https_print_flag(sb, turn_params.no_tcp, "no-tcp", 0);
        https_print_flag(sb, turn_params.no_dtls, "no-dtls", 0);
        https_print_flag(sb, turn_params.no_tls, "no-tls", 0);

        https_print_flag(sb, (!turn_params.no_tlsv1_2 && !turn_params.no_tls), "TLSv1.2", 0);

        https_print_uint(sb, (unsigned long)turn_params.listener_port, "listener-port", 0);
        https_print_uint(sb, (unsigned long)turn_params.tls_listener_port, "tls-listener-port", 0);
        https_print_uint(sb, (unsigned long)turn_params.alt_listener_port, "alt-listener-port", 0);
        https_print_uint(sb, (unsigned long)turn_params.alt_tls_listener_port, "alt-tls-listener-port", 0);

        https_print_addr(sb, turn_params.external_ip, 0, "External public IP");

        https_print_empty_row(sb, 2);

        {
          size_t an = https_print_addr_list(sb, &turn_params.aux_servers_list, 1, "Aux server");
          an += https_print_addr_list(sb, &turn_params.alternate_servers_list, 1, "Alternate server");
          an += https_print_addr_list(sb, &turn_params.tls_alternate_servers_list, 1, "TLS alternate server");

          if (an) {
            https_print_empty_row(sb, 2);
          }
        }

        https_print_str_array(sb, turn_params.relay_addrs, turn_params.relays_number, "Relay addr");

        if (turn_params.relay_ifname[0]) {
          https_print_str(sb, turn_params.relay_ifname, "relay-ifname", 0);
        }

        https_print_flag(sb, turn_params.server_relay, "server-relay", 0);

        https_print_flag(sb, turn_params.no_udp_relay, "no-udp-relay", "no-udp-relay");
        https_print_flag(sb, turn_params.no_tcp_relay, "no-tcp-relay", "no-tcp-relay");

        https_print_uint(sb, (unsigned long)turn_params.min_port, "min-port", 0);
        https_print_uint(sb, (unsigned long)turn_params.max_port, "max-port", 0);

        https_print_flag(sb, turn_params.no_multicast_peers, "no-multicast-peers", "no-multicast-peers");
        https_print_flag(sb, turn_params.allow_loopback_peers, "allow-loopback-peers", "allow-loopback-peers");

        https_print_empty_row(sb, 2);

        if (turn_params.default_users_db.persistent_users_db.userdb[0]) {
          https_print_str(sb, userdb_type_to_string(turn_params.default_users_db.userdb_type), "DB type", 0);
          if (is_superuser()) {
            https_print_str(sb, turn_params.default_users_db.persistent_users_db.userdb, "DB", 0);
          }
        } else {
          https_print_str(sb, "none", "DB type", 0);
          https_print_str(sb, "none", "DB", 0);
        }

#if !defined(TURN_NO_HIREDIS)
        if (is_superuser()) {
          if (turn_params.use_redis_statsdb && turn_params.redis_statsdb.connection_string_sanitized[0]) {
            https_print_str(sb, turn_params.redis_statsdb.connection_string_sanitized, "Redis Statistics DB", 0);
          }
        }
#endif

        https_print_empty_row(sb, 2);

        if (turn_params.ct == TURN_CREDENTIALS_LONG_TERM) {
          https_print_flag(sb, 1, "Long-term authorization mechanism", 0);
        } else {
          https_print_flag(sb, 1, "Anonymous credentials", 0);
        }
        https_print_flag(sb, turn_params.use_auth_secret_with_timestamp, "TURN REST API support", 0);
        if (turn_params.use_auth_secret_with_timestamp) {

          if (!turn_params.rest_api_separator || ((unsigned int)turn_params.rest_api_separator == (unsigned int)':')) {
            https_print_str(sb, ":", "TURN REST API separator", 0);
          } else {
            https_print_uint(sb, turn_params.rest_api_separator, "TURN REST API separator ASCII number", 0);
          }
        }

        https_print_empty_row(sb, 2);

        if (is_superuser()) {
          char *rn = get_realm(NULL)->options.name;
          if (rn[0]) {
            https_print_str(sb, rn, "Default realm", 0);
          }
        }

        realm_params_t *rp = get_realm(current_eff_realm());
        if (!rp) {
          rp = get_realm(NULL);
        }

        https_print_str(sb, rp->options.name, "Admin session (current) realm", 0);

        https_print_uint(sb, (unsigned long)rp->options.perf_options.total_quota,
                         "current realm max number of sessions (total-quota)", "cr-total-quota");
        https_print_uint(sb, (unsigned long)rp->options.perf_options.user_quota,
                         "current realm max sessions per user (user-quota)", "cr-user-quota");
        https_print_uint(sb, (unsigned long)rp->options.perf_options.max_bps, "current realm max-bps (per session)",
                         "cr-max-bps");

        https_print_empty_row(sb, 2);

        https_print_uint(sb, (unsigned long)rp->status.total_current_allocs, "total-current-allocs", 0);

        https_print_empty_row(sb, 2);

        https_print_uint(sb, (unsigned long)turn_params.total_quota, "Default total-quota (per realm)", "total-quota");
        https_print_uint(sb, (unsigned long)turn_params.user_quota, "Default user-quota (per realm)", "user-quota");
        https_print_uint(sb, (unsigned long)get_bps_capacity(), "Total bps-capacity (per server)", "bps-capacity");
        https_print_uint(sb, (unsigned long)get_bps_capacity_allocated(), "Allocated bps-capacity (per server)", 0);
        https_print_uint(sb, (unsigned long)get_max_bps(), "Default max-bps (per session)", "max-bps");

        https_print_empty_row(sb, 2);

        https_print_ip_range_list(sb, &turn_params.ip_whitelist, "Whitelist IP (static)", NULL, 0);
        {
          ip_range_list_t *l = get_ip_list("allowed");
          https_print_ip_range_list(sb, l, "Whitelist IP (dynamic)", "allowed", 1);
          ip_list_free(l);
        }

        https_print_empty_row(sb, 2);

        https_print_ip_range_list(sb, &turn_params.ip_blacklist, "Blacklist IP (static)", NULL, 0);
        {
          ip_range_list_t *l = get_ip_list("denied");
          https_print_ip_range_list(sb, l, "Blacklist IP (dynamic)", "denied", 1);
          ip_list_free(l);
        }
      }

      str_buffer_append(sb, "\r\n</table>\r\n");

      https_finish_page(sb, s, 0);
    }
  }
}

struct https_ps_arg {
  struct str_buffer *sb;
  size_t counter;
  turn_time_t ct;
  const char *client_protocol;
  const char *user_pattern;
  size_t max_sessions;
  turnsession_id cs;
};

static bool https_print_session(ur_map_key_type key, ur_map_value_type value, void *arg) {
  if (key && value && arg) {
    struct https_ps_arg *csarg = (struct https_ps_arg *)arg;
    struct str_buffer *sb = csarg->sb;
    struct turn_session_info *tsi = (struct turn_session_info *)value;

    if (current_eff_realm()[0] && strcmp(current_eff_realm(), tsi->realm)) {
      return false;
    }

    if (csarg->user_pattern[0]) {
      if (!strstr((char *)tsi->username, csarg->user_pattern)) {
        return false;
      }
    }

    if (csarg->cs == tsi->id) {
      return false;
    }

    {
      const char *pn = csarg->client_protocol;
      if (pn[0]) {
        if (!strcmp(pn, "TLS") || !strcmp(pn, "tls") || !strcmp(pn, "Tls")) {
          if ((tsi->client_protocol != TLS_SOCKET) && (tsi->client_protocol != TLS_SCTP_SOCKET)) {
            return false;
          }
        } else if (!strcmp(pn, "DTLS") || !strcmp(pn, "dtls") || !strcmp(pn, "Dtls")) {
          if (tsi->client_protocol != DTLS_SOCKET) {
            return false;
          }
        } else if (!strcmp(pn, "TCP") || !strcmp(pn, "tcp") || !strcmp(pn, "Tcp")) {
          if ((tsi->client_protocol != TCP_SOCKET) && (tsi->client_protocol != SCTP_SOCKET)) {
            return false;
          }
        } else if (!strcmp(pn, "UDP") || !strcmp(pn, "udp") || !strcmp(pn, "Udp")) {
          if (tsi->client_protocol != UDP_SOCKET) {
            return false;
          }
        } else {
          return false;
        }
      }
    }

    if ((unsigned long)csarg->counter < (unsigned long)csarg->max_sessions) {
      str_buffer_append(sb, "<tr><td>");
      str_buffer_append_sz(sb, (size_t)(csarg->counter + 1));
      str_buffer_append(sb, "</td><td>");
      str_buffer_append_sid(sb, tsi->id);
      str_buffer_append(sb, "<br><a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_PS].name);
      str_buffer_append(sb, "?cs=");
      str_buffer_append_sid(sb, tsi->id);
      str_buffer_append(sb, "\">cancel</a>");
      str_buffer_append(sb, "</td><td>");
      str_buffer_append_html_escaped(sb, (char *)tsi->username);
      str_buffer_append(sb, "</td><td>");
      str_buffer_append_html_escaped(sb, tsi->realm);
      str_buffer_append(sb, "</td><td>");
      str_buffer_append_html_escaped(sb, tsi->origin);
      str_buffer_append(sb, "</td><td>");
      if (turn_time_before(csarg->ct, tsi->start_time)) {
        str_buffer_append(sb, "undefined time\n");
      } else {
        str_buffer_append_sz(sb, (size_t)(csarg->ct - tsi->start_time));
      }
      str_buffer_append(sb, "</td><td>");
      if (turn_time_before(tsi->expiration_time, csarg->ct)) {
        str_buffer_append(sb, "expired");
      } else {
        str_buffer_append_sz(sb, (size_t)(tsi->expiration_time - csarg->ct));
      }
      str_buffer_append(sb, "</td><td>");
      str_buffer_append(sb, socket_type_name(tsi->client_protocol));
      str_buffer_append(sb, "</td><td>");
      str_buffer_append(sb, socket_type_name(tsi->peer_protocol));
      str_buffer_append(sb, "</td><td>");
      {
        if (!tsi->local_addr_data.saddr[0]) {
          addr_to_string(&(tsi->local_addr_data.addr), tsi->local_addr_data.saddr);
        }
        if (!tsi->remote_addr_data.saddr[0]) {
          addr_to_string(&(tsi->remote_addr_data.addr), tsi->remote_addr_data.saddr);
        }
        if (!tsi->relay_addr_data_ipv4.saddr[0]) {
          addr_to_string(&(tsi->relay_addr_data_ipv4.addr), tsi->relay_addr_data_ipv4.saddr);
        }
        if (!tsi->relay_addr_data_ipv6.saddr[0]) {
          addr_to_string(&(tsi->relay_addr_data_ipv6.addr), tsi->relay_addr_data_ipv6.saddr);
        }
        str_buffer_append_html_escaped(sb, tsi->remote_addr_data.saddr);
        str_buffer_append(sb, "</td><td>");
        str_buffer_append_html_escaped(sb, tsi->local_addr_data.saddr);
        str_buffer_append(sb, "</td><td>");
        str_buffer_append_html_escaped(sb, tsi->relay_addr_data_ipv4.saddr);
        str_buffer_append(sb, "</td><td>");
        str_buffer_append_html_escaped(sb, tsi->relay_addr_data_ipv6.saddr);
        str_buffer_append(sb, "</td><td>");
        str_buffer_append(sb, get_flag(tsi->enforce_fingerprints));
        str_buffer_append(sb, "</td><td>");
        str_buffer_append(sb, get_flag(tsi->is_mobile));
        str_buffer_append(sb, "</td><td>");
        str_buffer_append_html_escaped(sb, tsi->tls_method);
        str_buffer_append(sb, "</td><td>");
        str_buffer_append_html_escaped(sb, tsi->tls_cipher);
        str_buffer_append(sb, "</td><td>");
        str_buffer_append_sz(sb, (size_t)tsi->bps);
        str_buffer_append(sb, "</td><td>");
        {
          char str[1025];
          snprintf(str, sizeof(str) - 1, "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));
          str_buffer_append(sb, str);
          str_buffer_append(sb, "</td><td>");
        }
        {
          char str[1025];
          snprintf(str, sizeof(str) - 1, "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));
          str_buffer_append(sb, str);
          str_buffer_append(sb, "</td><td>");
        }

        if (tsi->main_peers_size) {
          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), tsi->main_peers_data[i].saddr);
            }
            str_buffer_append(sb, " ");
            str_buffer_append_html_escaped(sb, tsi->main_peers_data[i].saddr);
            str_buffer_append(sb, " ");
          }
          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), tsi->extra_peers_data[i].saddr);
              }
              str_buffer_append(sb, " ");
              str_buffer_append_html_escaped(sb, tsi->extra_peers_data[i].saddr);
              str_buffer_append(sb, " ");
            }
          }
        }
        str_buffer_append(sb, "</td>");
      }
    }

    csarg->counter += 1;
  }
  return false;
}

static size_t https_print_sessions(struct str_buffer *sb, const char *client_protocol, const char *user_pattern,
                                   size_t max_sessions, turnsession_id cs) {
  struct https_ps_arg arg = {sb, 0, 0, client_protocol, user_pattern, max_sessions, cs};

  arg.ct = turn_time();

  ur_map_foreach_arg(adminserver.sessions, (foreachcb_arg_type)https_print_session, &arg);

  return arg.counter;
}

static void write_ps_page(ioa_socket_handle s, const char *client_protocol, const char *user_pattern,
                          size_t max_sessions, turnsession_id cs) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_PS].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(sb, "  <fieldset><legend>Filter:</legend>\r\n");

      str_buffer_append(sb, "  <br>Realm name: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\"");
      if (!is_superuser()) {
        str_buffer_append(sb, " disabled ");
      }
      str_buffer_append(sb, ">");

      str_buffer_append(sb, "  Client protocol: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_CLIENT_PROTOCOL);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, client_protocol);
      str_buffer_append(sb, "\"");
      str_buffer_append(sb, ">");

      str_buffer_append(sb, "  User name contains: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_USER_PATTERN);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, user_pattern);
      str_buffer_append(sb, "\"");
      str_buffer_append(sb, "><br><br>");

      str_buffer_append(sb, "  Max number of output sessions in the page: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_MAX_SESSIONS);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_sz(sb, max_sessions);
      str_buffer_append(sb, "\"");
      str_buffer_append(sb, "><br>");

      str_buffer_append(sb, "<br><input type=\"submit\" value=\"Filter\">");

      str_buffer_append(sb, "</fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");

      str_buffer_append(sb, "<br><b>TURN Sessions:</b><br><br><table>\r\n");
      str_buffer_append(
          sb,
          "<tr><th>N</th><th>Session ID</th><th>User</th><th>Realm</th><th>Origin</th><th>Age, secs</th><th>Expires, "
          "secs</th><th>Client protocol</th><th>Relay protocol</th><th>Client addr</th><th>Server addr</th><th>Relay "
          "addr (IPv4)</th><th>Relay addr (IPv6)</th><th>Fingerprints</th><th>Mobile</th><th>TLS method</th><th>TLS "
          "cipher</th><th>BPS (allocated)</th><th>Packets</th><th>Rate</th><th>Peers</th></tr>\r\n");

      const size_t total_sz = https_print_sessions(sb, client_protocol, user_pattern, max_sessions, cs);

      str_buffer_append(sb, "\r\n</table>\r\n");

      str_buffer_append(sb, "<br>Total sessions = ");
      str_buffer_append_sz(sb, total_sz);
      str_buffer_append(sb, "<br>\r\n");

      https_finish_page(sb, s, 0);
    }
  }
}

static size_t https_print_users(struct str_buffer *sb) {
  size_t ret = 0;
  const turn_dbdriver_t *dbd = get_dbdriver();
  if (dbd && dbd->list_users) {
    secrets_list_t users, realms;
    init_secrets_list(&users);
    init_secrets_list(&realms);
    dbd->list_users((uint8_t *)current_eff_realm(), &users, &realms);

    const size_t sz = get_secrets_list_size(&users);
    size_t i;
    for (i = 0; i < sz; ++i) {
      str_buffer_append(sb, "<tr><td>");
      str_buffer_append_sz(sb, i + 1);
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&users, i));
      str_buffer_append(sb, "</td>");
      if (!current_eff_realm()[0]) {
        str_buffer_append(sb, "<td>");
        str_buffer_append_html_escaped(sb, get_secrets_list_elem(&realms, i));
        str_buffer_append(sb, "</td>");
      }
      str_buffer_append(sb, "<td> <a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_USERS].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_DELETE_USER);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, get_secrets_list_elem(&users, i));
      str_buffer_append(sb, "&");
      str_buffer_append(sb, HR_DELETE_REALM);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, get_secrets_list_elem(&realms, i));
      str_buffer_append(sb, "\">delete</a>");
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "</tr>");
      ++ret;
    }

    clean_secrets_list(&users);
    clean_secrets_list(&realms);
  }

  return ret;
}

static void write_users_page(ioa_socket_handle s, const uint8_t *add_user, const uint8_t *add_realm, const char *msg) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_USERS].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(sb, "  <fieldset><legend>Filter:</legend>\r\n");

      str_buffer_append(sb, "  <br>Realm name: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\"");
      if (!is_superuser()) {
        str_buffer_append(sb, " disabled ");
      }
      str_buffer_append(sb, ">");

      str_buffer_append(sb, "<br><input type=\"submit\" value=\"Filter\">");

      str_buffer_append(sb, "</fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");

      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_USERS].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(sb, "  <fieldset><legend>User:</legend>\r\n");

      if (msg && msg[0]) {
        str_buffer_append(sb, "<br><table id=\"msg\"><th>");
        str_buffer_append_html_escaped(sb, msg);
        str_buffer_append(sb, "</th></table><br>");
      }

      str_buffer_append(sb, "  <br>Realm name: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_ADD_REALM);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, (const char *)add_realm);
      str_buffer_append(sb, "\"");
      if (!is_superuser()) {
        str_buffer_append(sb, " disabled ");
      }
      str_buffer_append(sb, "><br>\r\n");

      str_buffer_append(sb, "  <br>User name: <input required type=\"text\" name=\"");
      str_buffer_append(sb, HR_ADD_USER);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, (const char *)add_user);
      str_buffer_append(sb, "\"");
      str_buffer_append(sb, "><br>\r\n");

      str_buffer_append(sb, "  <br>Password: <input required type=\"password\" name=\"");
      str_buffer_append(sb, HR_PASSWORD);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append(sb, "");
      str_buffer_append(sb, "\"");
      str_buffer_append(sb, "><br>\r\n");

      str_buffer_append(sb, "  <br>Confirm password: <input required type=\"password\" name=\"");
      str_buffer_append(sb, HR_PASSWORD1);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append(sb, "");
      str_buffer_append(sb, "\"");
      str_buffer_append(sb, "><br><br>\r\n");

      str_buffer_append(sb, "<br><input type=\"submit\" value=\"Add user\">");

      str_buffer_append(sb, "</fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");

      str_buffer_append(sb, "<br><b>Users:</b><br><br>\r\n");
      str_buffer_append(sb, "<table>\r\n");
      str_buffer_append(sb, "<tr><th>N</th><th>Name</th>");
      if (!current_eff_realm()[0]) {
        str_buffer_append(sb, "<th>Realm</th>");
      }
      str_buffer_append(sb, "<th> </th>");
      str_buffer_append(sb, "</tr>\r\n");

      const size_t total_sz = https_print_users(sb);

      str_buffer_append(sb, "\r\n</table>\r\n");

      str_buffer_append(sb, "<br>Total users = ");
      str_buffer_append_sz(sb, total_sz);
      str_buffer_append(sb, "<br>\r\n");

      https_finish_page(sb, s, 0);
    }
  }
}

static size_t https_print_secrets(struct str_buffer *sb) {
  size_t ret = 0;
  const turn_dbdriver_t *dbd = get_dbdriver();
  if (dbd && dbd->list_secrets) {
    secrets_list_t secrets, realms;
    init_secrets_list(&secrets);
    init_secrets_list(&realms);
    dbd->list_secrets((uint8_t *)current_eff_realm(), &secrets, &realms);

    const size_t sz = get_secrets_list_size(&secrets);
    size_t i;
    for (i = 0; i < sz; ++i) {
      str_buffer_append(sb, "<tr><td>");
      str_buffer_append_sz(sb, i + 1);
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&secrets, i));
      str_buffer_append(sb, "</td>");
      if (!current_eff_realm()[0]) {
        str_buffer_append(sb, "<td>");
        str_buffer_append_html_escaped(sb, get_secrets_list_elem(&realms, i));
        str_buffer_append(sb, "</td>");
      }
      str_buffer_append(sb, "<td> <a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_SS].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_DELETE_SECRET);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, get_secrets_list_elem(&secrets, i));
      str_buffer_append(sb, "&");
      str_buffer_append(sb, HR_DELETE_REALM);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, get_secrets_list_elem(&realms, i));
      str_buffer_append(sb, "\">delete</a>");
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "</tr>");
      ++ret;
    }

    clean_secrets_list(&secrets);
    clean_secrets_list(&realms);
  }

  return ret;
}

static void write_shared_secrets_page(ioa_socket_handle s, const char *add_secret, const char *add_realm,
                                      const char *msg) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_SS].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(sb, "  <fieldset><legend>Filter:</legend>\r\n");

      str_buffer_append(sb, "  <br>Realm name: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\"");
      if (!is_superuser()) {
        str_buffer_append(sb, " disabled ");
      }
      str_buffer_append(sb, ">");

      str_buffer_append(sb, "<br><input type=\"submit\" value=\"Filter\">");

      str_buffer_append(sb, "</fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");

      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_SS].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(sb, "  <fieldset><legend>Secret:</legend>\r\n");

      if (msg && msg[0]) {
        str_buffer_append(sb, "<br><table id=\"msg\"><th>");
        str_buffer_append_html_escaped(sb, msg);
        str_buffer_append(sb, "</th></table><br>");
      }

      str_buffer_append(sb, "  <br>Realm name: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_ADD_REALM);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, (const char *)add_realm);
      str_buffer_append(sb, "\"");
      if (!is_superuser()) {
        str_buffer_append(sb, " disabled ");
      }
      str_buffer_append(sb, "><br>\r\n");

      str_buffer_append(sb, "  <br>Secret: <input required type=\"text\" name=\"");
      str_buffer_append(sb, HR_ADD_SECRET);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, (const char *)add_secret);
      str_buffer_append(sb, "\"");
      str_buffer_append(sb, "><br>\r\n");

      str_buffer_append(sb, "<br><input type=\"submit\" value=\"Add secret\">");

      str_buffer_append(sb, "</fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");

      str_buffer_append(sb, "<br><b>Shared secrets:</b><br><br>\r\n");
      str_buffer_append(sb, "<table>\r\n");
      str_buffer_append(sb, "<tr><th>N</th><th>Value</th>");
      if (!current_eff_realm()[0]) {
        str_buffer_append(sb, "<th>Realm</th>");
      }
      str_buffer_append(sb, "<th> </th>");
      str_buffer_append(sb, "</tr>\r\n");

      const size_t total_sz = https_print_secrets(sb);

      str_buffer_append(sb, "\r\n</table>\r\n");

      str_buffer_append(sb, "<br>Total secrets = ");
      str_buffer_append_sz(sb, total_sz);
      str_buffer_append(sb, "<br>\r\n");

      https_finish_page(sb, s, 0);
    }
  }
}

static size_t https_print_origins(struct str_buffer *sb) {
  size_t ret = 0;
  const turn_dbdriver_t *dbd = get_dbdriver();
  if (dbd && dbd->list_origins) {
    secrets_list_t origins, realms;
    init_secrets_list(&origins);
    init_secrets_list(&realms);
    dbd->list_origins((uint8_t *)current_eff_realm(), &origins, &realms);

    const size_t sz = get_secrets_list_size(&origins);
    size_t i;
    for (i = 0; i < sz; ++i) {
      str_buffer_append(sb, "<tr><td>");
      str_buffer_append_sz(sb, i + 1);
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&origins, i));
      str_buffer_append(sb, "</td>");
      if (!current_eff_realm()[0]) {
        str_buffer_append(sb, "<td>");
        str_buffer_append_html_escaped(sb, get_secrets_list_elem(&realms, i));
        str_buffer_append(sb, "</td>");
      }
      if (is_superuser()) {
        str_buffer_append(sb, "<td> <a href=\"");
        str_buffer_append(sb, form_names[AS_FORM_OS].name);
        str_buffer_append(sb, "?");
        str_buffer_append(sb, HR_DELETE_ORIGIN);
        str_buffer_append(sb, "=");
        str_buffer_append_uri_escaped(sb, get_secrets_list_elem(&origins, i));
        str_buffer_append(sb, "\">delete</a>");
        str_buffer_append(sb, "</td>");
      }
      str_buffer_append(sb, "</tr>");
      ++ret;
    }

    clean_secrets_list(&origins);
    clean_secrets_list(&realms);
  }

  return ret;
}

static void write_origins_page(ioa_socket_handle s, const char *add_origin, const char *add_realm, const char *msg) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      str_buffer_append(sb, "<form action=\"");
      str_buffer_append(sb, form_names[AS_FORM_OS].name);
      str_buffer_append(sb, "\" method=\"POST\">\r\n");
      str_buffer_append(sb, "  <fieldset><legend>Filter:</legend>\r\n");

      str_buffer_append(sb, "  <br>Realm name: <input type=\"text\" name=\"");
      str_buffer_append(sb, HR_REALM);
      str_buffer_append(sb, "\" value=\"");
      str_buffer_append_html_escaped(sb, current_eff_realm());
      str_buffer_append(sb, "\"");
      if (!is_superuser()) {
        str_buffer_append(sb, " disabled ");
      }
      str_buffer_append(sb, ">");

      str_buffer_append(sb, "<br><input type=\"submit\" value=\"Filter\">");

      str_buffer_append(sb, "</fieldset>\r\n");
      str_buffer_append(sb, "</form>\r\n");

      if (is_superuser()) {
        str_buffer_append(sb, "<form action=\"");
        str_buffer_append(sb, form_names[AS_FORM_OS].name);
        str_buffer_append(sb, "\" method=\"POST\">\r\n");
        str_buffer_append(sb, "  <fieldset><legend>Origin:</legend>\r\n");

        if (msg && msg[0]) {
          str_buffer_append(sb, "<br><table id=\"msg\"><th>");
          str_buffer_append_html_escaped(sb, msg);
          str_buffer_append(sb, "</th></table><br>");
        }

        str_buffer_append(sb, "  <br>Realm name: <input required type=\"text\" name=\"");
        str_buffer_append(sb, HR_ADD_REALM);
        str_buffer_append(sb, "\" value=\"");
        str_buffer_append_html_escaped(sb, (const char *)add_realm);
        str_buffer_append(sb, "\"");
        str_buffer_append(sb, "><br>\r\n");

        str_buffer_append(sb, "  <br>Origin: <input required type=\"text\" name=\"");
        str_buffer_append(sb, HR_ADD_ORIGIN);
        str_buffer_append(sb, "\" value=\"");
        str_buffer_append_html_escaped(sb, (const char *)add_origin);
        str_buffer_append(sb, "\"");
        str_buffer_append(sb, "><br>\r\n");

        str_buffer_append(sb, "<br><input type=\"submit\" value=\"Add origin\">");

        str_buffer_append(sb, "</fieldset>\r\n");
        str_buffer_append(sb, "</form>\r\n");
      }

      str_buffer_append(sb, "<br><b>Origins:</b><br><br>\r\n");
      str_buffer_append(sb, "<table>\r\n");
      str_buffer_append(sb, "<tr><th>N</th><th>Value</th>");
      if (!current_eff_realm()[0]) {
        str_buffer_append(sb, "<th>Realm</th>");
      }
      if (is_superuser()) {
        str_buffer_append(sb, "<th> </th>");
      }
      str_buffer_append(sb, "</tr>\r\n");

      const size_t total_sz = https_print_origins(sb);

      str_buffer_append(sb, "\r\n</table>\r\n");

      str_buffer_append(sb, "<br>Total origins = ");
      str_buffer_append_sz(sb, total_sz);
      str_buffer_append(sb, "<br>\r\n");

      https_finish_page(sb, s, 0);
    }
  }
}

static size_t https_print_oauth_keys(struct str_buffer *sb) {
  size_t ret = 0;
  const turn_dbdriver_t *dbd = get_dbdriver();
  if (dbd && dbd->list_oauth_keys) {
    secrets_list_t kids, teas, tss, lts, realms;
    init_secrets_list(&kids);
    init_secrets_list(&teas);
    init_secrets_list(&tss);
    init_secrets_list(&lts);
    init_secrets_list(&realms);
    dbd->list_oauth_keys(&kids, &teas, &tss, &lts, &realms);

    const size_t sz = get_secrets_list_size(&kids);
    size_t i;
    for (i = 0; i < sz; ++i) {
      str_buffer_append(sb, "<tr><td>");
      str_buffer_append_sz(sb, i + 1);
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&kids, i));
      str_buffer_append(sb, "</td>");

      str_buffer_append(sb, "<td><a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_OAUTH_SHOW_KEYS].name);
      str_buffer_append(sb, "?");
      str_buffer_append(sb, HR_OAUTH_KID);
      str_buffer_append(sb, "=");
      str_buffer_append_uri_escaped(sb, get_secrets_list_elem(&kids, i));
      str_buffer_append(sb, "\"> show </a></td>");

      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&tss, i));
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&lts, i));
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&teas, i));
      str_buffer_append(sb, "</td>");
      str_buffer_append(sb, "<td>");
      str_buffer_append_html_escaped(sb, get_secrets_list_elem(&realms, i));
      str_buffer_append(sb, "</td>");

      {
        str_buffer_append(sb, "<td> <a href=\"");
        str_buffer_append(sb, form_names[AS_FORM_OAUTH].name);
        str_buffer_append(sb, "?");
        str_buffer_append(sb, HR_DELETE_OAUTH_KID);
        str_buffer_append(sb, "=");
        str_buffer_append_uri_escaped(sb, get_secrets_list_elem(&kids, i));
        str_buffer_append(sb, "\">delete</a>");
        str_buffer_append(sb, "</td>");
      }
      str_buffer_append(sb, "</tr>");
      ++ret;
    }

    clean_secrets_list(&kids);
    clean_secrets_list(&teas);
    clean_secrets_list(&tss);
    clean_secrets_list(&lts);
    clean_secrets_list(&realms);
  }

  return ret;
}

static void write_https_oauth_show_keys(ioa_socket_handle s, const char *kid) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else if (!is_superuser()) {
      write_https_home_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      str_buffer_append(sb, "<a href=\"");
      str_buffer_append(sb, form_names[AS_FORM_OAUTH].name);
      str_buffer_append(sb, "\">back to oauth list</a><br><br>\r\n");

      if (kid && kid[0]) {
        const turn_dbdriver_t *dbd = get_dbdriver();
        if (dbd && dbd->get_oauth_key) {
          oauth_key_data_raw key;
          if ((*dbd->get_oauth_key)((const uint8_t *)kid, &key) < 0) {
            str_buffer_append(sb, "data retrieval error");
          } else {

            oauth_key_data okd;
            memset(&okd, 0, sizeof(okd));

            convert_oauth_key_data_raw(&key, &okd);

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

            oauth_key okey;
            memset(&okey, 0, sizeof(okey));

            if (!convert_oauth_key_data(&okd, &okey, err_msg, err_msg_size)) {
              str_buffer_append_html_escaped(sb, err_msg);
            } else {

              str_buffer_append(sb, "<table>\r\n");

              if (key.ikm_key[0]) {
                str_buffer_append(sb, "<tr><td>Base64-encoded key:</td><td>");
                str_buffer_append_html_escaped(sb, key.ikm_key);
                str_buffer_append(sb, "</td></tr>\r\n");
              }

              str_buffer_append(sb, "</table>\r\n");
            }
          }
        }
      }

      https_finish_page(sb, s, 0);
    }
  }
}

static void write_https_oauth_page(ioa_socket_handle s, const char *add_kid, const char *add_ikm, const char *add_tea,
                                   const char *add_ts, const char *add_lt, const char *add_realm, const char *msg) {
  if (s && !ioa_socket_tobeclosed(s)) {

    if (!is_as_ok(s)) {
      write_https_logon_page(s);
    } else if (!is_superuser()) {
      write_https_home_page(s);
    } else {

      struct str_buffer *sb = str_buffer_new();

      https_print_page_header(sb);

      {
        str_buffer_append(sb, "<form action=\"");
        str_buffer_append(sb, form_names[AS_FORM_OAUTH].name);
        str_buffer_append(sb, "\" method=\"POST\">\r\n");
        str_buffer_append(sb, "  <fieldset><legend>oAuth key:</legend>\r\n");

        if (msg && msg[0]) {
          str_buffer_append(sb, "<br><table id=\"msg\"><th>");
          str_buffer_append_html_escaped(sb, msg);
          str_buffer_append(sb, "</th></table><br>");
        }

        str_buffer_append(sb, "<table><tr><td>");

        {
          if (!add_kid) {
            add_kid = "";
          }

          str_buffer_append(sb, "  <br>KID (required): <input required type=\"text\" name=\"");
          str_buffer_append(sb, HR_ADD_OAUTH_KID);
          str_buffer_append(sb, "\" value=\"");
          str_buffer_append_html_escaped(sb, (const char *)add_kid);
          str_buffer_append(sb, "\"><br>\r\n");
        }

        str_buffer_append(sb, "</td><td>");

        {
          if (!add_ts) {
            add_ts = "";
          }

          str_buffer_append(sb, "  <br>Timestamp, secs (optional): <input type=\"number\" min=\"0\" name=\"");
          str_buffer_append(sb, HR_ADD_OAUTH_TS);
          str_buffer_append(sb, "\" value=\"");
          str_buffer_append_html_escaped(sb, (const char *)add_ts);
          str_buffer_append(sb, "\"><br>\r\n");
        }

        str_buffer_append(sb, "</td><td>");

        {
          if (!add_lt) {
            add_lt = "";
          }

          str_buffer_append(sb, "  <br>Lifetime, secs (optional): <input type=\"number\" min=\"0\" name=\"");
          str_buffer_append(sb, HR_ADD_OAUTH_LT);
          str_buffer_append(sb, "\" value=\"");
          str_buffer_append_html_escaped(sb, (const char *)add_lt);
          str_buffer_append(sb, "\"><br>\r\n");
        }

        str_buffer_append(sb, "</td></tr>\r\n");

        str_buffer_append(sb, "<tr><td colspan=\"1\">");

        {
          if (!add_ikm) {
            add_ikm = "";
          }

          str_buffer_append(sb, "  <br>Base64-encoded input keying material (required):<br><textarea wrap=\"soft\" "
                                "cols=40 rows=4 name=\"");
          str_buffer_append(sb, HR_ADD_OAUTH_IKM);
          str_buffer_append(sb, "\" maxLength=256 >");
          str_buffer_append_html_escaped(sb, (const char *)add_ikm);
          str_buffer_append(sb, "</textarea>");
          str_buffer_append(sb, "<br>\r\n");
        }

        str_buffer_append(sb, "</td><td>");

        {
          if (!add_realm) {
            add_realm = "";
          }

          str_buffer_append(sb, "  <br>Realm (optional): <input type=\"text\" name=\"");
          str_buffer_append(sb, HR_ADD_OAUTH_REALM);
          str_buffer_append(sb, "\" value=\"");
          str_buffer_append_html_escaped(sb, (const char *)add_realm);
          str_buffer_append(sb, "\"><br>\r\n");
        }

        str_buffer_append(sb, "</td><td>");

        {
          str_buffer_append(sb, "<br>Token encryption algorithm (required):<br>\r\n");

          if (!add_tea || !add_tea[0]) {
            add_tea = "A256GCM";
          }

          str_buffer_append(sb, "<input type=\"radio\" name=\"");
          str_buffer_append(sb, HR_ADD_OAUTH_TEA);
          str_buffer_append(sb, "\" value=\"A128GCM\" ");
          if (!strcmp("A128GCM", add_tea)) {
            str_buffer_append(sb, " checked ");
          }
          str_buffer_append(sb, ">A128GCM\r\n<br>\r\n");

          str_buffer_append(sb, "<input type=\"radio\" name=\"");
          str_buffer_append(sb, HR_ADD_OAUTH_TEA);
          str_buffer_append(sb, "\" value=\"A256GCM\" ");
          if (!strcmp("A256GCM", add_tea)) {
            str_buffer_append(sb, " checked ");
          }
          str_buffer_append(sb, ">A256GCM\r\n<br>\r\n");
        }

        str_buffer_append(sb, "</td></tr>\r\n</table>\r\n");

        str_buffer_append(sb, "<br><input type=\"submit\" value=\"Add key\">");

        str_buffer_append(sb, "</fieldset>\r\n");
        str_buffer_append(sb, "</form>\r\n");
      }

      str_buffer_append(sb, "<br><b>OAuth keys:</b><br><br>\r\n");
      str_buffer_append(sb, "<table>\r\n");
      str_buffer_append(sb, "<tr><th>N</th><th>KID</th><th>keys</th>");
      str_buffer_append(sb, "<th>Timestamp, secs</th>");
      str_buffer_append(sb, "<th>Lifetime,secs</th>");
      str_buffer_append(sb, "<th>Token encryption algorithm</th>");
      str_buffer_append(sb, "<th>Realm</th>");
      str_buffer_append(sb, "<th> </th>");
      str_buffer_append(sb, "</tr>\r\n");

      const size_t total_sz = https_print_oauth_keys(sb);

      str_buffer_append(sb, "\r\n</table>\r\n");

      str_buffer_append(sb, "<br>Total oAuth keys = ");
      str_buffer_append_sz(sb, total_sz);
      str_buffer_append(sb, "<br>\r\n");

      https_finish_page(sb, s, 0);
    }
  }
}

static void handle_toggle_request(ioa_socket_handle s, struct http_request *hr) {
  if (s && hr) {
    const char *param = get_http_header_value(hr, HR_UPDATE_PARAMETER, NULL);
    toggle_param(param);
  }
}

static void handle_update_request(ioa_socket_handle s, struct http_request *hr) {
  if (s && hr) {
    {
      const char *param = get_http_header_value(hr, HR_UPDATE_PARAMETER, NULL);
      if (param) {
        update_param(param, get_http_header_value(hr, param, ""));
      }
    }

    {
      const char *eip = get_http_header_value(hr, HR_DELETE_IP, NULL);
      if (eip && eip[0]) {
        char *ip = evhttp_decode_uri(eip);
        const char *r = get_http_header_value(hr, HR_DELETE_IP_REALM, "");
        const char *kind = get_http_header_value(hr, HR_DELETE_IP_KIND, "");

        const turn_dbdriver_t *dbd = get_dbdriver();
        if (dbd && dbd->set_permission_ip) {

          if (!r || !r[0]) {
            r = current_realm();
          }

          if (check_ip_list_range(ip) < 0) {
            TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address range format: %s\n", ip);
          } else if (current_realm()[0] && strcmp(current_realm(), r)) {
            // forbidden
          } else if (strcmp(kind, "allowed") != 0 && strcmp(kind, "denied") != 0) {
            // forbidden
          } else if (r[0] && !is_secure_string((const uint8_t *)r, 1)) {
            // forbidden: realm contains invalid characters
          } else {

            uint8_t realm[STUN_MAX_REALM_SIZE + 1] = "\0";
            STRCPY(realm, r);

            dbd->set_permission_ip(kind, realm, ip, 1);
          }
        }
        free(ip);
      }
    }

    {
      const char *eip = get_http_header_value(hr, HR_ADD_IP, NULL);
      if (eip && eip[0]) {
        char *ip = evhttp_decode_uri(eip);

        if (check_ip_list_range(ip) < 0) {
          TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address range format: %s\n", ip);
        } else {

          const char *r = get_http_header_value(hr, HR_ADD_IP_REALM, "");
          const char *kind = get_http_header_value(hr, HR_ADD_IP_KIND, "");

          const turn_dbdriver_t *dbd = get_dbdriver();
          if (dbd && dbd->set_permission_ip) {

            if (!r || !r[0]) {
              r = current_realm();
            }

            if (current_realm()[0] && strcmp(current_realm(), r)) {
              // forbidden
            } else if (strcmp(kind, "allowed") != 0 && strcmp(kind, "denied") != 0) {
              // forbidden
            } else if (r[0] && !is_secure_string((const uint8_t *)r, 1)) {
              // forbidden: realm contains invalid characters
            } else {

              uint8_t realm[STUN_MAX_REALM_SIZE + 1] = "\0";
              STRCPY(realm, r);

              dbd->set_permission_ip(kind, realm, ip, 0);
            }
          }
        }
        free(ip);
      }
    }
  }
}

/* ---- HTTPS admin logon brute-force throttle (per source IP) ----
 * The HTTPS admin interface authenticates per TLS connection, so without a
 * cross-connection control an attacker can attempt unlimited password guesses
 * by opening a fresh connection per attempt. This bounds the guess rate per
 * source IP. State lives in a small fixed table; on overflow the
 * least-recently-seen entry is evicted (worst case an attacker evicts their own
 * lockout, which only resets them to the start of the failure window). All
 * access happens on the single admin thread, but the mutex keeps it safe if
 * that ever changes. */
#define ADMIN_LOGON_THROTTLE_SIZE (256)
#define ADMIN_LOGON_MAX_FAILURES (5)
#define ADMIN_LOGON_LOCKOUT_SECS (30)
#define ADMIN_LOGON_WINDOW_SECS (60)

struct admin_logon_throttle_entry {
  ioa_addr addr;
  bool used;
  int failures;
  turn_time_t window_start;
  turn_time_t lockout_until;
  turn_time_t last_seen;
};

static struct admin_logon_throttle_entry admin_logon_throttle[ADMIN_LOGON_THROTTLE_SIZE];

/* Find or (re)allocate the entry for addr. Caller must hold the mutex. */
static struct admin_logon_throttle_entry *admin_logon_throttle_lookup(const ioa_addr *addr, turn_time_t now) {
  struct admin_logon_throttle_entry *lru = &admin_logon_throttle[0];
  for (size_t i = 0; i < ADMIN_LOGON_THROTTLE_SIZE; ++i) {
    struct admin_logon_throttle_entry *e = &admin_logon_throttle[i];
    if (e->used && addr_eq_no_port(&e->addr, addr)) {
      return e;
    }
    if (!e->used) {
      if (lru->used) {
        lru = e; /* prefer any free slot */
      }
    } else if (lru->used && turn_time_before(e->last_seen, lru->last_seen)) {
      lru = e;
    }
  }
  memset(lru, 0, sizeof(*lru));
  addr_cpy(&lru->addr, addr);
  lru->used = true;
  lru->window_start = now;
  return lru;
}

static bool admin_logon_is_locked(const ioa_addr *addr) {
  if (!addr) {
    return false;
  }
  const turn_time_t now = turn_time();
  TURN_MUTEX_LOCK(&admin_logon_throttle_mutex);
  struct admin_logon_throttle_entry *e = admin_logon_throttle_lookup(addr, now);
  const bool locked = (e->lockout_until != 0) && turn_time_before(now, e->lockout_until);
  TURN_MUTEX_UNLOCK(&admin_logon_throttle_mutex);
  return locked;
}

static void admin_logon_record_failure(const ioa_addr *addr) {
  if (!addr) {
    return;
  }
  const turn_time_t now = turn_time();
  TURN_MUTEX_LOCK(&admin_logon_throttle_mutex);
  struct admin_logon_throttle_entry *e = admin_logon_throttle_lookup(addr, now);
  if (!turn_time_before(now, e->window_start + ADMIN_LOGON_WINDOW_SECS)) {
    e->failures = 0;
    e->window_start = now;
  }
  e->failures++;
  e->last_seen = now;
  if (e->failures >= ADMIN_LOGON_MAX_FAILURES) {
    e->lockout_until = now + ADMIN_LOGON_LOCKOUT_SECS;
    e->failures = 0;
    e->window_start = now;
  }
  TURN_MUTEX_UNLOCK(&admin_logon_throttle_mutex);
}

static void admin_logon_record_success(const ioa_addr *addr) {
  if (!addr) {
    return;
  }
  const turn_time_t now = turn_time();
  TURN_MUTEX_LOCK(&admin_logon_throttle_mutex);
  struct admin_logon_throttle_entry *e = admin_logon_throttle_lookup(addr, now);
  memset(e, 0, sizeof(*e)); /* clear counters/lockout on a successful logon */
  TURN_MUTEX_UNLOCK(&admin_logon_throttle_mutex);
}

static void handle_logon_request(ioa_socket_handle s, struct http_request *hr) {
  if (s && hr) {
    const char *uname = get_http_header_value(hr, HR_USERNAME, NULL);
    const char *pwd = get_http_header_value(hr, HR_PASSWORD, NULL);
    const ioa_addr *src = get_remote_addr_from_ioa_socket(s);

    struct admin_session *as = (struct admin_session *)s->special_session;
    if (!as) {
      as = (struct admin_session *)calloc(1, sizeof(struct admin_session));
      if (!as) {
        return;
      }
      s->special_session = as;
      s->special_session_size = sizeof(struct admin_session);
    }

    if (!(as->as_ok) && uname && is_secure_string((const uint8_t *)uname, 1) && pwd) {
      if (admin_logon_is_locked(src)) {
        TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,
                      "%s: HTTPS admin logon rejected: too many failed attempts from source address\n", __FUNCTION__);
        return;
      }
      const turn_dbdriver_t *dbd = get_dbdriver();
      if (dbd && dbd->get_admin_user) {
        password_t password;
        char realm[STUN_MAX_REALM_SIZE + 1] = "\0";
        if ((*(dbd->get_admin_user))((const uint8_t *)uname, (uint8_t *)realm, password) >= 0) {
          if (check_password_equal(pwd, (char *)password)) {
            STRCPY(as->as_login, uname);
            STRCPY(as->as_realm, realm);
            as->as_eff_realm[0] = 0;
            as->as_ok = 1;
            as->number_of_user_sessions = DEFAULT_CLI_MAX_OUTPUT_SESSIONS;
          }
        }
      }
      if (as->as_ok) {
        admin_logon_record_success(src);
      } else {
        admin_logon_record_failure(src);
      }
    }
  }
}

static void handle_logout_request(ioa_socket_handle s, struct http_request *hr) {
  UNUSED_ARG(hr);
  if (s) {
    struct admin_session *as = (struct admin_session *)s->special_session;
    if (as) {
      as->as_login[0] = 0;
      as->as_ok = 0;
      as->as_realm[0] = 0;
      as->as_eff_realm[0] = 0;
    }
  }
}

static void handle_https(ioa_socket_handle s, ioa_network_buffer_handle nbh) {
  current_socket = s;

  if (turn_params.verbose) {
    if (nbh) {
      ((char *)ioa_network_buffer_data(nbh))[ioa_network_buffer_get_size(nbh)] = 0;
      if (!strstr((char *)ioa_network_buffer_data(nbh), "pwd")) {
        TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: HTTPS connection input: %s\n", __FUNCTION__,
                      (char *)ioa_network_buffer_data(nbh));
      }
    } else {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: HTTPS connection initial input\n", __FUNCTION__);
    }
  }

  if (!nbh) {
    write_https_logon_page(s);
  } else {
    ((char *)ioa_network_buffer_data(nbh))[ioa_network_buffer_get_size(nbh)] = 0;
    struct http_request *hr = parse_http_request((char *)ioa_network_buffer_data(nbh));
    if (!hr) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: wrong HTTPS request (I cannot parse it)\n", __FUNCTION__);
      write_https_logon_page(s);
    } else {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: HTTPS request, path %s\n", __FUNCTION__, hr->path);

      const AS_FORM form = get_form(hr->path);

      switch (form) {
      case AS_FORM_PC: {
        if (is_as_ok(s)) {
          const char *realm0 = get_http_header_value(hr, HR_REALM, current_realm());
          if (!is_superuser()) {
            realm0 = current_realm();
          }
          strncpy(current_eff_realm(), realm0, STUN_MAX_REALM_SIZE);
          write_pc_page(s);
        } else {
          write_https_logon_page(s);
        }
        break;
      }
      case AS_FORM_PS: {
        if (is_as_ok(s)) {
          const char *realm0 = get_http_header_value(hr, HR_REALM, current_realm());
          if (!is_superuser()) {
            realm0 = current_realm();
          }
          strncpy(current_eff_realm(), realm0, STUN_MAX_REALM_SIZE);

          const char *client_protocol = get_http_header_value(hr, HR_CLIENT_PROTOCOL, "");

          const char *user_pattern = get_http_header_value(hr, HR_USER_PATTERN, "");

          turnsession_id csid = 0;
          const char *ssid = get_http_header_value(hr, HR_CANCEL_SESSION, NULL);
          if (ssid) {
            https_cancel_session(ssid);
            csid = (turnsession_id)strtoull(ssid, NULL, 10);
          }

          size_t max_sessions = current_max_output_sessions();
          const char *s_max_sessions = get_http_header_value(hr, HR_MAX_SESSIONS, NULL);
          if (s_max_sessions) {
            max_sessions = strtoul(s_max_sessions, NULL, 10);
            if (!max_sessions) {
              max_sessions = current_max_output_sessions();
            }
            set_current_max_output_sessions(max_sessions);
          }

          if (!max_sessions) {
            max_sessions = DEFAULT_CLI_MAX_OUTPUT_SESSIONS;
          }

          write_ps_page(s, client_protocol, user_pattern, max_sessions, csid);
        } else {
          write_https_logon_page(s);
        }
        break;
      }
      case AS_FORM_USERS: {
        if (is_as_ok(s)) {
          {
            const char *realm0 = get_http_header_value(hr, HR_REALM, current_realm());
            if (!is_superuser()) {
              realm0 = current_realm();
            }
            strncpy(current_eff_realm(), realm0, STUN_MAX_REALM_SIZE);
          }

          {
            const uint8_t *user = (const uint8_t *)get_http_header_value(hr, HR_DELETE_USER, NULL);
            if (user && user[0] && is_secure_string(user, 1)) {
              const uint8_t *realm = (const uint8_t *)get_http_header_value(hr, HR_DELETE_REALM, "");
              if (!is_superuser()) {
                realm = (const uint8_t *)current_realm();
              }
              if (realm && realm[0] && is_secure_string(realm, 1)) {
                const turn_dbdriver_t *dbd = get_dbdriver();
                if (dbd && dbd->del_user) {
                  uint8_t u[STUN_MAX_USERNAME_SIZE + 1];
                  uint8_t r[STUN_MAX_REALM_SIZE + 1];
                  STRCPY(u, user);
                  STRCPY(r, realm);
                  dbd->del_user(u, r);
                }
              }
            }
          }

          const uint8_t *add_realm = (const uint8_t *)current_eff_realm();
          const uint8_t *add_user = (const uint8_t *)get_http_header_value(hr, HR_ADD_USER, "");
          const char *msg = "";
          if (wrong_html_name((const char *)add_user)) {
            msg = "Error: wrong user name";
            add_user = (const uint8_t *)"";
          }
          if (add_user[0]) {
            add_realm = (const uint8_t *)get_http_header_value(hr, HR_ADD_REALM, current_realm());
            if (!is_superuser()) {
              add_realm = (const uint8_t *)current_realm();
            }
            if (!add_realm[0]) {
              add_realm = (const uint8_t *)current_eff_realm();
            }
            if (!add_realm[0]) {
              add_realm = (const uint8_t *)get_realm(NULL)->options.name;
            }
            if (wrong_html_name((const char *)add_realm)) {
              msg = "Error: wrong realm name";
              add_realm = (const uint8_t *)"";
            }
            if (add_realm[0]) {
              const uint8_t *pwd = (const uint8_t *)get_http_header_value(hr, HR_PASSWORD, NULL);
              const uint8_t *pwd1 = (const uint8_t *)get_http_header_value(hr, HR_PASSWORD1, NULL);
              if (pwd && pwd1 && pwd[0] && pwd1[0] && !strcmp((const char *)pwd, (const char *)pwd1)) {

                const turn_dbdriver_t *dbd = get_dbdriver();
                if (dbd && dbd->set_user_key) {

                  hmackey_t key;
                  char skey[sizeof(hmackey_t) * 2 + 1];

                  {
                    uint8_t u[STUN_MAX_USERNAME_SIZE + 1];
                    uint8_t r[STUN_MAX_REALM_SIZE + 1];
                    uint8_t p[STUN_MAX_PWD_SIZE + 1];
                    STRCPY(u, add_user);
                    STRCPY(r, add_realm);
                    STRCPY(p, pwd);
                    stun_produce_integrity_key_str(u, r, p, key, SHATYPE_DEFAULT);
                    size_t i = 0;
                    const size_t sz = get_hmackey_size(SHATYPE_DEFAULT);
                    int maxsz = (int)(sz * 2) + 1;
                    char *s = skey;
                    for (i = 0; (i < sz) && (maxsz > 2); i++) {
                      snprintf(s, (size_t)(sz * 2), "%02x", (unsigned int)key[i]);
                      maxsz -= 2;
                      s += 2;
                    }
                    skey[sz * 2] = 0;

                    (*dbd->set_user_key)(u, r, skey);
                  }

                  add_realm = (const uint8_t *)"";
                  add_user = (const uint8_t *)"";
                }
              } else {
                msg = "Error: wrong password";
              }
            }
          }

          write_users_page(s, add_user, add_realm, msg);

        } else {
          write_https_logon_page(s);
        }
        break;
      }
      case AS_FORM_SS: {
        if (is_as_ok(s)) {
          {
            const char *realm0 = get_http_header_value(hr, HR_REALM, current_realm());
            if (!is_superuser()) {
              realm0 = current_realm();
            }
            strncpy(current_eff_realm(), realm0, STUN_MAX_REALM_SIZE);
          }

          {
            const uint8_t *secret = (const uint8_t *)get_http_header_value(hr, HR_DELETE_SECRET, NULL);
            if (secret && secret[0] && is_secure_string(secret, 1)) {
              const uint8_t *realm = (const uint8_t *)get_http_header_value(hr, HR_DELETE_REALM, NULL);
              if (!is_superuser()) {
                realm = (const uint8_t *)current_realm();
              }
              if (realm && realm[0] && is_secure_string(realm, 1)) {
                const turn_dbdriver_t *dbd = get_dbdriver();
                if (dbd && dbd->del_secret) {
                  uint8_t ss[AUTH_SECRET_SIZE + 1];
                  uint8_t r[STUN_MAX_REALM_SIZE + 1];
                  STRCPY(ss, secret);
                  STRCPY(r, realm);
                  dbd->del_secret(ss, r);
                }
              }
            }
          }

          const uint8_t *add_realm = (const uint8_t *)current_eff_realm();
          const uint8_t *add_secret = (const uint8_t *)get_http_header_value(hr, HR_ADD_SECRET, "");
          const char *msg = "";
          if (wrong_html_name((const char *)add_secret)) {
            msg = "Error: wrong secret value";
            add_secret = (const uint8_t *)"";
          }
          if (add_secret[0]) {
            add_realm = (const uint8_t *)get_http_header_value(hr, HR_ADD_REALM, current_realm());
            if (!is_superuser()) {
              add_realm = (const uint8_t *)current_realm();
            }
            if (!add_realm[0]) {
              add_realm = (const uint8_t *)current_eff_realm();
            }
            if (!add_realm[0]) {
              add_realm = (const uint8_t *)get_realm(NULL)->options.name;
            }
            if (wrong_html_name((const char *)add_realm)) {
              msg = "Error: wrong realm name";
              add_realm = (const uint8_t *)"";
            }
            if (add_realm[0]) {
              const turn_dbdriver_t *dbd = get_dbdriver();
              if (dbd && dbd->set_secret) {
                uint8_t ss[AUTH_SECRET_SIZE + 1];
                uint8_t r[STUN_MAX_REALM_SIZE + 1];
                STRCPY(ss, add_secret);
                STRCPY(r, add_realm);
                (*dbd->set_secret)(ss, r);
              }

              add_secret = (const uint8_t *)"";
              add_realm = (const uint8_t *)"";
            }
          }

          write_shared_secrets_page(s, (const char *)add_secret, (const char *)add_realm, msg);

        } else {
          write_https_logon_page(s);
        }
        break;
      }
      case AS_FORM_OS: {
        if (is_as_ok(s)) {
          {
            const char *realm0 = get_http_header_value(hr, HR_REALM, current_realm());
            if (!is_superuser()) {
              realm0 = current_realm();
            }
            strncpy(current_eff_realm(), realm0, STUN_MAX_REALM_SIZE);
          }

          if (is_superuser()) {
            const uint8_t *origin = (const uint8_t *)get_http_header_value(hr, HR_DELETE_ORIGIN, NULL);
            if (origin && origin[0]) {
              const turn_dbdriver_t *dbd = get_dbdriver();
              if (dbd && dbd->del_origin) {
                uint8_t o[STUN_MAX_ORIGIN_SIZE + 1];
                STRCPY(o, origin);
                dbd->del_origin(o);
                uint8_t corigin[STUN_MAX_ORIGIN_SIZE + 1] = "\0";
                get_canonic_origin((const char *)origin, (char *)corigin, sizeof(corigin) - 1);
                dbd->del_origin(corigin);
              }
            }
          }

          const uint8_t *add_realm = (const uint8_t *)current_eff_realm();
          const uint8_t *add_origin = (const uint8_t *)get_http_header_value(hr, HR_ADD_ORIGIN, "");
          const char *msg = "";
          uint8_t corigin[STUN_MAX_ORIGIN_SIZE + 1] = "\0";
          get_canonic_origin((const char *)add_origin, (char *)corigin, sizeof(corigin) - 1);
          if (corigin[0]) {
            add_realm = (const uint8_t *)get_http_header_value(hr, HR_ADD_REALM, current_realm());
            if (!is_superuser()) {
              add_realm = (const uint8_t *)current_realm();
            }
            if (!add_realm[0]) {
              add_realm = (const uint8_t *)current_eff_realm();
            }
            if (!add_realm[0]) {
              add_realm = (const uint8_t *)get_realm(NULL)->options.name;
            }
            if (add_realm[0]) {
              const turn_dbdriver_t *dbd = get_dbdriver();
              if (dbd && dbd->add_origin) {
                uint8_t o[STUN_MAX_ORIGIN_SIZE + 1];
                uint8_t r[STUN_MAX_REALM_SIZE + 1];
                STRCPY(o, corigin);
                STRCPY(r, add_realm);
                (*dbd->add_origin)(o, r);
              }

              add_origin = (const uint8_t *)"";
              add_realm = (const uint8_t *)"";
            }
          }

          write_origins_page(s, (const char *)add_origin, (const char *)add_realm, msg);

        } else {
          write_https_logon_page(s);
        }
        break;
      }
      case AS_FORM_OAUTH_SHOW_KEYS: {
        if (!is_as_ok(s)) {
          write_https_logon_page(s);
        } else if (!is_superuser()) {
          write_https_home_page(s);
        } else {
          const char *kid = get_http_header_value(hr, HR_OAUTH_KID, "");
          write_https_oauth_show_keys(s, kid);
        }
        break;
      }
      case AS_FORM_OAUTH: {
        if (!is_as_ok(s)) {
          write_https_logon_page(s);
        } else if (!is_superuser()) {
          write_https_home_page(s);
        } else {

          {
            const char *del_kid = get_http_header_value(hr, HR_DELETE_OAUTH_KID, "");
            if (del_kid[0]) {
              const turn_dbdriver_t *dbd = get_dbdriver();
              if (dbd && dbd->del_oauth_key) {
                (*dbd->del_oauth_key)((const uint8_t *)del_kid);
              }
            }
          }

          const char *add_kid = "";
          const char *add_ts = "0";
          const char *add_lt = "0";
          const char *add_ikm = "";
          const char *add_tea = "";
          const char *add_realm = "";
          const char *msg = "";

          add_kid = get_http_header_value(hr, HR_ADD_OAUTH_KID, "");
          if (add_kid[0]) {
            add_ikm = get_http_header_value(hr, HR_ADD_OAUTH_IKM, "");
            add_ts = get_http_header_value(hr, HR_ADD_OAUTH_TS, "");
            add_lt = get_http_header_value(hr, HR_ADD_OAUTH_LT, "");
            add_tea = get_http_header_value(hr, HR_ADD_OAUTH_TEA, "");
            add_realm = get_http_header_value(hr, HR_ADD_OAUTH_REALM, "");

            const int keys_ok = (add_ikm[0] != 0);
            if (!keys_ok) {
              msg = "You must enter the key value.";
            } else {
              oauth_key_data_raw key;
              memset(&key, 0, sizeof(key));
              STRCPY(key.kid, add_kid);

              if (add_lt && add_lt[0]) {
                key.lifetime = (uint32_t)strtoul(add_lt, NULL, 10);
                if (key.lifetime) {
                  if (add_ts && add_ts[0]) {
                    key.timestamp = (uint64_t)strtoull(add_ts, NULL, 10);
                  }
                  if (!key.timestamp) {
                    key.timestamp = (uint64_t)time(NULL);
                  }
                }
              } else if (add_ts && add_ts[0]) {
                key.timestamp = (uint64_t)strtoull(add_ts, NULL, 10);
              }

              if (add_realm && add_realm[0]) {
                STRCPY(key.realm, add_realm);
              }

              STRCPY(key.ikm_key, add_ikm);
              STRCPY(key.as_rs_alg, add_tea);

              const turn_dbdriver_t *dbd = get_dbdriver();
              if (dbd && dbd->set_oauth_key) {
                if ((*dbd->set_oauth_key)(&key) < 0) {
                  msg = "Cannot insert oAuth key into the database";
                } else {
                  add_kid = "";
                  add_ts = "0";
                  add_lt = "0";
                  add_ikm = "";
                  add_tea = "";
                  add_realm = "";
                }
              }
            }
          }

          write_https_oauth_page(s, add_kid, add_ikm, add_tea, add_ts, add_lt, add_realm, msg);
        }
        break;
      }
      case AS_FORM_TOGGLE:
        if (is_as_ok(s)) {
          handle_toggle_request(s, hr);
          write_pc_page(s);
        } else {
          write_https_logon_page(s);
        }
        break;
      case AS_FORM_UPDATE:
        if (is_as_ok(s)) {
          handle_update_request(s, hr);
          write_pc_page(s);
        } else {
          write_https_logon_page(s);
        }
        break;
      case AS_FORM_LOGON:
        if (!is_as_ok(s)) {
          handle_logon_request(s, hr);
          if (is_as_ok(s)) {
            write_https_home_page(s);
          } else {
            write_https_logon_page(s);
          }
        } else {
          write_https_home_page(s);
        }
        break;
      case AS_FORM_LOGOUT:
        handle_logout_request(s, hr);
        write_https_logon_page(s);
        break;
      default: {
        const char *realm0 = get_http_header_value(hr, HR_REALM, current_realm());
        if (!is_superuser()) {
          realm0 = current_realm();
        }
        strncpy(current_eff_realm(), realm0, STUN_MAX_REALM_SIZE);
        write_https_home_page(s);
      }
      };
      free_http_request(hr);
    }
  }

  current_socket = NULL;
}

static void https_input_handler(ioa_socket_handle s, int event_type, ioa_net_data *data, void *arg, int can_resume) {

  UNUSED_ARG(arg);
  UNUSED_ARG(s);
  UNUSED_ARG(event_type);
  UNUSED_ARG(can_resume);

  handle_https(s, data->nbh);

  ioa_network_buffer_delete(adminserver.e, data->nbh);
  data->nbh = NULL;
}

void https_admin_server_receive_message(struct bufferevent *bev, void *ptr) {
  UNUSED_ARG(ptr);

  ioa_socket_handle s = NULL;
  int n = 0;
  struct evbuffer *input = bufferevent_get_input(bev);

  while ((n = evbuffer_remove(input, &s, sizeof(s))) > 0) {
    if (n != sizeof(s)) {
      fprintf(stderr, "%s: Weird HTTPS CLI buffer error: size=%d\n", __FUNCTION__, n);
      continue;
    }

    register_callback_on_ioa_socket(adminserver.e, s, IOA_EV_READ, https_input_handler, NULL, 0);

    handle_https(s, NULL);
  }
}

void send_https_socket(ioa_socket_handle s) {
  struct evbuffer *output = bufferevent_get_output(adminserver.https_out_buf);
  if (output) {
    evbuffer_add(output, &s, sizeof(s));
  }
}

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