sendmail.py 7.78 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-
#
# This file is part of GetTor, a Tor Browser distribution system.
#
# :authors: isra <ilv@torproject.org>
#           see also AUTHORS file
#
# :copyright:   (c) 2008-2014, The Tor Project, Inc.
#               (c) 2014-2018, Israel Leiva
#
# :license: This is Free Software. See LICENSE for license information.

from __future__ import absolute_import

import hashlib

import configparser
18
19
from email.mime.text import MIMEText

20
from twisted.internet import defer
21
from twisted.mail import smtp
22

Hiro's avatar
Hiro committed
23
from ...utils.db import SQLite3 as DB
24
from ...utils.commons import log
25
from ...utils import strings
26
27


28
from email.mime.text import MIMEText
29
30
31
32
33
34
35
36
37
38
39
40
41
class Sendmail(object):
    """
    Class for sending email replies to `help` and `links` requests.
    """
    def __init__(self, settings):
        """
        Constructor. It opens and stores a connection to the database.
        :dbname: reads from configs
        """
        self.settings = settings
        dbname = self.settings.get("dbname")
        self.conn = DB(dbname)

42
43
    def __del__(self):
        del self.conn
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

    def get_interval(self):
        """
        Get time interval for service periodicity.

        :return: time interval (float) in seconds.
        """
        return self.settings.get("sendmail_interval")


    def sendmail_callback(self, message):
        """
        Callback invoked after an email has been sent.

        :param message (string): Success details from the server.
        """
60
        log.debug("Email sent successfully.")
61
62
63
64
65

    def sendmail_errback(self, error):
        """
        Errback if we don't/can't send the message.
        """
66
        log.warn("Could not send email.")
67
        raise error
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

    def sendmail(self, email_addr, subject, body):
        """
        Send an email message. It creates a plain text message, set headers
        and content and finally send it.

        :param email_addr (str): email address of the recipient.
        :param subject (str): subject of the message.
        :param content (str): content of the message.

        :return: deferred whose callback/errback will handle the SMTP
        execution details.
        """
        log.debug("Creating plain text email")
        message = MIMEText(body)

        message['Subject'] = subject
85
        message['From'] = self.settings.get("sendmail_addr")
86
87
88
89
        message['To'] = email_addr

        log.debug("Calling asynchronous sendmail.")

90
        return smtp.sendmail(
91
92
            self.settings.get("sendmail_host"), self.settings.get("sendmail_addr"), email_addr, message,
            requireTransportSecurity=True
93
94
        ).addCallback(self.sendmail_callback).addErrback(self.sendmail_errback)

Cecylia Bocovich's avatar
Cecylia Bocovich committed
95
96
97
98
99
100
101
102
103
    def build_locale_string(self, locales):
        locale_string = ""
        for locale in locales:
            locale_string += "\t" + locale[0] + "\n"
        return locale_string

    def build_help_body_message(self, locale_string):
        body_msg = strings._("body_intro")
        body_msg += strings._("help_body_intro")
104
        body_msg += strings._("help_body_support")
Cecylia Bocovich's avatar
Cecylia Bocovich committed
105
106
107
108
109
        body_msg += "\twindows\n\tlinux\n\tosx\n\n"
        body_msg += strings._("help_body_respond")
        body_msg += strings._("help_body_locale")
        body_msg += locale_string + "\n"
        body_msg += strings._("help_body_example").format("Windows", "Arabic", "windows ar")
110
111
112
113
114
115
116
117
118

        return body_msg


    def build_link_strings(self, links, platform, locale):
        """
        Build the links strings
        """

Cecylia Bocovich's avatar
Cecylia Bocovich committed
119
        link_msg = ""
120
121
122
123
124
125
126
127
128

        for link in links:
            provider = link[5]
            version = link[4]
            arch = link[3]
            url = link[0]
            file = link[7]
            sig_url = url + ".asc"

Cecylia Bocovich's avatar
Cecylia Bocovich committed
129
            link_str = "\t{}: {}\n".format(provider, url)
130

Cecylia Bocovich's avatar
Cecylia Bocovich committed
131
            link_str += "\tSignature file: {}\n".format(sig_url)
