InonViewPswdSubmit
’s
source code (
Example 16-7
), notice that password
inputs are passed to anencode
function as they are added to the parameters dictionary; this causes
them to show up encrypted or otherwise obfuscated in hyperlinked URLs.
They are also URL encoded for transmission (with%
escapes if needed) and are later decoded and
decrypted within other scripts as needed to access the POP account. The
password encryption step,encode
, is
at the heart of PyMailCGI’s security policy.
In Python today, the standard library’sssl
module
supports Secure Sockets Layer (SSL)
with its socket wrapper call, if the required library is
built into your Python. SSL automatically encrypts transmitted data to
make it safe to pass over the Net. Unfortunately, for reasons we’ll
discuss when we reach thesecret.py
module later in this chapter (see
Example 16-13
), this wasn’t a
universal solution for PyMailCGI’s password data. In short, the
Python-coded web server we’re using doesn’t directly support its end of
a secure HTTP encrypted dialog, HTTPS. Because of that, an alternative
scheme was devised to minimize the chance that email account information
could be stolen off the Net in transit.
Here’s how it works. When this script is invoked by the password
input page’s form, it gets only one input parameter: the password typed
into the form. The username is imported from amailconfig
module installed on the server; it
is not transmitted together with the unencrypted password because such a
combination could be harmful if intercepted.
To pass the POP username and password to the next page as state
information, this script adds them to the end of the mail selection list
URLs, but only after the password has been encrypted or obfuscated bysecret.encode
—a function in a module
that lives on the server and may vary in every location that PyMailCGI
is installed. In fact,
PyMailCGI
was written to not have to know about the password encryptor at all;
because the encoder is a separate module, you can provide any flavor you
like. Unless you also publish your encoder module, the encoded password
shipped with the username won’t mean much if seen.
The upshot is that normally PyMailCGI never sends or receives both
username and password values together in a single transaction, unless
the password is encrypted or obfuscated with an encryptor of your
choice. This limits its utility somewhat (since only a single account
username can be installed on the server), but the alternative of popping
up two pages—one for password entry and one for username—seems even less
friendly. In general, if you want to read your mail with the system as
coded, you have to install its files on your server, edit its
mailconfig.py
to reflect your account
details, and change its
secret.py
encoder and decoder as desired.
One exception: since
any CGI script can be invoked with parameters in an
explicit URL instead of form field values, and sincecommonhtml
tries to fetch inputs from the
form object before importing them frommailconfig
, it is possible for any person to
use this script if installed at an accessible address to check his or
her mail without installing and configuring a copy of PyMailCGI of
their own. For example, a URL such as the following typed into your
browser’s address field or submitted with tools such asurllib.request
(but without the line break
used to make it fit here):
http://localhost:8000/cgi-bin/
onViewPswdSubmit.py?user=lutz&pswd=guess&site=pop.earthlink.net
will actually load email into a selection list page such as that
in
Figure 16-8
, using
whatever user, password, and mail site names are appended to the URL.
From the selection list, you may then view, reply, forward, and delete
email.
Notice that at this point in the interaction, the password you
send in a URL of this form is
not
encrypted.
Later scripts expect that the password inputs will be sent encrypted,
though, which makes it more difficult to use them with explicit URLs
(you would need to match the encrypted or obfuscated form produced by
thesecret
module on the server).
Passwords are encoded as they are added to links in the reply page’s
selection list, and they remain encoded in URLs and hidden form fields
thereafter.
But you shouldn’t use a URL like this, unless you don’t care
about exposing your email password. Sending your unencrypted mail
user ID and password strings across the Net in a URL such as this is
unsafe and open to interception. In fact, it’s like giving away your
email—anyone who intercepts this URL or views it in a server logfile
will have complete access to your email account. It is made even
more treacherous by the fact that this URL format appears in a book
that will be distributed all around the world.
If you care about security and want to use PyMailCGI on a
remote server, install it on your server and configuremailconfig
andsecret
. That should at least guarantee
that both your user and password information will never be
transmitted unencrypted in a single transaction. This scheme still
may not be foolproof, so be careful out there. Without secure HTTP
and sockets, the Internet is a “use at your own risk” medium.
Back to our
page flow; at this point, we are still viewing the message
selection list in
Figure 16-8
. When we click on
one of its generated hyperlinks, the stateful URL invokes the script in
Example 16-8
on the server,
sending the selected message number and mail account information (user,
password, and site) as parameters on the end of the script’s URL.
Example 16-8. PP4E\Internet\Web\PyMailCgi\cgi-bin\onViewListLink.py
#!/usr/bin/python
"""
################################################################################
On user click of message link in main selection list: make mail view page;
cgi.FieldStorage undoes any urllib.parse escapes in link's input parameters
(%xx and '+' for spaces already undone); in 2.0+ we only fetch 1 mail here, not
the entire list again; in 2.0+ we also find mail's main text part intelligently
instead of blindly displaying full text (with any attachments), and we generate
links to attachment files saved on the server; saved attachment files only work
for 1 user and 1 message; most 2.0 enhancements inherited from mailtools pkg;
3.0: mailtools decodes the message's full-text bytes prior to email parsing;
3.0: for display, mailtools decodes main text, commonhtml decodes message hdrs;
################################################################################
"""
import cgi
import commonhtml, secret
from externs import mailtools
#commonhtml.dumpstatepage(0)
def saveAttachments(message, parser, savedir='partsdownload'):
"""
save fetched email's parts to files on
server to be viewed in user's web browser
"""
import os
if not os.path.exists(savedir): # in CGI script's cwd on server
os.mkdir(savedir) # will open per your browser
for filename in os.listdir(savedir): # clean up last message: temp!
dirpath = os.path.join(savedir, filename)
os.remove(dirpath)
typesAndNames = parser.saveParts(savedir, message)
filenames = [fname for (ctype, fname) in typesAndNames]
for filename in filenames:
os.chmod(filename, 0o666) # some srvrs may need read/write
return filenames
form = cgi.FieldStorage()
user, pswd, site = commonhtml.getstandardpopfields(form)
pswd = secret.decode(pswd)
try:
msgnum = form['mnum'].value # from URL link
parser = mailtools.MailParser()
fetcher = mailtools.SilentMailFetcher(site, user, pswd)
fulltext = fetcher.downloadMessage(int(msgnum)) # don't eval!
message = parser.parseMessage(fulltext) # email pkg Message
parts = saveAttachments(message, parser) # for URL links
mtype, content = parser.findMainText(message) # first txt part
commonhtml.viewpage(msgnum, message, content, form, parts) # encoded pswd
except:
commonhtml.errorpage('Error loading message')
Again, much of the work here happens in thecommonhtml
module, listed later in this
section (see
Example 16-14
).
This script adds logic to decode the input password (using the
configurablesecret
encryption
module) and extract the selected mail’s headers and text using
themailtools
module
package from
Chapter 13
again. The full
text of the selected message is ultimately fetched, parsed, and decoded
bymailtools
, using the standard
library’spoplib
module andemail
package. Although we’ll have to
refetch this message if viewed again, version 2.0 and later do not grab
all mails to get just the one selected.
[
68
]
Also new in version 2.0, thesaveAttachments
function in this script splits
off the parts of a fetched message and stores them in a directory on the
web server machine. This was discussed earlier in this chapter—the view
page is then augmented with URL links that point at the saved part
files. Your web browser will open them according to their filenames and
content. All the work of part extraction, decoding, and naming is
inherited
frommailtools
. Part files are kept temporarily;
they are deleted when the next message is fetched. They are also
currently stored in a single directory and so apply to only a single
user.
If the message can be loaded and parsed successfully, the result
page, shown in
Figure 16-12
, allows us to
view, but not edit, the mail’s text. The functioncommonhtml.viewpage
generates a “read-only”
HTML option for all the text widgets in this page. If you look closely,
you’ll notice that this is the mail we sent to ourselves in
Figure 16-3
and which showed
up at the end of the list in
Figure 16-8
.
Figure 16-12. PyMailCGI view page
View pages like this have a pull-down action selection list near
the bottom; if you want to do more, use this list to pick an action
(Reply, Forward, or Delete) and click on the Next button to proceed to
the next screen. If you’re just in a browsing frame of mind, click the
“Back to root page” link at the bottom to return to the main page, or
use your browser’s Back button to return to the selection list
page.
As mentioned,
Figure 16-12
displays the
mail we sent earlier in this chapter, being viewed after being fetched.
Notice its “Parts:” links—when clicked, they trigger URLs that open the
temporary part files on the server, according to your browser’s rules
for the file type. For instance, clicking on the “.txt” file will likely
open it in either the browser or a text editor. In other mails, clicking
on “.jpg” files may open an image viewer, “.pdf” may open Adobe Reader,
and so on.
Figure 16-13
shows the
result of clicking the “.py” attachment part of
Figure 16-12
’s
message in Chrome.
Figure 16-13. Attached part file link display
What you don’t see on
the view page in
Figure 16-12
is
just as important as what you do see. We need to defer to
Example 16-14
for coding details, but
something new is going on here. The original message number, as well as
the POP user and (still encoded) password information sent to this
script as part of the stateful link’s URL, wind up being copied into the
HTML used to create this view page, as the values of hidden input fields
in the form. The hidden field generation code incommonhtml
looks like this:
print('