3
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
4
# See LICENSE for details.
8
Simple IMAP4 client which displays the subjects of all messages in a
14
from twisted.internet import protocol
15
from twisted.internet import ssl
16
from twisted.internet import defer
17
from twisted.internet import stdio
18
from twisted.mail import imap4
19
from twisted.protocols import basic
20
from twisted.python import util
21
from twisted.python import log
23
class TrivialPrompter(basic.LineReceiver):
24
from os import linesep as delimiter
28
def prompt(self, msg):
29
assert self.promptDeferred is None
31
self.promptDeferred = defer.Deferred()
32
return self.promptDeferred
34
def display(self, msg):
35
self.transport.write(msg)
37
def lineReceived(self, line):
38
if self.promptDeferred is None:
40
d, self.promptDeferred = self.promptDeferred, None
43
class SimpleIMAP4Client(imap4.IMAP4Client):
46
def serverGreeting(self, caps):
47
self.serverCapabilities = caps
48
if self.greetDeferred is not None:
49
d, self.greetDeferred = self.greetDeferred, None
52
class SimpleIMAP4ClientFactory(protocol.ClientFactory):
55
protocol = SimpleIMAP4Client
57
def __init__(self, username, onConn):
58
self.ctx = ssl.ClientContextFactory()
60
self.username = username
63
def buildProtocol(self, addr):
64
assert not self.usedUp
67
p = self.protocol(self.ctx)
69
p.greetDeferred = self.onConn
71
auth = imap4.CramMD5ClientAuthenticator(self.username)
72
p.registerAuthenticator(auth)
76
def clientConnectionFailed(self, connector, reason):
77
d, self.onConn = self.onConn, None
80
# Initial callback - invoked after the server sends us its greet message
81
def cbServerGreeting(proto, username, password):
83
tp = TrivialPrompter()
86
# And make it easily accessible
87
proto.prompt = tp.prompt
88
proto.display = tp.display
90
# Try to authenticate securely
91
return proto.authenticate(password
92
).addCallback(cbAuthentication, proto
93
).addErrback(ebAuthentication, proto, username, password
96
# Fallback error-handler. If anything goes wrong, log it and quit.
97
def ebConnection(reason):
98
log.startLogging(sys.stdout)
100
from twisted.internet import reactor
103
# Callback after authentication has succeeded
104
def cbAuthentication(result, proto):
105
# List a bunch of mailboxes
106
return proto.list("", "*"
107
).addCallback(cbMailboxList, proto
110
# Errback invoked when authentication fails
111
def ebAuthentication(failure, proto, username, password):
112
# If it failed because no SASL mechanisms match, offer the user the choice
113
# of logging in insecurely.
114
failure.trap(imap4.NoSupportedAuthentication)
115
return proto.prompt("No secure authentication available. Login insecurely? (y/N) "
116
).addCallback(cbInsecureLogin, proto, username, password
119
# Callback for "insecure-login" prompt
120
def cbInsecureLogin(result, proto, username, password):
121
if result.lower() == "y":
122
# If they said yes, do it.
123
return proto.login(username, password
124
).addCallback(cbAuthentication, proto
126
return defer.fail(Exception("Login failed for security reasons."))
128
# Callback invoked when a list of mailboxes has been retrieved
129
def cbMailboxList(result, proto):
130
result = [e[2] for e in result]
131
s = '\n'.join(['%d. %s' % (n + 1, m) for (n, m) in zip(range(len(result)), result)])
133
return defer.fail(Exception("No mailboxes exist on server!"))
134
return proto.prompt(s + "\nWhich mailbox? [1] "
135
).addCallback(cbPickMailbox, proto, result
138
# When the user selects a mailbox, "examine" it.
139
def cbPickMailbox(result, proto, mboxes):
140
mbox = mboxes[int(result or '1') - 1]
141
return proto.examine(mbox
142
).addCallback(cbExamineMbox, proto
145
# Callback invoked when examine command completes.
146
def cbExamineMbox(result, proto):
147
# Retrieve the subject header of every message on the mailbox.
148
return proto.fetchSpecific('1:*',
149
headerType='HEADER.FIELDS',
150
headerArgs=['SUBJECT']
151
).addCallback(cbFetch, proto
154
# Finally, display headers.
155
def cbFetch(result, proto):
159
proto.display('%s %s' % (k, result[k][0][2]))
160
return proto.logout()
165
hostname = raw_input('IMAP4 Server Hostname: ')
166
username = raw_input('IMAP4 Username: ')
167
password = util.getPassword('IMAP4 Password: ')
169
onConn = defer.Deferred(
170
).addCallback(cbServerGreeting, username, password
171
).addErrback(ebConnection
174
factory = SimpleIMAP4ClientFactory(username, onConn)
176
from twisted.internet import reactor
177
conn = reactor.connectTCP(hostname, PORT, factory)
180
if __name__ == '__main__':