mercurial/mail.py
author Matt Mackall <mpm@selenic.com>
Thu, 17 Jan 2008 13:51:59 -0600
changeset 5866 dc6ed2736c81
parent 5749 4fba4fee0718
child 5947 528c986f0162
permissions -rw-r--r--
patchbomb: prompt only once for SMTP password - simplify mail._sendmail to be a function rather than a class - simplify connect to return a function rather than a class - move exception handling from mail.sendmail to mail.connect - use a single connection for all messages in patchbomb
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     1
# mail.py - mail sending bits for mercurial
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     2
#
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     3
# Copyright 2006 Matt Mackall <mpm@selenic.com>
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     4
#
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     5
# This software may be used and distributed according to the terms
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     6
# of the GNU General Public License, incorporated herein by reference.
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     7
3891
6b4127c7d52a Simplify i18n imports
Matt Mackall <mpm@selenic.com>
parents: 3877
diff changeset
     8
from i18n import _
4096
49237d6ae97d merge with crew-stable
Alexis S. L. Carvalho <alexis@cecm.usp.br>
parents: 3963 4093
diff changeset
     9
import os, smtplib, templater, util, socket
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    10
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    11
def _smtp(ui):
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    12
    '''send mail using smtp.'''
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    13
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    14
    local_hostname = ui.config('smtp', 'local_hostname')
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    15
    s = smtplib.SMTP(local_hostname=local_hostname)
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    16
    mailhost = ui.config('smtp', 'host')
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    17
    if not mailhost:
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    18
        raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    19
    mailport = int(ui.config('smtp', 'port', 25))
2964
26c8d37496c2 fix typo in mail.py
Alexis S. L. Carvalho <alexis@cecm.usp.br>
parents: 2929
diff changeset
    20
    ui.note(_('sending mail: smtp host %s, port %s\n') %
26c8d37496c2 fix typo in mail.py
Alexis S. L. Carvalho <alexis@cecm.usp.br>
parents: 2929
diff changeset
    21
            (mailhost, mailport))
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    22
    s.connect(host=mailhost, port=mailport)
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    23
    if ui.configbool('smtp', 'tls'):
4093
669f99f78db0 mail.py: don't try to use TLS if python doesn't have SSL support
Alexis S. L. Carvalho <alexis@cecm.usp.br>
parents: 2964
diff changeset
    24
        if not hasattr(socket, 'ssl'):
669f99f78db0 mail.py: don't try to use TLS if python doesn't have SSL support
Alexis S. L. Carvalho <alexis@cecm.usp.br>
parents: 2964
diff changeset
    25
            raise util.Abort(_("can't use TLS: Python SSL support "
669f99f78db0 mail.py: don't try to use TLS if python doesn't have SSL support
Alexis S. L. Carvalho <alexis@cecm.usp.br>
parents: 2964
diff changeset
    26
                               "not installed"))
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    27
        ui.note(_('(using tls)\n'))
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    28
        s.ehlo()
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    29
        s.starttls()
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    30
        s.ehlo()
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    31
    username = ui.config('smtp', 'username')
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    32
    password = ui.config('smtp', 'password')
5749
4fba4fee0718 Patchbomb: Prompt password when using SMTP/TLS and no password in .hgrc.
Arun Thomas <arun.thomas@gmail.com>
parents: 5472
diff changeset
    33
    if username and not password:
4fba4fee0718 Patchbomb: Prompt password when using SMTP/TLS and no password in .hgrc.
Arun Thomas <arun.thomas@gmail.com>
parents: 5472
diff changeset
    34
        password = ui.getpass()
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    35
    if username and password:
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    36
        ui.note(_('(authenticating to mail server as %s)\n') %
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    37
                  (username))
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    38
        s.login(username, password)
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    39
    return s
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    40
5866
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    41
def _sendmail(ui, sender, recipients, msg):
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    42
    '''send mail using sendmail.'''
5866
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    43
    program = ui.config('email', 'method')
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    44
    cmdline = '%s -f %s %s' % (program, templater.email(sender),
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    45
                               ' '.join(map(templater.email, recipients)))
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    46
    ui.note(_('sending mail: %s\n') % cmdline)
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    47
    fp = os.popen(cmdline, 'w')
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    48
    fp.write(msg)
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    49
    ret = fp.close()
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    50
    if ret:
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    51
        raise util.Abort('%s %s' % (
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    52
            os.path.basename(program.split(None, 1)[0]),
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    53
            util.explain_exit(ret)[0]))
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    54
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    55
def connect(ui):
5866
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    56
    '''make a mail connection. return a function to send mail.
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    57
    call as sendmail(sender, list-of-recipients, msg).'''
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    58
5866
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    59
    func =  _sendmail
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    60
    if ui.config('email', 'method', 'smtp') == 'smtp':
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    61
        func = _smtp(ui)
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    62
5866
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    63
    def send(ui, sender, recipients, msg):
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    64
        try:
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    65
            return func.sendmail(sender, recipients, msg)
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    66
        except smtplib.SMTPRecipientsRefused, inst:
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    67
            recipients = [r[1] for r in inst.recipients.values()]
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    68
            raise util.Abort('\n' + '\n'.join(recipients))
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    69
        except smtplib.SMTPException, inst:
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    70
            raise util.Abort(inst)
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    71
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    72
    return send
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    73
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    74
def sendmail(ui, sender, recipients, msg):
5472
23889160905a Catch smtp exceptions
Christian Ebert <blacktrash@gmx.net>
parents: 4489
diff changeset
    75
    try:
5866
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    76
        send = connect(ui)
dc6ed2736c81 patchbomb: prompt only once for SMTP password
Matt Mackall <mpm@selenic.com>
parents: 5749
diff changeset
    77
        return send(sender, recipients, msg)
5472
23889160905a Catch smtp exceptions
Christian Ebert <blacktrash@gmx.net>
parents: 4489
diff changeset
    78
    except smtplib.SMTPRecipientsRefused, inst:
23889160905a Catch smtp exceptions
Christian Ebert <blacktrash@gmx.net>
parents: 4489
diff changeset
    79
        recipients = [r[1] for r in inst.recipients.values()]
23889160905a Catch smtp exceptions
Christian Ebert <blacktrash@gmx.net>
parents: 4489
diff changeset
    80
        raise util.Abort('\n' + '\n'.join(recipients))
23889160905a Catch smtp exceptions
Christian Ebert <blacktrash@gmx.net>
parents: 4489
diff changeset
    81
    except smtplib.SMTPException, inst:
23889160905a Catch smtp exceptions
Christian Ebert <blacktrash@gmx.net>
parents: 4489
diff changeset
    82
        raise util.Abort(inst)
4489
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    83
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    84
def validateconfig(ui):
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    85
    '''determine if we have enough config data to try sending email.'''
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    86
    method = ui.config('email', 'method', 'smtp')
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    87
    if method == 'smtp':
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    88
        if not ui.config('smtp', 'host'):
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    89
            raise util.Abort(_('smtp specified as email transport, '
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    90
                               'but no smtp host configured'))
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    91
    else:
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    92
        if not util.find_exe(method):
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    93
            raise util.Abort(_('%r specified as email transport, '
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
    94
                               'but not in PATH') % method)