3 # Copyright (c) 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__':