132
133
134
135
136
137
138

            link_msg = "{}\n{}".format(link_msg, link_str)

        return link_msg, file


    def build_body_message(self, link_msg, platform, file):
139
140
141
142
143
144
145
146
147
148
        signature_strings = {
            "windows":"links_body_windows",
            "linux":"links_body_linux",
            "osx":"links_body_osx"
        }
        signature_cmds = {
            "windows":"gpgv --keyring .\\tor.keyring Downloads\\{0}.asc Downloads\\{0}",
            "linux":"gpgv --keyring ./tor.keyring ~/Downloads/{}{{.asc,}}",
            "osx":"gpgv --keyring ./tor.keyring ~/Downloads/{}{{.asc,}}"
        }
Cecylia Bocovich's avatar
Cecylia Bocovich committed
149
150
151
152
        body_msg = strings._("body_intro")
        body_msg += strings._("links_body_platform").format(platform)
        body_msg += strings._("links_body_step1").format(link_msg)
        body_msg += strings._("links_body_archive").format(file)
153
154
        body_msg += strings._("links_body_internet_archive")
        body_msg += strings._("links_body_google_drive")
Cecylia Bocovich's avatar
Cecylia Bocovich committed
155
        body_msg += strings._("links_body_step2")
156
157
        body_msg += strings._(signature_strings[platform])
        body_msg += strings._("links_body_all").format(signature_cmds[platform].format(file))
Cecylia Bocovich's avatar
Cecylia Bocovich committed
158
        body_msg += strings._("links_body_step3")
159
160
161
162

        return body_msg


163
164
    @defer.inlineCallbacks
    def get_new(self):
Hiro's avatar
Hiro committed
165
        """
166
167
168
169
170
        Get new requests to process. This will define the `main loop` of
        the Sendmail service.
        """

        # Manage help and links messages separately
171
172
        requests = yield self.conn.get_requests(
            status="ONHOLD", service="email"
173
174
        )

175
176
        strings.load_strings("en")
        try:
177

178
179
180
181
182
183
            for request in requests:
                id = request[0]
                command = request[1]
                platform = request[2]
                language = request[3]
                date = request[5]
184

185
186
187
188
189
190
191
                if not language:
                    language = 'en'

                body_msg =""
                subject_msg =""

                if command == "help":
192

Cecylia Bocovich's avatar
Cecylia Bocovich committed
193
194
                    locales = yield self.conn.get_locales()
                    locale_string = self.build_locale_string(locales)
195

196
                    # build message
Cecylia Bocovich's avatar
Cecylia Bocovich committed
197
                    body_msg = self.build_help_body_message(locale_string)
198
                    subject_msg = strings._("help_subject")
Hiro's avatar
Hiro committed
199

200
                elif command == "links":
201
                    log.debug("Getting links for {} {}.".format(platform, language))
202
                    links = yield self.conn.get_links(
203
                        platform=platform, language=language, status="ACTIVE"
204
205
206
                    )

                    # build message
207
                    link_msg, file = self.build_link_strings(links, platform, language)
208
                    body_msg = self.build_body_message(link_msg, platform, file)
209
                    subject_msg = strings._("links_subject")
210
                else:
211
                    log.warn("Invalid gettor command {}.".format(command))
212
213
                    yield self.conn.remove_request(
                        id=id, service="email", date=date
214
215
                    )

216
                log.debug("Sending {} message.".format(request[1]))
217

218
219
220
221
222
                yield self.sendmail(
                    email_addr=id,
                    subject=subject_msg,
                    body=body_msg
                )
223

224
225
226
227
                yield self.conn.update_stats(
                    command=command, platform=platform, language=language,
                    service="email"
                )
228

229
230
231
232
233
234
235
236
237
                yield self.conn.remove_request(
                    id=id, service="email", date=date
                )

        except smtp.SMTPClientError as e:
            if e.code == 501: # Bad recipient address syntax
                yield self.conn.remove_request(
                    id=id, service="email", date=date
                )
238
239
240
            log.error(
                strings.redact_emails("Error sending email:{}.".format(e))
                )
241

242
        except Exception as e:
243
244
245
            log.error(
                strings.redact_emails("Error sending email:{}.".format(e))
                )