text: # Copyright (c) 2016, Neil Booth
  #
  # All rights reserved.
  #
  # See the file "LICENCE" for information about the copyright
  # and warranty status of this software.
  
  '''Class for handling environment configuration and defaults.'''
  
  
  import re
  import resource
  from collections import namedtuple
  from ipaddress import ip_address
  
  from lib.coins import Coin
  from lib.env_base import EnvBase
  import lib.util as lib_util
  
  
  NetIdentity = namedtuple('NetIdentity', 'host tcp_port ssl_port nick_suffix')
  
  
  class Env(EnvBase):
      '''Wraps environment configuration.'''
  
      # Peer discovery
      PD_OFF, PD_SELF, PD_ON = range(3)
  
      def __init__(self):
          super().__init__()
          self.obsolete(['UTXO_MB', 'HIST_MB', 'NETWORK'])
          self.db_dir = self.required('DB_DIRECTORY')
          self.db_engine = self.default('DB_ENGINE', 'leveldb')
          self.daemon_url = self.required('DAEMON_URL')
          coin_name = self.required('COIN').strip()
          network = self.default('NET', 'mainnet').strip()
          self.coin = Coin.lookup_coin_class(coin_name, network)
          self.cache_MB = self.integer('CACHE_MB', 1200)
          self.host = self.default('HOST', 'localhost')
          self.reorg_limit = self.integer('REORG_LIMIT', self.coin.REORG_LIMIT)
          # Server stuff
          self.tcp_port = self.integer('TCP_PORT', None)
          self.ssl_port = self.integer('SSL_PORT', None)
          if self.ssl_port:
              self.ssl_certfile = self.required('SSL_CERTFILE')
              self.ssl_keyfile = self.required('SSL_KEYFILE')
          self.rpc_port = self.integer('RPC_PORT', 8000)
          self.max_subscriptions = self.integer('MAX_SUBSCRIPTIONS', 10000)
          self.banner_file = self.default('BANNER_FILE', None)
          self.tor_banner_file = self.default('TOR_BANNER_FILE',
                                              self.banner_file)
          self.anon_logs = self.boolean('ANON_LOGS', False)
          self.log_sessions = self.integer('LOG_SESSIONS', 3600)
          # Peer discovery
          self.peer_discovery = self.peer_discovery_enum()
          self.peer_announce = self.boolean('PEER_ANNOUNCE', True)
          self.force_proxy = self.boolean('FORCE_PROXY', False)
          self.tor_proxy_host = self.default('TOR_PROXY_HOST', 'localhost')
          self.tor_proxy_port = self.integer('TOR_PROXY_PORT', None)
          # The electrum client takes the empty string as unspecified
          self.donation_address = self.default('DONATION_ADDRESS', '')
          # Server limits to help prevent DoS
          self.max_send = self.integer('MAX_SEND', 1000000)
          self.max_subs = self.integer('MAX_SUBS', 250000)
          self.max_sessions = self.sane_max_sessions()
          self.max_session_subs = self.integer('MAX_SESSION_SUBS', 50000)
          self.bandwidth_limit = self.integer('BANDWIDTH_LIMIT', 2000000)
          self.session_timeout = self.integer('SESSION_TIMEOUT', 600)
          self.drop_client = self.custom("DROP_CLIENT", None, re.compile)
  
          # Identities
          clearnet_identity = self.clearnet_identity()
          tor_identity = self.tor_identity(clearnet_identity)
          self.identities = [identity
                             for identity in (clearnet_identity, tor_identity)
                             if identity is not None]
  
      def sane_max_sessions(self):
          '''Return the maximum number of sessions to permit.  Normally this
          is MAX_SESSIONS.  However, to prevent open file exhaustion, ajdust
          downwards if running with a small open file rlimit.'''
          env_value = self.integer('MAX_SESSIONS', 1000)
          nofile_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
          # We give the DB 250 files; allow ElectrumX 100 for itself
          value = max(0, min(env_value, nofile_limit - 350))
          if value < env_value:
              self.logger.warning('lowered maximum sessions from {:,d} to {:,d} '
                                  'because your open file limit is {:,d}'
                                  .format(env_value, value, nofile_limit))
          return value
  
      def clearnet_identity(self):
          host = self.default('REPORT_HOST', None)
          if host is None:
              return None
          try:
              ip = ip_address(host)
          except ValueError:
              bad = (not lib_util.is_valid_hostname(host)
                     or host.lower() == 'localhost')
          else:
              bad = (ip.is_multicast or ip.is_unspecified
                     or (ip.is_private and self.peer_announce))
          if bad:
              raise self.Error('"{}" is not a valid REPORT_HOST'.format(host))
          tcp_port = self.integer('REPORT_TCP_PORT', self.tcp_port) or None
          ssl_port = self.integer('REPORT_SSL_PORT', self.ssl_port) or None
          if tcp_port == ssl_port:
              raise self.Error('REPORT_TCP_PORT and REPORT_SSL_PORT '
                               'both resolve to {}'.format(tcp_port))
          return NetIdentity(
              host,
              tcp_port,
              ssl_port,
              ''
          )
  
      def tor_identity(self, clearnet):
          host = self.default('REPORT_HOST_TOR', None)
          if host is None:
              return None
          if not host.endswith('.onion'):
              raise self.Error('tor host "{}" must end with ".onion"'
                               .format(host))
  
          def port(port_kind):
              '''Returns the clearnet identity port, if any and not zero,
              otherwise the listening port.'''
              result = 0
              if clearnet:
                  result = getattr(clearnet, port_kind)
              return result or getattr(self, port_kind)
  
          tcp_port = self.integer('REPORT_TCP_PORT_TOR', port('tcp_port')) or None
          ssl_port = self.integer('REPORT_SSL_PORT_TOR', port('ssl_port')) or None
          if tcp_port == ssl_port:
              raise self.Error('REPORT_TCP_PORT_TOR and REPORT_SSL_PORT_TOR '
                               'both resolve to {}'.format(tcp_port))
  
          return NetIdentity(
              host,
              tcp_port,
              ssl_port,
              '_tor',
          )
  
      def hosts_dict(self):
          return {identity.host: {'tcp_port': identity.tcp_port,
                                  'ssl_port': identity.ssl_port}
                  for identity in self.identities}
  
      def peer_discovery_enum(self):
          pd = self.default('PEER_DISCOVERY', 'on').strip().lower()
          if pd in ('off', ''):
              return self.PD_OFF
          elif pd == 'self':
              return self.PD_SELF
          else:
              return self.PD_ON
  