#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Matteo Castellini <self {at} mcastellini [dot] net>
#
# 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 2 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 sys
import socket
from optparse import OptionParser
from twisted.internet import reactor, threads, defer
from ConfigParser import RawConfigParser
from os.path import expanduser

defaults = {'server': 'dnsbl.httpbl.org', 'key': '', 'verbose': 'False'}
conf = RawConfigParser(defaults)

usage = "%prog [OPTIONS] ip_address"
version = "%prog 0.2"
parser = OptionParser(usage=usage, version=version, prog="pyhttpbl")
parser.add_option("-c", "--config", metavar="FILE", dest="config_file",
        default="~/.pyhttpbl", help="load configuration from FILE (default: "
        "%default)")
parser.add_option("-s", "--server", metavar="HOST", dest="server", 
        help="set http:BL server hostname")
parser.add_option("-k", "--key", metavar="CODE", dest="key", 
        help="set your http:BL Access Key")
parser.add_option("-l", "--list", metavar="FILE", dest="list", 
        help="check multiple IP addresses from a list")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
        help="verbose output")
(opts, args) = parser.parse_args()
if (not conf.read(expanduser(opts.config_file)) and 
        opts.config_file != "~/.pyhttpbl"):
    parser.error("Configuration file '%s' not found." % opts.config_file)
if not opts.server:
    opts.server = conf.get('main', 'server')
if not opts.verbose:
    opts.verbose = conf.getboolean('main', 'verbose')
if not opts.key and not conf.has_option('main', 'key'):
    parser.error("You must supply a http:BL Access Key (option: -k CODE).")
elif not opts.key:
    opts.key = conf.get('main', 'key')

def check_ip(ip_addr):
    rip = '.'.join(ip_addr.split('.')[::-1])
    try:
        score = socket.gethostbyname(opts.key + '.' + rip + '.' + opts.server)
    except socket.gaierror:
        score = ip_addr
    return score

def check_ip_list(ip_addr_list):
    def completed(result):
        reactor.stop()
        for s, r in result:
            if opts.verbose:
                print verbose_output(r[0], r[1])
            else:
                if s and r[0] != r[1]:
                    print r[0], r[1]
    stream = open(ip_addr_list, 'r')
    l = [threads.deferToThread(lambda x: (x, check_ip(x)), *(ip.strip(), ))
            for ip in stream.readlines()]
    stream.close()
    dl = defer.DeferredList(l, consumeErrors=True)
    dl.addCallback(completed)
    reactor.run()

def verbose_output(query, result):
    s = "Queried IP address: '%s'. Responded: '%s'\n" % (query, result)
    if query != result:
        octets = [int(octet) for octet in result.split('.')]
        types = ("search engine", 
                "suspicious",
                "harvester", 
                "suspicious and harvester", 
                "comment spammer",
                "suspicious and comment spammer", 
                "harvester and comment spammer",
                "suspicious, harvester and comment spammer",)
        if octets[0] != 127:
            s+= "An error occurred, the queried address may be malformed."
            return s
        s += "Address found in Http:BL database:\n" \
                "\tVistor identified as %s.\n" \
                "\t%i days since last activity." % (types[octets[3]], octets[1])
        if octets[3] != 0:
            s += "\n\t%i points of threat score." % octets[2]
    else:
        s+= "Address not found in Http:BL database."
    return s

if __name__ == '__main__':
    if opts.list:
        check_ip_list(opts.list)
    else:
        response = check_ip(args[0])
        if opts.verbose:
            print verbose_output(args[0], response)
        else:
            print response
    sys.exit(0)

