5 # Communicate with mail servers
16 # Every X seconds, send a message, and queue messages to be sent at in this
21 # Real subject used for all encrypted messages
22 # Note: make a Gmail filter that filters (Secure Message) into the Secure tag
23 FAKE_SUBJECT
= "(Secure Message)"
25 # Dummy message used in channel filling if there is nothing to send.
26 DUMMY_SUBJECT
= "Filler Message"
27 # TODO: should be the person you're always sending to
28 FILLER_TO
= "shellreef@gmail.com"
30 class SecureMail(threading
.Thread
):
31 def __init__(self
, username
=None, password
=None):
32 """Prompt for login and password, or using stored values if possible, then login."""
36 username
= file(".otplogin").read().strip()
38 username
= raw_input("Username: ")
42 password
= file(".otppass").read().strip()
44 password
= getpass
.getpass()
46 threading
.Thread
.__init
__(self
)
47 return self
.login(username
, password
)
50 def login(self
, username
, password
):
51 """Login to a Gmail account, IMAP and SMTP server, with login and password."""
53 # IMAP SSL for incoming messages
54 self
.mail_in
= imaplib
.IMAP4_SSL("imap.gmail.com", 993)
56 self
.username
= username
59 typ
, data
= self
.mail_in
.login(username
, password
)
61 raise "Login failure: %s" % (sys
.exc_info(),)
63 assert typ
== "OK", "imap login returned: %s %s" % (status
, message
)
64 print "Logged in:", typ
, data
66 # Always have "Secure" mailbox selected
67 typ
, num_msgs
= self
.mail_in
.select(mailbox
="Secure")
69 raise ("imap select failure: %s %s" % (typ
, num_msgs
) +
70 "The 'Secure' tag doesn't exist. Tag some of your EMOTP messages" +
71 "using a new label, named 'Secure'")
73 # Outgoing mail, SMTP server
74 self
.mail_out
= smtplib
.SMTP("smtp.gmail.com", 25)
75 ret
= self
.mail_out
.ehlo()
76 assert ret
[0] == 250, "SMTP server EHLO failed: %s" % (ret
,)
78 ret
= self
.mail_out
.starttls()
79 assert ret
[0] == 220, "SMTP server STARTTLS failed: %s" % (ret
,)
81 ret
= self
.mail_out
.ehlo()
82 assert ret
[0] == 250, "SMTP server EHLO over TLS failed: %s" % (ret
,)
84 ret
= self
.mail_out
.noop()
85 assert ret
[0] == 250, "SMTP server NOOP failed: %s" % (ret
,)
87 ret
= self
.mail_out
.login(username
, password
)
88 assert ret
[0] == 235, "SMTP server login failed: %s" % (ret
,)
89 print "Logged in to SMTP server: %s" % (ret
,)
91 # Start channel filler
92 self
.sendq
= Queue
.Queue()
95 # Channel filling thread
96 # Problem: computer needs to be online to fill channel!
98 if FILL_INTERVAL
is None:
99 print "Channel filling disabled - messages will send immediately"
104 item
= self
.sendq
.get_nowait()
106 print "Sending filler message"
107 body
= "" # empty, they should have padding!
108 to
= FILLER_TO
# todo: find out other person's email
109 subject
= DUMMY_SUBJECT
111 # Note: subject is encrypted and sent in body, so it is secure
112 self
.send_now(to
, subject
, body
)
114 print "Sending queued message now"
115 to
, subject
, body
= item
116 self
.send_now(to
, subject
, body
)
118 time
.sleep(FILL_INTERVAL
)
123 def get_messages(self
):
127 typ
, all_msgs_string
= self
.mail_in
.search(None, 'ALL')
128 except imaplib
.error
, e
:
129 raise "imap search failed: %s" % (e
,)
131 all_msgs
= all_msgs_string
[0].split()
133 typ
, body
= self
.mail_in
.fetch(num
, "(BODY[])")
134 msg
= email
.message_from_string(body
[0][1])
136 enc_body
= str(msg
.get_payload())
137 fake_subject
= msg
.get("Subject") # not encrypted
138 sender
= msg
.get("From")
139 id = msg
.get("Message-ID")
141 #print 'Message %s\n%s\n' % (num, data[0][1])
142 if "--EMOTP_BEGIN--" not in enc_body
:
145 subject_plus_body
= cotp
.decode(enc_body
)
147 if "<body>" in subject_plus_body
:
149 subject
, body
= subject_plus_body
.split("<body>")
151 subject
, body
= fake_subject
, subject_plus_body
153 if subject
== DUMMY_SUBJECT
:
157 msgs
.append({"body": body
,
158 "fake-subject": fake_subject
,
167 """Fetch messages from server."""
168 self
.msgs
= self
.get_messages()
169 return iter(self
.msgs
)
171 def __getitem__(self
, k
):
172 """Lookup a message of a given number. Messages
173 must have been previous fetched from server up by __iter__."""
177 def send(self
, to
, subject
, body
):
178 """Send, in a timeslot if channel filling is enabled, or immediately
179 if channel filling is disabled."""
180 if FILL_INTERVAL
is None:
181 print "Sending %s bytes now" % (len(body
,))
182 return self
.send_now(to
, subject
, body
)
184 self
.sendq
.put((to
, subject
, body
))
185 print "Enqueued to send at next interval, pending: %s" % (self
.sendq
.qsize(),)
188 def send_now(self
, to
, subject
, body
):
189 """Send an encrypted message immediately."""
190 from_address
= "%s@gmail.com" % (self
.username
,)
191 # TODO: encode with different pads based on 'to' email
192 enc_body
= cotp
.encode(subject
+ "<body>" + body
)
193 return self
.mail_out
.sendmail(from_address
,
196 "From: %s" % (from_address
,),
198 "Subject: %s" % (FAKE_SUBJECT
,),
204 self
.mail_in
.logout()
208 ms
= SecureMail("shellreef", getpass
.getpass())
210 for m
in ms
.get_messages():
211 print m
["sender"], m
["subject"]
213 if __name__
== "__main__":