How to work around problem with Python smtpd? - python-3.x

I want to make a small SMTP server for testing, using Python, so I was trying the server example code
https://pymotw.com/2/smtpd/
import smtpd
import asyncore
class CustomSMTPServer(smtpd.SMTPServer):
def process_message(self, peer, mailfrom, rcpttos, data):
print 'Receiving message from:', peer
print 'Message addressed from:', mailfrom
print 'Message addressed to :', rcpttos
print 'Message length :', len(data)
return
server = CustomSMTPServer(('127.0.0.1', 1025), None)
asyncore.loop()
Together with the example client code on that same page:
import smtplib
import email.utils
from email.mime.text import MIMEText
# Create the message
msg = MIMEText('This is the body of the message.')
msg['To'] = email.utils.formataddr(('Recipient', 'recipient#example.com'))
msg['From'] = email.utils.formataddr(('Author', 'author#example.com'))
msg['Subject'] = 'Simple test message'
server = smtplib.SMTP('127.0.0.1', 1025)
server.set_debuglevel(True) # show communication with the server
try:
server.sendmail('author#example.com', ['recipient#example.com'], msg.as_string())
finally:
server.quit()
However, when I try to run the client, I am getting the following on the server side:
error: uncaptured python exception, closing channel <smtpd.SMTPChannel connected 127.0.0.1:38634 at 0x7fe28a901490> (<class 'TypeError'>:process_message() got an unexpected keyword argument 'mail_options' [/root/Python-3.8.1/Lib/asyncore.py|read|83] [/root/Python-3.8.1/Lib/asyncore.py|handle_read_event|420] [/root/Python-3.8.1/Lib/asynchat.py|handle_read|171] [/root/Python-3.8.1/Lib/smtpd.py|found_terminator|386])
^CTraceback (most recent call last):
File "./mysmtpd.py", line 18, in <module>
asyncore.loop()
File "/root/Python-3.8.1/Lib/asyncore.py", line 203, in loop
poll_fun(timeout, map)
File "/root/Python-3.8.1/Lib/asyncore.py", line 144, in poll
r, w, e = select.select(r, w, e, timeout)
KeyboardInterrupt
Then I found this Issue page:
https://bugs.python.org/issue35837
and I think that that is the problem I've been running into.
That issue hasn't been fixed yet, so I was wondering if, meantime, is there something that I can modify in the example client code that would get around the problem that is described in that issue?
Thanks,
Jim

Add an mail_options=None in your process_message() function.

Just for reference, do the same with the rcpt_options argument.
Ref: https://docs.python.org/3/library/smtpd.html?highlight=process_message#smtpd.SMTPServer.process_message

The error seems to appear
in the line with def process_message(self, peer, mailfrom, rcpttos, data):.
You can replace it with def process_message(self, peer, mailfrom, rcpttos, data,**my_krargs):

Related

Please , can you help me get rid of this error?

