1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
import re
import gnupg
from zope.interface import implements
from StringIO import StringIO
from twisted.mail import smtp
from twisted.internet.protocol import ServerFactory
from twisted.internet import reactor
from twisted.internet import defer
from twisted.application import internet, service
from email.Header import Header
class SMTPFactory(ServerFactory):
"""
Factory for an SMTP server with encrypted relaying capabilities.
"""
def buildProtocol(self, addr):
"Return a protocol suitable for the job."
smtpProtocol = smtp.SMTP(SMTPDelivery())
smtpProtocol.factory = self
return smtpProtocol
class SMTPDelivery(object):
"""
Validate email addresses and handle message delivery.
"""
implements(smtp.IMessageDelivery)
def receivedHeader(self, helo, origin, recipients):
myHostname, clientIP = helo
headerValue = "by %s from %s with ESMTP ; %s" % (
myHostname, clientIP, smtp.rfc822date( ))
# email.Header.Header used for automatic wrapping of long lines
return "Received: %s" % Header(headerValue)
def validateTo(self, user):
"""Assert existence of GPG public key for a recipient."""
# for now just accept any receipient
print "Accepting mail for %s..." % user.dest
return lambda: EncryptedMessage(user)
def validateFrom(self, helo, originAddress):
# accept mail from anywhere. To reject an address, raise
# smtp.SMTPBadSender here.
return originAddress
class EncryptedMessage():
"""
Receive plaintext from client, encrypt it and send message to a
recipient.
"""
implements(smtp.IMessage)
SMTP_HOSTNAME = "mail.riseup.net"
SMTP_PORT = 25
def __init__(self, user):
self.user = user
self.getSMTPInfo()
self.lines = []
self.gpg = GPGWrapper()
def lineReceived(self, line):
"""Store email DATA lines as they arrive."""
self.lines.append(line)
def eomReceived(self):
"""Encrypt and send message."""
print "Message data complete."
self.lines.append('') # add a trailing newline
self.parseMessage()
self.encrypt()
return self.sendMessage()
def parseMessage(self):
"""Separate message headers from body."""
sep = self.lines.index('')
self.header = self.lines[:sep]
self.body = self.lines[sep+1:]
def connectionLost(self):
print "Connection lost unexpectedly!"
# unexpected loss of connection; don't save
self.lines = []
def sendSuccess(self, r):
print r
def sendError(self, e):
print e
def prepareHeader(self):
self.header.insert(1, "From: %s" % self.user.orig.addrstr)
self.header.insert(2, "To: %s" % self.user.dest.addrstr)
self.header.append('')
def sendMessage(self):
self.prepareHeader()
msg = '\n'.join(self.header+[self.cyphertext])
d = defer.Deferred()
factory = smtp.ESMTPSenderFactory(self.smtp_username,
self.smtp_password,
self.smtp_username,
self.user.dest.addrstr,
StringIO(msg),
d)
# the next call is TSL-powered!
reactor.connectTCP(self.SMTP_HOSTNAME, self.SMTP_PORT, factory)
d.addCallback(self.sendSuccess)
d.addErrback(self.sendError)
return d
def encrypt(self):
fp = self.gpg.get_fingerprint(self.user.dest.addrstr)
print "Encrypting to %s" % fp
self.cyphertext = str(self.gpg.encrypt('\n'.join(self.body), [fp]))
# this will be replaced by some other mechanism of obtaining credentials
# for SMTP server.
def getSMTPInfo(self):
f = open('/media/smtp-info.txt', 'r')
self.smtp_host = f.readline().rstrip()
self.smtp_port = f.readline().rstrip()
self.smtp_username = f.readline().rstrip()
self.smtp_password = f.readline().rstrip()
f.close()
class GPGWrapper():
"""
This is a temporary class for handling GPG requests, and should be
replaced by a more general class used throughout the project.
"""
GNUPG_HOME = "~/.config/leap/gnupg"
GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS
def __init__(self, gpghome=GNUPG_HOME, gpgbinary=GNUPG_BINARY):
self.gpg = gnupg.GPG(gnupghome=gpghome, gpgbinary=gpgbinary)
def get_fingerprint(self, email):
"""
Find user's fingerprint based on their email.
"""
for key in self.gpg.list_keys():
for uid in key['uids']:
if re.search(email, uid):
return key['fingerprint']
def encrypt(self, data, recipient):
return self.gpg.encrypt(data, recipient)
def import_keys(self, data):
return self.gpg.import_keys(data)
# service configuration
port = 25
factory = SMTPFactory()
# this enables the use of this application with twistd
application = service.Application("LEAP SMTP Relay") # create the Application
service = internet.TCPServer(port, factory) # create the service
# add the service to the application
service.setServiceParent(application)
|