• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / relman-auto-nag / #4696

pending completion
#4696

push

coveralls-python

web-flow
Ensure SMTP over SSL verifies the server certificate (#2193)

This patch supplies an ssl context to the `SMTP_SSL` constructor, which enables certificate verification. The default context will use the system's trusted CA certificates. See https://docs.python.org/3/library/ssl.html#ssl-security for more.

Kudos to Martin Schobert and Tobias Ospelt of Pentagrid AG for reporting to Mozilla Security.

716 of 3566 branches covered (20.08%)

2 of 2 new or added lines in 1 file covered. (100.0%)

1925 of 8730 relevant lines covered (22.05%)

0.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.8
/bugbot/mail.py
1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
# You can obtain one at http://mozilla.org/MPL/2.0/.
4

5
import smtplib
1✔
6
import ssl
1✔
7
from email.mime.application import MIMEApplication
1✔
8
from email.mime.multipart import MIMEMultipart
1✔
9
from email.mime.text import MIMEText
1✔
10
from os.path import basename
1✔
11

12
from jinja2 import Environment, FileSystemLoader
1✔
13

14
from . import logger, utils
1✔
15

16
SMTP = "smtp.mozilla.org"
1✔
17
PORT = 465
1✔
18

19

20
def replaceUnicode(s):
1✔
21
    pos = 0
1✔
22
    ss = ""
1✔
23
    for i, c in enumerate(s):
1✔
24
        n = ord(c)
1✔
25
        if n > 128:
1✔
26
            ss += s[pos:i] + "&#" + str(n) + ";"
1✔
27
            pos = i + 1
1✔
28

29
    if pos < len(s):
1✔
30
        ss += s[pos:]
1✔
31
    return ss
1✔
32

33

34
def clean_cc(cc, to):
1✔
35
    to = set(to)
×
36
    cc = set(cc)
×
37
    cc = cc - to
×
38
    return list(sorted(cc))
×
39

40

41
def send_from_template(template_file, To, title, Cc=[], dryrun=False, **kwargs):
1✔
42
    login_info = utils.get_login_info()
×
43
    env = Environment(loader=FileSystemLoader("templates"))
×
44
    template = env.get_template(template_file)
×
45
    message = template.render(**kwargs)
×
46
    common = env.get_template("common.html")
×
47
    body = common.render(message=message, has_table=False)
×
48
    send(
×
49
        login_info["ldap_username"],
50
        To,
51
        "[bugbot] {}".format(title),
52
        body,
53
        Cc=Cc,
54
        html=True,
55
        login=login_info,
56
        dryrun=dryrun,
57
    )
58

59

60
def send(
1✔
61
    From, To, Subject, Body, Cc=[], Bcc=[], html=False, files=[], login={}, dryrun=False
62
):
63
    """Send an email"""
64

65
    if utils.get_config("common", "test", False):
×
66
        # just to send a dryrun email
67
        special = "<p><b>To: {}</b></p><p><b>Cc: {}</b></p>".format(To, Cc)
×
68
        i = Body.index("<body>") + len("<body>")
×
69
        Body = Body[:i] + special + Body[i:]
×
70
        ft = utils.get_config("common", "test_from_to", {})
×
71
        From = ft["from"]
×
72
        To = ft["to"]
×
73
        Cc = []
×
74

75
    if isinstance(To, str):
×
76
        To = [To]
×
77
    if isinstance(Cc, str):
×
78
        Cc = [Cc]
×
79
    if isinstance(Bcc, str):
×
80
        Bcc = [Bcc]
×
81

82
    Cc = clean_cc(Cc, To)
×
83

84
    subtype = "html" if html else "plain"
×
85
    message = MIMEMultipart()
×
86
    message["From"] = From
×
87
    message["To"] = ", ".join(To)
×
88
    message["Subject"] = Subject
×
89
    message["Cc"] = ", ".join(Cc)
×
90
    message["Bcc"] = ", ".join(Bcc)
×
91
    message["X-Mailer"] = "bugbot"
×
92

93
    if subtype == "html":
×
94
        Body = replaceUnicode(Body)
×
95
    message.attach(MIMEText(Body, subtype))
×
96

97
    for file in files:
×
98
        with open(file, "rb") as In:
×
99
            file = basename(file)
×
100
            part = MIMEApplication(In.read(), Name=basename(file))
×
101
            part["Content-Disposition"] = 'attachment; filename="%s"' % file
×
102
            message.attach(part)
×
103

104
    sendMail(From, To + Cc + Bcc, message.as_string(), login=login, dryrun=dryrun)
×
105

106

107
def sendMail(From, To, msg, login={}, dryrun=False):
1✔
108
    """Send an email"""
109
    if dryrun:
×
110
        out = "\n****************************\n"
×
111
        out += "* DRYRUN: not sending mail *\n"
×
112
        out += "****************************\n"
×
113
        out += "Receivers: {}\n".format(To)
×
114
        out += "Message:\n"
×
115
        out += msg
×
116
        logger.info(out)
×
117
        return
×
118

119
    if login is None:
×
120
        login = {}
×
121

122
    # TODO: default_login has been added to fix issues with old auto_nag
123
    # so need to remove that stuff once old auto_nag will have been removed.
124
    default_login = utils.get_login_info()
×
125
    smtp_server = login.get("smtp_server", default_login.get("smtp_server", SMTP))
×
126
    smtp_port = login.get("smtp_port", default_login.get("smtp_port", PORT))
×
127
    smtp_ssl = login.get("smtp_ssl", default_login.get("smtp_ssl", True))
×
128

129
    if smtp_ssl:
×
130
        mailserver = smtplib.SMTP_SSL(
×
131
            smtp_server, smtp_port, context=ssl.create_default_context()
132
        )
133
    else:
134
        mailserver = smtplib.SMTP(smtp_server, smtp_port)
×
135

136
    mailserver.set_debuglevel(1)
×
137
    if login:
×
138
        username = login.get("ldap_username")
×
139
        password = login.get("ldap_password")
×
140
        if username and password:
×
141
            mailserver.login(username, password)
×
142

143
    mailserver.sendmail(From, To, msg)
×
144
    mailserver.quit()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc