5 # Communicate with mail servers
16 # Every X seconds, send a message, and queue messages to be sent at in this
20 # Real subject used for all encrypted messages
21 # Note: make a Gmail filter that filters (Secure Message) into the Secure tag
22 FAKE_SUBJECT
= "(Secure Message)"
24 # Dummy message used in channel filling if there is nothing to send.
25 DUMMY_SUBJECT
= "Filler Message"
26 # TODO: should be the person you're always sending to
27 FILLER_TO
= "shellreef@gmail.com"
29 class SecureMail(threading
.Thread
):
30 def __init__(self
, username
=None, password
=None):
31 """Prompt for login and password, or using stored values if possible, then login."""
35 username
= file(".otplogin").read().strip()
37 username
= raw_input("Username: ")
41 password
= file(".otppass").read().strip()
43 password
= getpass
.getpass()
45 threading
.Thread
.__init
__(self
)
46 return self
.login(username
, password
)
49 def login(self
, username
, password
):
50 """Login to a Gmail account, IMAP and SMTP server, with login and password."""
52 # IMAP SSL for incoming messages
53 self
.mail_in
= imaplib
.IMAP4_SSL("imap.gmail.com", 993)
55 self
.username
= username
58 typ
, data
= self
.mail_in
.login(username
, password
)
60 raise "Login failure: %s" % (sys
.exc_info(),)
62 assert typ
== "OK", "imap login returned: %s %s" % (status
, message
)
63 print "Logged in:", typ
, data
65 # Always have "Secure" mailbox selected
66 typ
, num_msgs
= self
.mail_in
.select(mailbox
="Secure")
68 raise ("imap select failure: %s %s" % (typ
, num_msgs
) +
69 "The 'Secure' tag doesn't exist. Tag some of your EMOTP messages" +
70 "using a new label, named 'Secure'")
72 # Outgoing mail, SMTP server
73 self
.mail_out
= smtplib
.SMTP("smtp.gmail.com", 25)
74 ret
= self
.mail_out
.ehlo()
75 assert ret
[0] == 250, "SMTP server EHLO failed: %s" % (ret
,)
77 ret
= self
.mail_out
.starttls()
78 assert ret
[0] == 220, "SMTP server STARTTLS failed: %s" % (ret
,)
80 ret
= self
.mail_out
.ehlo()
81 assert ret
[0] == 250, "SMTP server EHLO over TLS failed: %s" % (ret
,)
83 ret
= self
.mail_out
.noop()
84 assert ret
[0] == 250, "SMTP server NOOP failed: %s" % (ret
,)
86 ret
= self
.mail_out
.login(username
, password
)
87 assert ret
[0] == 235, "SMTP server login failed: %s" % (ret
,)
88 print "Logged in to SMTP server: %s" % (ret
,)
90 # Start channel filler
91 self
.sendq
= Queue
.Queue()
94 # Channel filling thread
95 # Problem: computer needs to be online to fill channel!
97 if FILL_INTERVAL
is None:
98 print "Channel filling disabled - messages will send immediately"
103 item
= self
.sendq
.get_nowait()
105 print "Sending filler message"
106 body
= "" # empty, they should have padding!
107 to
= FILLER_TO
# todo: find out other person's email
108 subject
= DUMMY_SUBJECT
110 # Note: subject is encrypted and sent in body, so it is secure
111 self
.send_now(to
, subject
, body
)
113 print "Sending queued message now"
114 to
, subject
, body
= item
115 self
.send_now(to
, subject
, body
)
117 time
.sleep(FILL_INTERVAL
)
122 def get_messages(self
):
126 typ
, all_msgs_string
= self
.mail_in
.search(None, 'ALL')
127 except imaplib
.error
, e
:
128 raise "imap search failed: %s" % (e
,)
130 all_msgs
= all_msgs_string
[0].split()
132 typ
, body
= self
.mail_in
.fetch(num
, "(BODY[])")
133 msg
= email
.message_from_string(body
[0][1])
135 enc_body
= str(msg
.get_payload())
136 fake_subject
= msg
.get("Subject") # not encrypted
137 sender
= msg
.get("From")
138 id = msg
.get("Message-ID")
140 #print 'Message %s\n%s\n' % (num, data[0][1])
141 if "--EMOTP_BEGIN--" not in enc_body
:
144 subject_plus_body
= cotp
.decode(enc_body
)
146 if "<body>" in subject_plus_body
:
148 subject
, body
= subject_plus_body
.split("<body>")
150 subject
, body
= fake_subject
, subject_plus_body
152 if subject
== DUMMY_SUBJECT
:
156 msgs
.append({"body": body
,
157 "fake-subject": fake_subject
,
166 """Fetch messages from server."""
167 self
.msgs
= self
.get_messages()
168 return iter(self
.msgs
)
170 def __getitem__(self
, k
):
171 """Lookup a message of a given number. Messages
172 must have been previous fetched from server up by __iter__."""
176 def send(self
, to
, subject
, body
):
177 """Send, in a timeslot if channel filling is enabled, or immediately
178 if channel filling is disabled."""
179 if FILL_INTERVAL
is None:
180 print "Sending %s bytes now" % (len(body
,))
181 return self
.send(to
, subject
, body
)
183 self
.sendq
.put((to
, subject
, body
))
184 print "Enqueued to send at next interval, pending: %s" % (self
.sendq
.qsize(),)
187 def send_now(self
, to
, subject
, body
):
188 """Send an encrypted message immediately."""
189 from_address
= "%s@gmail.com" % (self
.username
,)
190 # TODO: encode with different pads based on 'to' email
191 enc_body
= cotp
.encode(subject
+ "<body>" + body
)
192 return self
.mail_out
.sendmail(from_address
,
195 "From: %s" % (from_address
,),
197 "Subject: %s" % (FAKE_SUBJECT
,),
203 self
.mail_in
.logout()
207 ms
= SecureMail("shellreef", getpass
.getpass())
209 for m
in ms
.get_messages():
210 print m
["sender"], m
["subject"]
212 if __name__
== "__main__":