#[
  Newly written module to generate Tor's config
  Document of each config's field can be read here
  https://2019.www.torproject.org/docs/tor-manual.html.en
  # TODO bypass firewall
]#

import strutils
import os
import random
import load_sys_config
import .. / .. / dnstool / cli / print

const
  config_torrc_base = staticRead("../../../configs/torrc.base")
  path_torrc_config = "/etc/tor/torrc"
  path_torrc_backup = "/etc/tor/torrc.bak"
  path_list_bridges = "/etc/anonsurf/bridges.txt"


proc maketorrc_get_random_bridge(): string =
  #[
    Read the list from official list
    Random selecting from the list
  ]#
  # New list from https://trac.torproject.org/projects/tor/wiki/doc/TorBrowser/DefaultBridges
  randomize()

  var
    list_bridge_addrs: seq[string]

  for line in lines(path_list_bridges):
    if not line.startsWith("#") and not isEmptyOrWhitespace(line):
      list_bridge_addrs.add(line)

  return sample(list_bridge_addrs)


proc maketorrc_validate_bridge_setting(base_settings: var string, bridge_addr: string): bool =
  #[
    Manage bridge type (obfs4 or other)
    Make a pre-check if binary to use bridge exists, or return error
    obfs4proxy:
      https://sigvids.gitlab.io/create-tor-private-obfs4-bridges.html
      https://community.torproject.org/relay/setup/bridge/debian-ubuntu/
    snowflake:
      https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/tree/main/client#running-the-snowflake-client-with-tor
    meek
      https://blog.torproject.org/how-use-meek-pluggable-transport/
  ]#

  if bridge_addr.startsWith("obfs4 "):
    let obfs4_path = findExe("obfs4proxy")
    if isEmptyOrWhitespace(obfs4_path):
      print_error("obfs4proxy not found. Config will not enable bridge in torrc")
      base_settings &= "# Error: obfs4proxy not found in the system\n"
      return false
    else:
      base_settings &= "UseBridges 1\nClientTransportPlugin obfs4 exec " & obfs4_path & " managed\n"

  elif bridge_addr.startsWith("snowflake "):
    let snowflake_path = findExe("snowflake-client")
    if isEmptyOrWhitespace(snowflake_path):
      print_error("snowflake-client not found. Config will not enable bridge in torrc")
      base_settings &= "# Error: snowflake-client not found in the system\n"
      return false
    else:
      base_settings &= "UseBridges 1\nClientTransportPlugin snowflake exec " & snowflake_path & "\n"

  elif bridge_addr.startsWith("meek "):
    base_settings &= "# Error: meek not supported yet\n"

  return true


proc maketorrc_set_bridge(base_settings: var string, auto_bridge: bool, bridge_addr: string) =
  #[
    Generate option to use bridge, and bridge's address for Torrc
    If auto_bridge is true, this program selects random bridge addr from list
    Else:
      - If bridge addr is empty, it's no bridge mode
      - If bridge addr is not empty, use current bridge addr. It's as same as manual in previous version
  ]#
  var
    final_bridge_addr: string = bridge_addr

  if auto_bridge == true:
    final_bridge_addr = maketorrc_get_random_bridge()
  else:
    if isEmptyOrWhitespace(final_bridge_addr):
      base_settings &= "\n# Bridge setting: No bridge\n"
    else:
      base_settings &= "\n# Enable bridge mode\n"
      if maketorrc_validate_bridge_setting(base_settings, final_bridge_addr):
        base_settings &= "\nBridge " & final_bridge_addr & "\n"


