smtp: verify the certificate of the SMTP server for STARTTLS/SMTPS
Before this patch, the certificate of the SMTP server for STARTTLS or
SMTPS isn't verified.
This may cause man-in-the-middle security problem (stealing
authentication information), even though SMTP channel itself is
encrypted by SSL.
When "[smtp] tls" is configured as "smtps" or "starttls", this patch:
- uses classes introduced by preceding patches instead of "SMTP" or
"SMTP_SSL" of smtplib, and
- verifies the certificate of the SMTP server, if "[smtp]
verifycert" is configured as other than False
"[smtp] verifycert" can be configured in 3 levels:
- "strict":
This verifies peer certificate, and aborts if:
- peer certification is not valid, or
- no configuration in "[hostfingerprints]" and "[web] cacerts"
This is default value of "[smtp] verifycert" for security.
- "loose":
This verifies peer certificate, and aborts if peer certification is
not valid.
This just shows warning message ("certificate not verified"), if
there is no configuration in "[hostfingerprints]" and "[web]
cacerts".
This is as same as verification for HTTPS connection.
- False(no verification):
Peer certificate is not verified.
This is as same as the behavior before this patch series.
"hg email --insecure" uses "loose" level, and ignores "[web] cacerts"
as same as push/pull/etc... with --insecure.
Ignoring "[web] cacerts" configuration for "hg email --insecure" is
already done in "dispatch._dispatch()" by looking "insecure" up in the
table of command options.
--- a/hgext/patchbomb.py Tue Mar 26 02:28:10 2013 +0900
+++ b/hgext/patchbomb.py Tue Mar 26 02:28:10 2013 +0900
@@ -540,7 +540,13 @@
fp.close()
else:
if not sendmail:
- sendmail = mail.connect(ui, mbox=mbox)
+ verifycert = ui.config('smtp', 'verifycert')
+ if opts.get('insecure'):
+ ui.setconfig('smtp', 'verifycert', 'loose')
+ try:
+ sendmail = mail.connect(ui, mbox=mbox)
+ finally:
+ ui.setconfig('smtp', 'verifycert', verifycert)
ui.status(_('sending '), subj, ' ...\n')
ui.progress(_('sending'), i, item=subj, total=len(msgs))
if not mbox:
--- a/mercurial/help/config.txt Tue Mar 26 02:28:10 2013 +0900
+++ b/mercurial/help/config.txt Tue Mar 26 02:28:10 2013 +0900
@@ -1052,6 +1052,16 @@
Optional. Method to enable TLS when connecting to mail server: starttls,
smtps or none. Default: none.
+``verifycert``
+ Optional. Verification for the certificate of mail server, when
+ ``tls`` is starttls or smtps. "strict", "loose" or False. For
+ "strict" or "loose", the certificate is verified as same as the
+ verification for HTTPS connections (see ``[hostfingerprints]`` and
+ ``[web] cacerts`` also). For "strict", sending email is also
+ aborted, if there is no configuration for mail server in
+ ``[hostfingerprints]`` and ``[web] cacerts``. --insecure for
+ :hg:`email` overwrites this as "loose". Default: "strict".
+
``username``
Optional. User name for authenticating with the SMTP server.
Default: none.
--- a/mercurial/mail.py Tue Mar 26 02:28:10 2013 +0900
+++ b/mercurial/mail.py Tue Mar 26 02:28:10 2013 +0900
@@ -92,14 +92,25 @@
smtps = tls == 'smtps'
if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
raise util.Abort(_("can't use TLS: Python SSL support not installed"))
- if smtps:
- ui.note(_('(using smtps)\n'))
- s = smtplib.SMTP_SSL(local_hostname=local_hostname)
- else:
- s = smtplib.SMTP(local_hostname=local_hostname)
mailhost = ui.config('smtp', 'host')
if not mailhost:
raise util.Abort(_('smtp.host not configured - cannot send mail'))
+ verifycert = ui.config('smtp', 'verifycert', 'strict')
+ if verifycert not in ['strict', 'loose']:
+ if util.parsebool(verifycert) is not False:
+ raise util.Abort(_('invalid smtp.verifycert configuration: %s')
+ % (verifycert))
+ if (starttls or smtps) and verifycert:
+ sslkwargs = sslutil.sslkwargs(ui, mailhost)
+ else:
+ sslkwargs = {}
+ if smtps:
+ ui.note(_('(using smtps)\n'))
+ s = SMTPS(sslkwargs, local_hostname=local_hostname)
+ elif starttls:
+ s = STARTTLS(sslkwargs, local_hostname=local_hostname)
+ else:
+ s = smtplib.SMTP(local_hostname=local_hostname)
mailport = util.getport(ui.config('smtp', 'port', 25))
ui.note(_('sending mail: smtp host %s, port %s\n') %
(mailhost, mailport))
@@ -109,6 +120,9 @@
s.ehlo()
s.starttls()
s.ehlo()
+ if (starttls or smtps) and verifycert:
+ ui.note(_('(verifying remote certificate)\n'))
+ sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
username = ui.config('smtp', 'username')
password = ui.config('smtp', 'password')
if username and not password: