#!/usr/bin/env python
# Copyright (C) 2013 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 gevent
import gevent.monkey

gevent.monkey.patch_all()

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

from gevent import Greenlet

import heralding
import heralding.honeypot
import heralding.reporting.reporting_relay
import yaml

logger = logging.getLogger()


def on_unhandled_greenlet_exception(dead_greenlet):
    logger.error('Stopping because %s died: %s', dead_greenlet, dead_greenlet.exception)
    sys.exit(1)


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.addFilter(LogFilter())
    console_log.setLevel(loglevel)
    console_log.setFormatter(default_formatter)
    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)


class LogFilter(logging.Filter):
    def filter(self, rec):
        if rec.name == 'paramiko.transport':
            return False
        else:
            return True


def drop_privileges(uid_name='nobody', gid_name='nogroup'):
    if os.getuid() != 0:
        return

    wanted_uid = pwd.getpwnam(uid_name)[2]
    # special handling for os x. (getgrname has trouble with gid below 0)
    if platform.mac_ver()[0] and platform.mac_ver()[0] < float('10.9'):
        wanted_gid = -2
    else:
        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__':
    parser = ArgumentParser(description='Heralding')

    group = parser.add_argument_group()
    group.add_argument('-se', '--server', action='store_true', help='Starts heralding in server mode.')

    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')
    args = parser.parse_args()

    setup_logging(args.logfile, args.verbose)

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

    config_file = 'heralding.yml'
    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))

    with open(config_file, "r") as _file:
        config = yaml.safe_load(_file.read())

    # startup reporting relay
    reporting_relay_greenlet = heralding.reporting.reporting_relay.ReportingRelay()
    reporting_relay_greenlet.link_exception(on_unhandled_greenlet_exception)
    reporting_relay_greenlet.start()

    honeypot = heralding.honeypot.Honeypot(config)
    drone_greenlet = Greenlet.spawn(honeypot.start)
    honeypot.blokUntilReadyForDroppingPrivs()
    drop_privileges()

    gevent.wait([drone_greenlet, ])
