#!/usr/bin/env python


"""
Copyright (c) 2016, Are Hansen - Honeypot Development

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.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND AN
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 COPYRIGHT HOLDER 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.
"""


__author__ = 'Are Hansen'
__date__ = '2016, January 1'
__version__ = '0.0.6'


import argparse
import os
import operator
import sys
import uuid
from collections import Counter
from bzlib.files import Read
from bzlib.files import Write
from bzlib.geoloc import Geo
from bzlib.sendto import Slack


def parse_args():
    parser = argparse.ArgumentParser(
             description='Honeypot stats - v{0} - {1}'.format(__version__ , __author__)
             )

    dly = parser.add_argument_group('- HonSSH data')
    dlyd = '/opt/honssh/logs'
    enum = 100
    dly.add_argument(
                    '-D',
                    dest='daily',
                    help='Show data sets by order of occurence.',
                    choices=['auth', 'conn', 'ip'],
                    required=True
                    )
    dly.add_argument(
                    '-DL',
                    dest='dpath',
                    help='Path to daily logs (default: {0})'.format(dlyd),
                    default=dlyd
                    )
    dly.add_argument(
                    '-NE',
                    dest='enum',
                    help='Number of entries to return (default: {0})'.format(enum),
                    default=enum
                    )

    psh = parser.add_argument_group('- Push results')
    psh.add_argument(
                    '-Ps',
                    dest='slack',
                    help='Post results to Slack',
                    action='store_true'
                    )

    geo = parser.add_argument_group('- Geo location lookup')
    mmdb = '/etc/geoip2/GeoLite2-City.mmdb'
    geo = geo.add_argument(
                          '-DB',
                          dest='geodb',
                          help='Path to GeoLite2.mmdb (default: {0})'.format(mmdb),
                          default=mmdb
                          )

    args = parser.parse_args()
    return args


def data_stdout(data_dict, metric, enum, mmdb):
    if metric == 'auth': 
        print '{0:>6}  {1}'.format('Count', 'Credentials')
        print '------  -----------'
        for creds, count in sorted(data_dict.items(), key=operator.itemgetter(1), reverse=True)[0:enum]:
            print '{0:>6}  {1}'.format(count, creds)
        print ''

    if metric == 'conn': 
        print '{0:>8} {1:>6}'.format('Attempts', 'Date')
        print '--------   --------'
        for day, count in sorted(data_dict.items(), reverse=True)[0:enum]:
            date = day.split('/')[-1]
            print '{0:>8} {1:>10}'.format(count, date)
        print ''

    if metric == 'ip':
        geo = Geo(mmdb)

        print '{0:>6}  {1:>15}  {2}'.format('Count', 'IPv4 address', 'Country')
        print '------  ---------------  ------------------'
        for ipv4, count in sorted(data_dict.items(), key=operator.itemgetter(1), reverse=True)[0:enum]:
            print '{0:>6}  {1:>15}  {2}'.format(count, ipv4, geo.country(ipv4))
        print ''


def data_slack(data_dict, metric, enum, mmdb):
    tmp_file = '/tmp/auth_info_{0}'.format(uuid.uuid4().hex)

    if metric == 'auth':
        Write().aline(tmp_file, '{0:>6}  {1}'.format('Count', 'Credentials'))
        Write().aline(tmp_file, '------  -----------')
        for creds, count in sorted(data_dict.items(), key=operator.itemgetter(1), reverse=True)[0:enum]:
            Write().aline(tmp_file, '{0:>6}  {1}'.format(count, creds))

    if metric == 'conn':
        Write().aline(tmp_file, '{0:>8} {1:>6}'.format('Attempts', 'Date'))
        Write().aline(tmp_file, '--------   --------')
        for day, count in sorted(data_dict.items(), reverse=True):
            date = day.split('/')[-1]
            Write().aline(tmp_file, '{0:>8} {1:>10}'.format(count, date))

    if metric == 'ip':
        geo = Geo(mmdb)
        Write().aline(tmp_file, '{0:>6}  {1:>15}  {2}'.format('Count', 'IPv4 Address', 'Country'))
        Write().aline(tmp_file, '------  ---------------  ------------------')
        for ipv4, count in sorted(data_dict.items(), key=operator.itemgetter(1), reverse=True)[0:enum]:
            Write().aline(tmp_file, '{0:>6}  {1:>15}  {2}'.format(count, ipv4, geo.country(ipv4)))

    return tmp_file



def daily_connects(glob_patt):
    day_total = Read().flinesg(glob_patt)
    return day_total


def daily_creds(glob_patt):
    all_creds = Read().freadg(glob_patt)
    cred_data = []

    for cred in all_creds:
        if cred.endswith((',0\n',',1\n')):
            user = cred.split(',')[2]
            pwds = cred.split(',')[3]
            cred_data.append('{0}/{1}'.format(user, pwds))
    
    calc_cred = dict(Counter(cred_data))
    return calc_cred


def daily_ip(glob_patt):
    all_ips = Read().freadg(glob_patt)
    ip_data = []

    for ips in all_ips:
        ipv4 = ips.split(',')[1]
        ip_data.append('{0}'.format(ipv4).rstrip())

    ip_dict = dict(Counter(ip_data))
    return ip_dict 


def check_args(args):
    url = 'INSERT_WEB_HOOK_URL_HERE'

    if not os.path.exists(args.dpath):
        print 'PathCheckError: \"{0}\" was not found'.format(args.dpath)
        sys.exit(1)

    lpath = '{0}/20*'.format(args.dpath)

    if not os.path.isfile(args.geodb):
        print 'FileNotFound: {0}'.format(args.geodb)
        sys.exit(1)

    if args.daily == 'auth':
        result = daily_creds(lpath)
        if args.slack:
            payload = data_slack(result, args.daily, int(args.enum), args.geodb)
            Slack(url).file_post(payload)
        if not args.slack:
            data_stdout(result, args.daily, int(args.enum), args.geodb)

    if args.daily == 'conn': 
        result = daily_connects(lpath)
        if args.slack:
            payload = data_slack(result, args.daily, int(args.enum), args.geodb)
            Slack(url).file_post(payload)
        if not args.slack:
            data_stdout(result, args.daily, int(args.enum), args.geodb)

    if args.daily == 'ip':
        result = daily_ip(lpath)
        if args.slack:
            payload = data_slack(result, args.daily, int(args.enum), args.geodb)
            Slack(url).file_post(payload)
        if not args.slack:
            data_stdout(result, args.daily, int(args.enum), args.geodb)


def main():
    args = parse_args()
    check_args(args)


if __name__ == '__main__':
    main()