I am trying to create a code for a random password generator with python and flask. I have created both the front end and the backend yet every time i am trying to run it i get a json error:
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
this is my front end code:
import requests
import json
session=requests.session()
data={"letters":0,"symbols":0,"numbers":0}
header={"content-type": "application/json"}
print("Welcome to the PyPassword generator!")
while True:
data ["letters"]=input("how many lettres would you like in your password? > ")
data ["symbols"]=input("how many symbols would you like? > ")
data ["numbers"]=input("how many numbers would you like? > ")
print(data)
r=session.post("http://127.0.0.1:1337/api/PyPassword_generator", data=json.dumps(data), headers=header)
if r:
break
json_data=r.json()
print(json_data)
EDIT: Ading the response from the server component
Traceback (most recent call last):
File "C:\Users\User\PycharmProjects\pythonProject\venv\lib\site-packages\requests\models.py", line 972, in json
return complexjson.loads(self.text, **kwargs)
File "C:\Users\User\PycharmProjects\pythonProject\venv\lib\site-packages\simplejson\__init__.py", line 525, in loads
return _default_decoder.decode(s)
File "C:\Users\User\PycharmProjects\pythonProject\venv\lib\site-packages\simplejson\decoder.py", line 370, in decode
obj, end = self.raw_decode(s)
File "C:\Users\User\PycharmProjects\pythonProject\venv\lib\site-packages\simplejson\decoder.py", line 400, in raw_decode
return self.scan_once(s, idx=_w(s, idx).end())
simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\User\Downloads\client.py", line 19, in <module>
json_data = r.json()
File "C:\Users\User\PycharmProjects\pythonProject\venv\lib\site-packages\requests\models.py", line 976, in json
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
b'<!doctype html>\n<html lang=en>\n<title>405 Method Not Allowed</title>\n<h1>Method Not Allowed</h1>\n<p>The method is not allowed for the requested URL.</p>\n'
Process finished with exit code 1ode here
so this is the server code
from flask import Flask, request, session, jsonify,json
import random
class game:
def __init__(self,lettres,symbols,numbers):
self.letters=lettres
self.symbols=symbols
self.numbers=numbers
self.app = Flask("Shmuel Api server",static_url_path='')
self.app.secret_key="Secret"
#self.app.route('/api/ PyPassword_generator', methods=['Get',"Post"])
def PyPassword_generator():
print(request.data)
print(json.loads(request.data.decode("ascii")))
a=int(request.json["letters"])
b=int(request.json["symbols"])
c=int(request.json["numbers"])
password = self.test(a,b,c)
jsonify({f"your password is : {password}"})
def run(self):
debug=True
self.app.run(host='10.0.0.10', port='5000', debug=debug)
password_list=[]
for char in range(a):
password_list.append(random.choice(self.letters))
for char in range(b):
password_list.append(random.choice(self.symbols))
for char in range(c):
password_list.append(random.choice(self.numbers))
random.shuffle(password_list)
password="".join(password_list)
return password
mp=game(["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"],["0","1","2","3","4","5","6","7","8","9"],["!","#","$","%","&","(",")","*","+"])
mp.run()
and this is the response I get
* Serving Flask app 'Shmuel Api server' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://10.0.0.10:5000 (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 446-336-887
10.0.0.10 - - [11/Jun/2022 21:23:52] "POST /api/PyPassword_generator HTTP/1.1" 405 -
There's a space in the URL of the server component, and the methods must be in caps...
#self.app.route('/api/ PyPassword_generator', methods=['Get',"Post"])
should look like:
#self.app.route('/api/PyPassword_generator', methods=['GET',"POST"])
Defining Flask routes inside an object like this looks wrong though. I would probably simplify it:
from flask import Flask, request, session, jsonify,json
import random
app = Flask("__name__")
app.config['SECRET_KEY']='make me super secret'
#app.route('/api/PyPassword_generator', methods=['GET',"POST"])
def PyPassword_generator():
# print(request.data)
# print(json.loads(request.data.decode("ascii")))
try:
submitted_data = request.get_json()
a=int(submitted_data["letters"])
b=int(submitted_data["symbols"])
c=int(submitted_data["numbers"])
except ValueError: # Some issue with the input
return {'error': 'invalid input data'}, 400
password = generate_password(a,b,c)
return jsonify({'message': f"your password is : {password}"})
Then on the client end, simplify things, by using requests native json argument, which means you don't need to use the json.dumps or pass the headers either, as this arg automatically sets the Content-Type...
import requests
session=requests.session()
data={"letters":0,"symbols":0,"numbers":0}
print("Welcome to the PyPassword generator!")
while True:
data["letters"]=input("how many lettres would you like in your password? > ")
data["symbols"]=input("how many symbols would you like? > ")
data["numbers"]=input("how many numbers would you like? > ")
print(data)
r = session.post("http://127.0.0.1:5005/api/PyPassword_generator", json=data)
json_data=r.json()
print(json_data)
if r.ok:
break
Of course generating passwords comes with its own security considerations, and I assume this is just for learning python. Transmitting passwords over the wire comes with security considerations you should understand first.
With that in mind, instead of random.shuffle for this, perhaps investigate the python secrets module. Let's implement the generate_password function, and instead only work with the input a to set the length of the overall password. Using their example, you could have something like:
import secrets
import string
def generate_password(a,b,c):
alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(alphabet) for i in range(a))
return password
Now this all works like:
Welcome to the PyPassword generator!
how many lettres would you like in your password? > 20
how many symbols would you like? > 1
how many numbers would you like? > 1
{'letters': '20', 'symbols': '1', 'numbers': '1'}
{'message': 'your password is : R#!o]KFnqk`,+Wg{JQ:#'}
Of course some obvious changes required on your part, but hope this helps.

Python3 SMTP 'Connection unexpectedly closed'

