SHARE
TWEET

Untitled

a guest Apr 21st, 2018 212 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import asyncore
  2. import base64
  3.  
  4. from smtpd import SMTPServer, SMTPChannel, DEBUGSTREAM
  5.  
  6.  
  7. def decode_b64(data):
  8.     """Wrapper for b64decode, without having to struggle with bytestrings."""
  9.     byte_string = data.encode('utf-8')
  10.     decoded = base64.b64decode(byte_string)
  11.     return decoded.decode('utf-8')
  12.  
  13.  
  14. def encode_b64(data):
  15.     """Wrapper for b64encode, without having to struggle with bytestrings."""
  16.     byte_string = data.encode('utf-8')
  17.     encoded = base64.b64encode(byte_string)
  18.     return encoded.decode('utf-8')
  19.  
  20.  
  21. class FakeCredentialValidator(object):
  22.     def __init__(self, username, password, chanel):
  23.         self.username = username
  24.         self.password = password
  25.         self.chanel = chanel
  26.  
  27.     def validate(self):
  28.         if self.username == 'hasan@test.com' and self.password == 'gholami':
  29.             return True
  30.         return False
  31.  
  32.  
  33. class MySMTPChanel(SMTPChannel):
  34.     credential_validator = FakeCredentialValidator
  35.  
  36.     def __init__(self, server, conn, addr, *args, **kwargs):
  37.         super().__init__(server, conn, addr, *args, **kwargs)
  38.         self.username = None
  39.         self.password = None
  40.         self.authenticated = False
  41.         self.authenticating = False
  42.  
  43.     def smtp_AUTH(self, arg):
  44.         if 'PLAIN' in arg:
  45.             split_args = arg.split(' ')
  46.             # second arg is Base64-encoded string of blah\0username\0password
  47.             authbits = decode_b64(split_args[1]).split('\0')
  48.             self.username = authbits[1]
  49.             self.password = authbits[2]
  50.             if self.credential_validator(self.username, self.password, self).validate():
  51.                 self.authenticated = True
  52.                 self.push('235 Authentication successful.')
  53.             else:
  54.                 self.push('454 Temporary authentication failure.')
  55.                 self.close_when_done()
  56.  
  57.         elif 'LOGIN' in arg:
  58.             self.authenticating = True
  59.             split_args = arg.split(' ')
  60.  
  61.             # Some implmentations of 'LOGIN' seem to provide the username
  62.             # along with the 'LOGIN' stanza, hence both situations are
  63.             # handled.
  64.             if len(split_args) == 2:
  65.                 self.username = decode_b64(arg.split(' ')[1])
  66.                 self.push('334 ' + encode_b64('Username'))
  67.             else:
  68.                 self.push('334 ' + encode_b64('Username'))
  69.  
  70.         elif not self.username:
  71.             self.username = decode_b64(arg)
  72.             self.push('334 ' + encode_b64('Password'))
  73.         else:
  74.             self.authenticating = False
  75.             self.password = decode_b64(arg)
  76.             if self.credential_validator and self.credential_validator.validate(self.username, self.password):
  77.                 self.authenticated = True
  78.                 self.push('235 Authentication successful.')
  79.             else:
  80.                 self.push('454 Temporary authentication failure.')
  81.                 self.close_when_done()
  82.  
  83.     def smtp_EHLO(self, arg):
  84.         if not arg:
  85.             self.push('501 Syntax: EHLO hostname')
  86.             return
  87.         # See issue #21783 for a discussion of this behavior.
  88.         if self.seen_greeting:
  89.             self.push('503 Duplicate HELO/EHLO')
  90.             return
  91.         self._set_rset_state()
  92.         self.seen_greeting = arg
  93.         self.extended_smtp = True
  94.         self.push('250-%s' % self.fqdn)
  95.         self.push('250-AUTH LOGIN PLAIN')
  96.         if self.data_size_limit:
  97.             self.push('250-SIZE %s' % self.data_size_limit)
  98.             self.command_size_limits['MAIL'] += 26
  99.         if not self._decode_data:
  100.             self.push('250-8BITMIME')
  101.         if self.enable_SMTPUTF8:
  102.             self.push('250-SMTPUTF8')
  103.             self.command_size_limits['MAIL'] += 10
  104.         self.push('250 HELP')
  105.  
  106.     # SMTP and ESMTP commands
  107.     def smtp_HELO(self, arg):
  108.         print(arg)
  109.         if not arg:
  110.             self.push('501 Syntax: HELO hostname')
  111.             return
  112.         # See issue #21783 for a discussion of this behavior.
  113.         if self.seen_greeting:
  114.             self.push('503 Duplicate HELO/EHLO')
  115.             return
  116.         self._set_rset_state()
  117.         self.seen_greeting = arg
  118.         self.push('250 %s' % self.fqdn)
  119.  
  120.         # This code is taken directly from the underlying smtpd.SMTPChannel
  121.         # support for AUTH is added.
  122.  
  123.     def run_command_with_arg(self, command, arg):
  124.         method = getattr(self, 'smtp_' + command, None)
  125.         if not method:
  126.             self.push('500 Error: command "%s" not recognized' % command)
  127.             return
  128.  
  129.         # White list of operations that are allowed prior to AUTH.
  130.         if command not in ['AUTH', 'EHLO', 'HELO', 'NOOP', 'RSET', 'QUIT']:
  131.             if not self.authenticated:
  132.                 self.push('530 Authentication required')
  133.                 return
  134.  
  135.         method(arg)
  136.  
  137.     def found_terminator(self):
  138.         line = self._emptystring.join(self.received_lines)
  139.         print('Data:', repr(line), file=DEBUGSTREAM)
  140.         self.received_lines = []
  141.         if self.smtp_state == self.COMMAND:
  142.             sz, self.num_bytes = self.num_bytes, 0
  143.             if not line:
  144.                 self.push('500 Error: bad syntax')
  145.                 return
  146.             if not self._decode_data:
  147.                 line = str(line, 'utf-8')
  148.             i = line.find(' ')
  149.  
  150.             if self.authenticating:
  151.                 # If we are in an authenticating state, call the
  152.                 # method smtp_AUTH.
  153.                 arg = line.strip()
  154.                 command = 'AUTH'
  155.             elif i < 0:
  156.                 command = line.upper()
  157.                 arg = None
  158.             else:
  159.                 command = line[:i].upper()
  160.                 arg = line[i + 1:].strip()
  161.             max_sz = (self.command_size_limits[command]
  162.                       if self.extended_smtp else self.command_size_limit)
  163.             if sz > max_sz:
  164.                 self.push('500 Error: line too long')
  165.                 return
  166.  
  167.             self.run_command_with_arg(command, arg)
  168.             return
  169.         else:
  170.             if self.smtp_state != self.DATA:
  171.                 self.push('451 Internal confusion')
  172.                 self.num_bytes = 0
  173.                 return
  174.             if self.data_size_limit and self.num_bytes > self.data_size_limit:
  175.                 self.push('552 Error: Too much mail data')
  176.                 self.num_bytes = 0
  177.                 return
  178.             # Remove extraneous carriage returns and de-transparency according
  179.             # to RFC 5321, Section 4.5.2.
  180.             data = []
  181.             for text in line.split(self._linesep):
  182.                 if text and text[0] == self._dotsep:
  183.                     data.append(text[1:])
  184.                 else:
  185.                     data.append(text)
  186.             self.received_data = self._newline.join(data)
  187.             args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)
  188.             kwargs = {}
  189.             if not self._decode_data:
  190.                 kwargs = {
  191.                     'mail_options': self.mail_options,
  192.                     'rcpt_options': self.rcpt_options,
  193.                 }
  194.             status = self.smtp_server.process_message(*args, **kwargs)
  195.             self._set_post_data_state()
  196.             if not status:
  197.                 self.push('250 OK')
  198.             else:
  199.                 self.push(status)
  200.  
  201.  
  202. class MySMTPServer(SMTPServer):
  203.     channel_class = MySMTPChanel
  204.  
  205.     def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
  206.         print(peer, mailfrom, rcpttos, data, kwargs)
  207.  
  208.  
  209. MySMTPServer(
  210.     ('0.0.0.0', 4465),
  211.     None
  212. )
  213.  
  214. asyncore.loop()
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top