User Tools

Site Tools


programming:python:smtprelaytest

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

programming:python:smtprelaytest [2008/01/07 17:56] (current)
crustymonkey created
Line 1: Line 1:
 +====== SMTPRelayTest ======
  
 +===== What is it? =====
 +''​smtprelaytest.py''​ is a simple script to be run from the command-line that will test a mail server to see if it is an open relay. ​ It will output color coded responses from the mail server as it just simply tests whether or not you can relay mail given the specified HELO name, sender and recipient. ​ It also supports this test using STARTTLS. ​ This is basically just a shortcut to using [[wp>​netcat]] or [[wp>​telnet]].
 +
 +===== The Script =====
 +You can either download it using {{programming:​python:​smtprelaytest.py.gz|this link}} or copy it from below.
 +<code python>
 +#​!/​usr/​bin/​env python
 +
 +__cvsversion__ = '$Id: smtprelaytest.py,​v 1.3 2008/01/07 17:41:23 jay Exp $'
 +__author__ = 'Jay Deiman'​
 +
 +import smtplib , os , sys , getopt , socket , re
 +
 +class TerminalController:​
 +    """​
 +    Author of the TerminalController class: Edward Loper
 +    Code copied from: http://​aspn.activestate.com/​ASPN/​Cookbook/​Python/​Recipe/​475116
 +    ​
 +    A class that can be used to portably generate formatted output to
 +    a terminal.  ​
 +    ​
 +    `TerminalController` defines a set of instance variables whose
 +    values are initialized to the control sequence necessary to
 +    perform a given action. ​ These can be simply included in normal
 +    output to the terminal:
 +
 +        >>>​ term = TerminalController()
 +        >>>​ print 'This is '​+term.GREEN+'​green'​+term.NORMAL
 +
 +    Alternatively,​ the `render()` method can used, which replaces
 +    '​${action}'​ with the string required to perform '​action':​
 +
 +        >>>​ term = TerminalController()
 +        >>>​ print term.render('​This is ${GREEN}green${NORMAL}'​)
 +
 +    If the terminal doesn'​t support a given action, then the value of
 +    the corresponding instance variable will be set to ''​. ​ As a
 +    result, the above code will still work on terminals that do not
 +    support color, except that their output will not be colored.
 +    Also, this means that you can test whether the terminal supports a
 +    given action by simply testing the truth value of the
 +    corresponding instance variable:
 +
 +        >>>​ term = TerminalController()
 +        >>>​ if term.CLEAR_SCREEN:​
 +        ...     print 'This terminal supports clearning the screen.'​
 +
 +    Finally, if the width and height of the terminal are known, then
 +    they will be stored in the `COLS` and `LINES` attributes.
 +    """​
 +    # Cursor movement:
 +    BOL = '' ​            #: Move the cursor to the beginning of the line
 +    UP = '' ​             #: Move the cursor up one line
 +    DOWN = '' ​           #: Move the cursor down one line
 +    LEFT = '' ​           #: Move the cursor left one char
 +    RIGHT = '' ​          #: Move the cursor right one char
 +
 +    # Deletion:
 +    CLEAR_SCREEN = '' ​   #: Clear the screen and move to home position
 +    CLEAR_EOL = '' ​      #: Clear to the end of the line.
 +    CLEAR_BOL = '' ​      #: Clear to the beginning of the line.
 +    CLEAR_EOS = '' ​      #: Clear to the end of the screen
 +
 +    # Output modes:
 +    BOLD = '' ​           #: Turn on bold mode
 +    BLINK = '' ​          #: Turn on blink mode
 +    DIM = '' ​            #: Turn on half-bright mode
 +    REVERSE = '' ​        #: Turn on reverse-video mode
 +    NORMAL = '' ​         #: Turn off all modes
 +
 +    # Cursor display:
 +    HIDE_CURSOR = '' ​    #: Make the cursor invisible
 +    SHOW_CURSOR = '' ​    #: Make the cursor visible
 +
 +    # Terminal size:
 +    COLS = None          #: Width of the terminal (None for unknown)
 +    LINES = None         #: Height of the terminal (None for unknown)
 +
 +    # Foreground colors:
 +    BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''​
 +    ​
 +    # Background colors:
 +    BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''​
 +    BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''​
 +    ​
 +    _STRING_CAPABILITIES = """​
 +    BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
 +    CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
 +    BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
 +    HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm"""​.split()
 +    _COLORS = """​BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE"""​.split()
 +    _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE"​.split()
 +
 +    def __init__(self,​ term_stream=sys.stdout):​
 +        """​
 +        Create a `TerminalController` and initialize its attributes
 +        with appropriate values for the current terminal.
 +        `term_stream` is the stream that will be used for terminal
 +        output; if this stream is not a tty, then the terminal is
 +        assumed to be a dumb terminal (i.e., have no capabilities).
 +        """​
 +        # Curses isn't available on all platforms
 +        try: import curses
 +        except: return
 +
 +        # If the stream isn't a tty, then assume it has no capabilities.
 +        if not term_stream.isatty():​ return
 +
 +        # Check the terminal type.  If we fail, then assume that the
 +        # terminal has no capabilities.
 +        try: curses.setupterm()
 +        except: return
 +
 +        # Look up numeric capabilities.
 +        self.COLS = curses.tigetnum('​cols'​)
 +        self.LINES = curses.tigetnum('​lines'​)
 +        ​
 +        # Look up string capabilities.
 +        for capability in self._STRING_CAPABILITIES:​
 +            (attrib, cap_name) = capability.split('​='​)
 +            setattr(self,​ attrib, self._tigetstr(cap_name) or ''​)
 +
 +        # Colors
 +        set_fg = self._tigetstr('​setf'​)
 +        if set_fg:
 +            for i,color in zip(range(len(self._COLORS)),​ self._COLORS):​
 +                setattr(self,​ color, curses.tparm(set_fg,​ i) or ''​)
 +        set_fg_ansi = self._tigetstr('​setaf'​)
 +        if set_fg_ansi:​
 +            for i,color in zip(range(len(self._ANSICOLORS)),​ self._ANSICOLORS):​
 +                setattr(self,​ color, curses.tparm(set_fg_ansi,​ i) or ''​)
 +        set_bg = self._tigetstr('​setb'​)
 +        if set_bg:
 +            for i,color in zip(range(len(self._COLORS)),​ self._COLORS):​
 +                setattr(self,​ '​BG_'​+color,​ curses.tparm(set_bg,​ i) or ''​)
 +        set_bg_ansi = self._tigetstr('​setab'​)
 +        if set_bg_ansi:​
 +            for i,color in zip(range(len(self._ANSICOLORS)),​ self._ANSICOLORS):​
 +                setattr(self,​ '​BG_'​+color,​ curses.tparm(set_bg_ansi,​ i) or ''​)
 +
 +    def _tigetstr(self,​ cap_name):
 +        # String capabilities can include "​delays"​ of the form "​$<​2>"​.
 +        # For any modern terminal, we should be able to just ignore
 +        # these, so strip them out.
 +        import curses
 +        cap = curses.tigetstr(cap_name) or ''​
 +        return re.sub(r'​\$<​\d+>​[/​*]?',​ '',​ cap)
 +
 +    def render(self,​ template):
 +        """​
 +        Replace each $-substitutions in the given template string with
 +        the corresponding terminal control string (if it's defined) or
 +        ''​ (if it's not).
 +        """​
 +        return re.sub(r'​\$\$|\${\w+}',​ self._render_sub,​ template)
 +
 +    def _render_sub(self,​ match):
 +        s = match.group()
 +        if s == '​$$':​ return s
 +        else: return getattr(self,​ s[2:-1])
 +
 +# Functions
 +def getHostName():​
 +    fqdn = re.compile(r'​^(?:​[^\s\.]+\.){1,​}(?:​[^\s\.]+)$'​)
 +    res = socket.gethostbyaddr('​127.0.0.1'​)
 +    if fqdn.match(res[0]):​
 +        return res[0]
 +    for name in res[1]:
 +        if fqdn.match(name):​
 +            return name
 +
 +def usage(exitCode=0):​
 +    print '%s -r <remote host> [-p <​port>​] ' % os.path.basename(sys.argv[0]) + \
 +            '-c <​recipient>​ [-f <send from>] [-t <helo name>​]'​
 +    print """​
 +    -h,​--help ​           Help, what you are looking at
 +    -s,​--usetls ​         Use TLS for the connection
 +    -r,​--remhost= ​       Remote hostname or IP
 +    -p,​--port= ​          ​Remote host port
 +    -t,​--helo= ​          HELO hostname. ​ An attempt will be made to determine
 +                         the hostname if not supplied
 +    -c,​--recip= ​         The recipient email address to use
 +    -f,​--from= ​          The email address that the request should come
 +                         ​from. ​ "​test@qwest.net"​ will be used by default
 +    """​
 +    sys.exit(exitCode)
 +    ​
 +def gAndR(code):​
 +    code = int(code)
 +    if code >= 200 and code < 300:
 +        return "​${GREEN}"​
 +    else:
 +        return "​${RED}"​
 +    ​
 +# Config vars
 +remoteHost = ''​
 +remotePort = 25
 +heloHost = getHostName()
 +mRecip = ''​
 +mFrom = '​test@qwest.net'​
 +useTls = False
 +
 +# Get the command line opts
 +shortOpts = '​hsr:​p:​t:​c:​f:'​
 +longOpts = ['​help'​ , '​usetls'​ , '​remhost='​ , '​port='​ , '​helo='​ , 
 +            '​recip='​ , '​from='​]
 +try:
 +    optList , junk = getopt.getopt(sys.argv[1:​] , shortOpts , longOpts)
 +except getopt.GetoptError , e:
 +    print e
 +    usage(1)
 +for opt , val in optList:
 +    if opt in ('​-h'​ , '​--help'​):​
 +        usage()
 +    elif opt in ('​-s'​ , '​--usetls'​):​
 +        useTls = True
 +    elif opt in ('​-r'​ , '​--remhost'​):​
 +        remoteHost = val
 +    elif opt in ('​-p'​ , '​--port'​):​
 +        remotePort = int(val)
 +    elif opt in ('​-t'​ , '​--helo'​):​
 +        heloHost = val
 +    elif opt in ('​-c'​ , '​--recip'​):​
 +        mRecip = val
 +    elif opt in ('​-f'​ , '​--from'​):​
 +        mFrom = val
 +# If we don't have a remoteHost or recipient, usage and exit
 +if not remoteHost or not mRecip:
 +    usage(1)
 +
 +# Now, establish the connection and try the relay
 +t = TerminalController()
 +s = smtplib.SMTP()
 +print '​Connecting to %s:%d' % (remoteHost , remotePort)
 +resp = s.connect(remoteHost , remotePort)
 +print t.render('​%s%d %s${NORMAL}'​ % (gAndR(resp[0]) , resp[0] , resp[1]))
 +print '​HELOing as %s' % heloHost
 +resp = s.helo(heloHost)
 +print t.render('​%s%d %s${NORMAL}'​ % (gAndR(resp[0]) , resp[0] , resp[1]))
 +if useTls:
 +    print '​Starting a TLS connection'​
 +    resp = s.starttls()
 +    print t.render('​%s%d %s${NORMAL}'​ % (gAndR(resp[0]) , resp[0] , resp[1]))
 +mailFrom = 'MAIL FROM: <​%s>​\r\n'​ % mFrom
 +print '​Sending:​ %s' % mailFrom.strip()
 +s.send(mailFrom)
 +resp = s.getreply()
 +print t.render('​%s%d %s${NORMAL}'​ % (gAndR(resp[0]) , resp[0] , resp[1]))
 +mailTo = 'RCPT TO: <​%s>​\r\n'​ % mRecip
 +print '​Sending:​ %s' % mailTo.strip()
 +s.send(mailTo)
 +resp = s.getreply()
 +print t.render('​%s%d %s${NORMAL}'​ % (gAndR(resp[0]) , resp[0] , resp[1]))
 +print '​Sending:​ QUIT'
 +s.send('​QUIT\r\n'​)
 +resp = s.getreply()
 +print t.render('​%s%d %s${NORMAL}'​ % (gAndR(resp[0]) , resp[0] , resp[1]))
 +s.close()
 +</​code>​
 +
 +===== Usage =====
 +Using ''​smtprelaytest.py -h''​ on the command line produces the following:
 +<​code>​
 +$ ./​smtprelaytest.py -h
 +smtprelaytest.py -r <remote host> [-p <​port>​] -c <​recipient>​ [-f <send from>] [-t <helo name>]
 +
 +    -h,​--help ​           Help, what you are looking at
 +    -s,​--usetls ​         Use TLS for the connection
 +    -r,​--remhost= ​       Remote hostname or IP
 +    -p,​--port= ​          ​Remote host port
 +    -t,​--helo= ​          HELO hostname. ​ An attempt will be made to determine
 +                         the hostname if not supplied
 +    -c,​--recip= ​         The recipient email address to use
 +    -f,​--from= ​          The email address that the request should come
 +                         ​from. ​ "​test@qwest.net"​ will be used by default
 +</​code>​
 +
 +A typical lookup, with TLS support would look like this:
 +<​code>​
 +# ./​smtprelaytest.py -s -r 127.0.0.1 -f admin@splitstreams.com -c admin@splitstreams.com
 +Connecting to 127.0.0.1:​25
 +220 mail.splitstreams.com ESMTP Postfix
 +HELOing as localhost.splitstreams.com
 +250 mail.splitstreams.com
 +Starting a TLS connection
 +220 2.0.0 Ready to start TLS
 +Sending: MAIL FROM: <​admin@splitstreams.com>​
 +250 2.1.0 Ok
 +Sending: RCPT TO: <​admin@splitstreams.com>​
 +250 2.1.5 Ok
 +Sending: QUIT
 +221 2.0.0 Bye
 +</​code>​
programming/python/smtprelaytest.txt · Last modified: 2008/01/07 17:56 by crustymonkey