Could somebody please tell me why I am getting a SMTPServerDisconnected("Connection unexpectedly closed") error from the following code?
import smtplib
from string import Template
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
MY_ADDRESS = '---'
PASSWORD = '---'
def get_contacts(filename):
"""
Return two lists names, emails containing names and email
addresses
read from a file specified by filename.
"""
names = []
emails = []
with open(filename, mode='r', encoding='utf-8') as contacts_file:
for a_contact in contacts_file:
names.append(a_contact.split()[0])
emails.append(a_contact.split()[1])
return names, emails
def read_template(filename):
"""
Returns a Template object comprising the contents of the
file specified by filename.
"""
with open(filename, 'r', encoding='utf-8') as template_file:
template_file_content = template_file.read()
return Template(template_file_content)
def main():
names, emails = get_contacts('contacts.txt') # read contacts
message_template = read_template('message.txt')
# set up the SMTP server
s = smtplib.SMTP('smtp.gmail.com', 465)
s.ehlo()
s.starttls()
s.login(MY_ADDRESS, PASSWORD)
# For each contact, send the email:
for name, email in zip(names, emails):
msg = MIMEMultipart() # create a message
# add in the actual person name to the message template
message =
message_template.substitute(PERSON_NAME=name.title())
# Prints out the message body for our sake
print(message)
# setup the parameters of the message
msg['From']=MY_ADDRESS
msg['To']=email
msg['Subject']="This is TEST"
# add in the message body
msg.attach(MIMEText(message, 'plain'))
# send the message via the server set up earlier.
s.send_message(msg)
del msg
# Terminate the SMTP session and close the connection
s.quit()
if __name__ == '__main__':
main()
Obviously when I run the code my address and password is filled in.
The traceback I get from this when running in terminal is:
Traceback (most recent call last):
File "emailAlert2.py", line 71, in
main()
File "emailAlert2.py", line 40, in main
s = smtplib.SMTP('smtp.gmail.com', 465)
File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/smtplib.py", line 251, in init
(code, msg) = self.connect(host, port)
File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/smtplib.py", line 338, in connect
(code, msg) = self.getreply()
File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/smtplib.py", line 394, in getreply
raise SMTPServerDisconnected("Connection unexpectedly closed")
smtplib.SMTPServerDisconnected: Connection unexpectedly closed
Thanks
The Google Gmail server is hanging up on your (dropping your connection attempt).
Provided that you have enabled third party access (link) to your Gmail account, change your code as follows:
s = smtplib.SMTP('smtp.gmail.com', 465)
s.ehlo()
s.starttls()
s.login(MY_ADDRESS, PASSWORD)
Change to this:
s = smtplib.SMTP_SSL('smtp.gmail.com', 465)
s.ehlo()
s.login(MY_ADDRESS, PASSWORD)
The reason for the hang up is that you are creating a connection using an unencrypted method (smtplib.SMTP()). Google is expecting that you are connecting using SMTPS which requires SSL.
Try port number 587 instead of port 465 in s = smtplib.SMTP('smtp.gmail.com', 465)
Probably you may need to come up with App password instead of your default password - Check this out - https://support.google.com/accounts/answer/185833

Can Someone Please Tell Me What This Code Does

import imaplib,time
class Mail():
def __init__(self):
self.user= 'USERNAME'
self.password= 'PASSWORD'
self.M = imaplib.IMAP4_SSL('imap.gmail.com', '993')
self.M.login(self.user, self.password)
def checkMail(self):
self.M.select()
self.unRead = self.M.search(None, 'UnSeen')
return len(self.unRead[1][0].split())
email = Mail()
while 1:
print ('Sending')
time.sleep(2)
You initialize a class called Mail that supposedly uses the imaplib library to check the unread messages of a gmail account. However, after you assign email to an instance of Mail, your while loop, which is responsible for the action in your code, is comprised of two events; a print and a time.sleep().
So your code essentially is a long way of printing 'Sending' every 2 seconds.
Running after commenting out the line email = Mail() due to invalid credentials
bash-3.2$ python foo.py
Sending
Sending
^CTraceback (most recent call last):
File "foo.py", line 21, in <module>
time.sleep(2)
KeyboardInterrupt
bash-3.2$

Connections aren't closing with Python3 asyncio concurrent HTTP get requests