proc maketorrc_set_sandbox(base_settings: var string, use_sandbox: bool) =
  #[
    Generate option "Sandbox" for Torrc. Parse from bool:
      false = 0
      true = 1
    If set to 1, Tor will run securely through the use of a syscall sandbox. Otherwise the sandbox will be disabled. The option is currently an experimental feature. It only works on Linux-based operating systems, and only when Tor has been built with the libseccomp library. This option can not be changed while tor is running.

    When the Sandbox is 1, the following options can not be changed when tor is running: Address, ConnLimit, CookieAuthFile, DirPortFrontPage, ExtORPortCookieAuthFile, Logs, ServerDNSResolvConfFile, ClientOnionAuthDir (and any files in it won’t reload on HUP signal).
    Launching new Onion Services through the control port is not supported with current syscall sandboxing implementation.
    Tor must remain in client or server mode (some changes to ClientOnly and ORPort are not allowed). Currently, if Sandbox is 1, ControlPort command "GETINFO address" will not work.
    (Default: 0) 
  ]#
  base_settings &= "\n# Use sandbox\nSandbox " & intToStr(int(use_sandbox)) & "\n"


proc maketorrc_set_safe_sock(base_settings: var string, use_safe_sock: bool) =
  #[
    Generate option "SafeSocks" for Torrc. Parse from bool:
      false = 0
      true = 1
    When this option is enabled, Tor will reject application connections that use unsafe variants of the socks protocol — ones that only provide an IP address, meaning the application is doing a DNS resolve first. Specifically, these are socks4 and socks5 when not doing remote DNS. (Default: 0) 
  ]#
  base_settings &= "\n# Use SafeSocks\nSafeSocks " & intToStr(int(use_safe_sock)) & "\n"


proc maketorrc_set_plaintext_port(base_settings: var string, reject_port: bool, list_port: string) =
  #[
    Generate setting for WarnPlaintextPorts or RejectPlaintextPorts depends on "reject_port"
    list_port is a string variable follows format port,port
  ]#
  if reject_port:
    base_settings &= "\n# Set RejectPlaintextPorts\nRejectPlaintextPorts " & list_port & "\n"
  else:
    base_settings &= "\n# Set WarnPlaintextPorts\nWarnPlaintextPorts " & list_port & "\n"


proc maketorrc_set_proxy_settings(base_settings: var string, http_proxy_addr, http_proxy_cred: string, is_https: bool) =
  #[
    Generate settings for HTTPSProxy, HTTPSProxyAuthenticator and HTTPProxy
    - Enable when addr is provided
    - Only use cred for HTTPS
  ]#
  if isEmptyOrWhitespace(http_proxy_addr):
    return

  base_settings &= "\n# Set proxy addr\n"

  if is_https:
    base_settings &= "HTTPSProxy " & http_proxy_addr & "\n"
    if not isEmptyOrWhitespace(http_proxy_cred):
      base_settings &= "HTTPSProxyAuthenticator " & http_proxy_cred & "\n"
  else:
    base_settings &= "HTTPProxy " & http_proxy_addr & "\n"


proc maketorrc_settings_from_config_file(settings: var string) =
  let
    config = maketorrc_get_system_config()

  settings.maketorrc_set_sandbox(config.use_sandbox)
  settings.maketorrc_set_bridge(config.auto_bridge, config.bridge_address)
  settings.maketorrc_set_safe_sock(config.use_safe_sock)
  settings.maketorrc_set_plaintext_port(config.reject_plain_port, config.list_plain_ports)
  settings.maketorrc_set_proxy_settings(config.http_proxy_address, config.http_proxy_credential, config.proxy_is_https)


proc maketorrc_create_tor_backup() =
  try:
    copyFile(path_torrc_config, path_torrc_backup)
  except:
    print_error("Failed to create new torrc backup")


proc maketorrc_write_setting(setting: string) =
  try:
    writeFile(path_torrc_config, setting)
  except:
    print_error("Failed to create new torrc")


proc maketorrc_generate_torrc_config*() =
  var
    setting = config_torrc_base

  maketorrc_create_tor_backup()
  maketorrc_settings_from_config_file(setting)
  maketorrc_write_setting(setting)


proc makettorc_restore_torrc_backup*() =
  try:
    moveFile(path_torrc_backup, path_torrc_config)
  except:
    print_error("Failed to restore torrc")
