#!/usr/bin/env python3
# Copyright (C) 2017 Johnny Vestergaard <jkv@unixcluster.dk>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import asyncio
import logging
import logging.handlers
import os, pwd, grp, yaml, sys
from argparse import ArgumentParser

import heralding
import heralding.honeypot
import heralding.reporting.reporting_relay
from heralding.misc.common import on_unhandled_task_exception

logger = logging.getLogger()


def setup_logging(logfile, verbose):
  """
        Sets up logging to the logfiles/console.
    :param logfile: Path of the file to write logs to.
    :param verbose: If True, enables verbose logging.
    """
  root_logger = logging.getLogger()

  default_formatter = logging.Formatter('%(asctime)-15s (%(name)s) %(message)s')

  if verbose:
    loglevel = logging.DEBUG
  else:
    loglevel = logging.INFO
  root_logger.setLevel(loglevel)

  console_log = logging.StreamHandler()
  console_log.setFormatter(default_formatter)
  console_log.setLevel(loglevel)
  root_logger.addHandler(console_log)

  if logfile in ('/dev/log', '/dev/syslog', '/var/run/syslog', '/var/run/log'):
    file_log = logging.handlers.SysLogHandler(
        address=logfile, facility='local1')
    syslog_formatter = logging.Formatter('heralding[%(process)d]: %(message)s')
    file_log.setFormatter(syslog_formatter)
  else:
    file_log = logging.FileHandler(logfile)
    file_log.setFormatter(default_formatter)
  file_log.setLevel(loglevel)
  root_logger.addHandler(file_log)

  # ensure the filer is applied to all handlers
  for handler in root_logger.handlers:
    handler.addFilter(LogFilter())


class LogFilter(logging.Filter):

  def filter(self, rec):
    """Filtering internal logs of aiosmtpd, asyncssh"""
    if rec.name == "asyncssh":
      if logging.getLogger().level == logging.DEBUG:
        return True
      if rec.levelno <= 20:
        return False
      else:
        return True

    if rec.name == 'mail.log':
      return False
    else:
      return True


def break_if_python_not_supported():
  if sys.version_info[0:2] < (3, 6):
    raise Exception(
        "Wrong python version! Your Python interpreter must be 3.6.0 or above!")


def drop_privileges(uid_name='nobody', gid_name='nogroup'):
  """Drops current privileges to the privileges of selected user."""
  if os.getuid() != 0:
    return

  wanted_uid = pwd.getpwnam(uid_name)[2]
  wanted_gid = grp.getgrnam(gid_name)[2]

  os.setgid(wanted_gid)
  os.setuid(wanted_uid)

  new_uid_name = pwd.getpwuid(os.getuid())[0]
  new_gid_name = grp.getgrgid(os.getgid())[0]

  logger.info("Privileges dropped, running as {0}/{1}.".format(
      new_uid_name, new_gid_name))


if __name__ == '__main__':
  break_if_python_not_supported()
  parser = ArgumentParser(description='Heralding')

  parser.add_argument(
      '-v',
      '--verbose',
      action='store_true',
      default=False,
      help='Logs debug messages.')
  parser.add_argument(
      '-l',
      '--logfile',
      dest='logfile',
      default='heralding.log',
      help='Heralding log file')
  parser.add_argument(
      '-c',
      '--config',
      dest='config',
      default='heralding.yml',
      help='Heralding config file')
  args = parser.parse_args()

  setup_logging(args.logfile, args.verbose)

  logger.info('Initializing Heralding version {0}'.format(heralding.version))

  config_file = args.config
  if not os.path.isfile(config_file):
    package_directory = os.path.dirname(os.path.abspath(heralding.__file__))
    config_file = os.path.join(package_directory, config_file)
    logger.warning(
        'Using default config file: "{0}", if you want to customize values please '
        'copy this file to the current working directory'.format(config_file))

  try:
    with open(config_file, "r") as _file:
      config = yaml.safe_load(_file.read())
  except Exception as ex:
    error_message = "Error while reading config file {0}: {1}.".format(
        config_file, ex)
    logger.error(error_message)
    sys.exit(error_message)

  loop = asyncio.get_event_loop()
  # startup reporting relay
  reporting_relay = heralding.reporting.reporting_relay.ReportingRelay()
  reporting_relay_task = loop.run_in_executor(None, reporting_relay.start)
  reporting_relay_task.add_done_callback(on_unhandled_task_exception)

  honeypot = heralding.honeypot.Honeypot(config, loop)
  try:
    honeypot.start()
  except Exception as ex:
    logger.exception(ex)
    honeypot.stop()
    reporting_relay.stop()
    # We give reporting_relay a chance to be finished.
    loop.run_until_complete(asyncio.sleep(0.5))
    loop.close()
    sys.exit(ex)

  drop_privileges()

  try:
    loop.run_forever()
  except KeyboardInterrupt:
    pass
  finally:
    honeypot.stop()
    reporting_relay.stop()
    # We give reporting_relay a chance to be finished.
    loop.run_until_complete(asyncio.sleep(0.5))
    loop.close()