I've just started using the asyncio libs from Python3.4 and wrote a small program which attempts to concurrently fetch 50 webpages at a time. The program blows up after a few hundred requests with a 'Too many open files' exception.
I thought that my fetch method closes the connections with the 'response.read_and_close()' method call.
Any ideas what's going on here? Am I going about this problem the right way?
import asyncio
import aiohttp
#asyncio.coroutine
def fetch(url):
response = yield from aiohttp.request('GET', url)
response = yield from response.read_and_close()
return response.decode('utf-8')
#asyncio.coroutine
def print_page(url):
page = yield from fetch(url)
# print(page)
#asyncio.coroutine
def process_batch_of_urls(round, urls):
print("Round starting: %d" % round)
coros = []
for url in urls:
coros.append(asyncio.Task(print_page(url)))
yield from asyncio.gather(*coros)
print("Round finished: %d" % round)
#asyncio.coroutine
def process_all():
api_url = 'https://google.com'
for i in range(10):
urls = []
for url in range(50):
urls.append(api_url)
yield from process_batch_of_urls(i, urls)
loop = asyncio.get_event_loop()
loop.run_until_complete(process_all())
The error I'm getting is:
Traceback (most recent call last):
File "/usr/local/lib/python3.4/site-packages/aiohttp/client.py", line 106, in request
File "/usr/local/lib/python3.4/site-packages/aiohttp/connector.py", line 135, in connect
File "/usr/local/lib/python3.4/site-packages/aiohttp/connector.py", line 242, in _create_connection
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/base_events.py", line 424, in create_connection
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/base_events.py", line 392, in create_connection
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/socket.py", line 123, in __init__
OSError: [Errno 24] Too many open files
During handling of the above exception, another exception occurred:
Aha, I grok you problem.
Explicit connector definitely can solve the issue.
https://github.com/KeepSafe/aiohttp/pull/79 should fix it for implicit connectors too.
Thank you very much for finding resource leak in aiohttp
UPD.
aiohttp 0.8.2 has no the problem.
Ok I finally got it to work.
Turns out I had to use a TCPConnector which pools connections.
So I made this variable:
connector = aiohttp.TCPConnector(share_cookies=True, loop=loop)
and pass it through to each get request. My new fetch routine looks like this:
#asyncio.coroutine
def fetch(url):
data = ""
try:
yield from asyncio.sleep(1)
response = yield from aiohttp.request('GET', url, connector=connector)
except Exception as exc:
print('...', url, 'has error', repr(str(exc)))
else:
data = (yield from response.read()).decode('utf-8', 'replace')
response.close()
return data

Telnet.read.until function doesn't work

Trying to telnet to a Brocade router and the python script is sending out error.... Not sure what is wrong here. Have tried a debug but cant make it working. I believe it's prompt issue. I appreciate if anyone has suggestion how to get it work.
Note: This is Python 3.0
import getpass
import sys
import telnetlib
HOST = "1.1.1.1"
user = "admin"
password = "password"
port = "23"
telnet = telnetlib.Telnet(HOST)
telnet.read_until("sw0 login:,3")
telnet.write(admin + "\r")
if password:
telnet.read_until("Password: ")
telnet.write(password + "\n")
tn.write("term len 0" + "\n")
telnet.write("sh ver br\n")
telnet.write("exit\n")
ERROR:
Traceback (most recent call last):
File "C:\Users\milan\Desktop\telnetNew.py", line 13, in <module>
telnet.read_until("Username :,3")
File "C:\Python33\lib\telnetlib.py", line 299, in read_until
return self._read_until_with_select(match, timeout)
File "C:\Python33\lib\telnetlib.py", line 352, in _read_until_with_select
i = self.cookedq.find(match)
TypeError: Type str doesn't support the buffer API
This is my prompt after logging manually using telnet port 23 and this is what i expected command to work.
_________________________________________________________________________________
Network OS (sw0)
xxxxxxxxxx
sw0 login: xxxxx
Password: xxxxx
WARNING: The default password of 'admin' and 'user' accounts have not been changed.
Welcome to the Brocade Network Operating System Software
admin connected from 10.100.131.18 using console on sw0
sw0# sh ver
sw0#
In looking at the docs, it appears that telnetlib want a bytestr, not a Str. so try this., which should convert everything to bytes as opposed to Str
import sys
import telnetlib
HOST = "1.1.1.1"
user = "admin"
password = "password"
port = "23"
telnet = telnetlib.Telnet(HOST,port)
telnet.read_until(b"login: ")
telnet.write(admin.encode('ascii') + b"\n")
telnet.read_until(b"Password: ")
telnet.write(password.encode('ascii') + b"\n")
tn.write(b"term len 0\n")
telnet.write(b"sh ver br\n")
telnet.write(b"exit\n")
--edit-- I installed python and tried this against one of my routers. Changing the username/password to my credentials I was able to login fine. ( I removed the password check and the getpass as they where not being used, being that your code has them hardcoded). It looks like you copied the 2.x example, but the 3.x requires the buffer compatible ones without the b" i Get this
Traceback (most recent call last):
File "foo.py", line 5, in <module>
telnet.read_until("login: ")
File "/usr/local/Cellar/python32/3.2.5/Frameworks/Python.framework/Versions/3.2/lib/python3.2/telnetlib.py", line 293, in read_until
return self._read_until_with_poll(match, timeout)
File "/usr/local/Cellar/python32/3.2.5/Frameworks/Python.framework/Versions/3.2/lib/python3.2/telnetlib.py", line 308, in _read_until_with_poll
i = self.cookedq.find(match)
TypeError: expected an object with the buffer interface
with the b" I get
[~] /usr/local/bin/python3.2 foo.py
b'\r\n\r\nThis computer system including all related equipment, network devices\r\n(specifically including Internet access), are provided only for\r\nauthorized use. All computer
which shows it is working.. what errors are you getting now
import telnetlib , socket
class TELNET(object):
def __init__(self):
self.tn = None
self.username = "root"
self.password = "12345678"
self.host = "10.1.1.1"
self.port = 23
self.timeout = 5
self.login_prompt = b"login: "
self.password_prompt = b"Password: "
def connect(self):
try :
self.tn = telnetlib.Telnet(self.host,self.port,self.timeout)
except socket.timeout :
print("TELNET.connect() socket.timeout")
self.tn.read_until(self.login_prompt, self.timeout)
self.tn.write(self.username.encode('ascii') + b"\n")
if self.password :
self.tn.read_until(self.password_prompt,self.timeout)
self.tn.write(self.password.encode('ascii') + b"\n")
def write(self,msg):
self.tn.write(msg.encode('ascii') + b"\n")
return True
def read_until(self,value):
return self.tn.read_until(value)
def read_all(self):
try :
return self.tn.read_all().decode('ascii')
except socket.timeout :
print("read_all socket.timeout")
return False
def close(self):
self.tn.close()
return True
def request(self,msg):
self.__init__()
self.connect()
if self.write(msg) == True :
self.close()
resp = self.read_all()
return resp
else :
return False
telnet = TELNET()
#call request function
resp = telnet.request('ps www') # it will be return ps output
print(resp)
That code works on python3
Most of these answers are great in-depth explanations and I found them a little hard to follow. The short answer is that the Sample code is wrong on the Python 3x site.
to fix this, instead of
' telnet.read_until("Username :,3")'
use
' telnet.read_until(b"Username :,3")'
Like the other answers say, the problem is that Telnetlib expects byte strings. In Python 2 the str type is a byte string (a string of binary data) whereas it's a unicode string in Python 3 and a new bytes type that represents binary data in the same was as str did in Python 2.
The upshot of this is that you need to convert your str data into bytes data when using Telnetlib in Python 3. I have some code that works in both Python2 and Python3 that I thought worth sharing.
class Telnet(telnetlib.Telnet,object):
if sys.version > '3':
def read_until(self,expected,timeout=None):
expected = bytes(expected, encoding='utf-8')
received = super(Telnet,self).read_until(expected,timeout)
return str(received, encoding='utf-8')
def write(self,buffer):
buffer = bytes(buffer, encoding='utf-8')
super(Telnet,self).write(buffer)
def expect(self,list,timeout=None):
for index,item in enumerate(list):
list[index] = bytes(item, encoding='utf-8')
match_index, match_object, match_text = super(Telnet,self).expect(list,timeout)
return match_index, match_object, str(match_text, encoding='utf-8')
You then instantiate the new Telnet class instead of telnetlib.Telnet. The new class overrides methods in Telnetlib to perform conversion from str to bytes and bytes to str so that the problem just goes away.
Of course, I've only overriden the methods that I was using, but it should be clear from the example how to extend the idea to other methods. Basically, you need to use
bytes(str_string, encoding='utf-8')
and
str(bytes_string, encoding='utf-8')
My code is valid Python 2 and 3 so will work unmodified with either interpreter. When in Python2 the code that overrides the methods is skipped over, effectively ignored. If you don't care for backwards compatibility you can remove the condition that checks for the Python version.

Resources