1# -*- coding: utf-8 -*-
2#
3# This file is part of urlwatch (https://thp.io/2008/urlwatch/).
4# Copyright (c) 2008-2021 Thomas Perl <m@thp.io>
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11# 1. Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13# 2. Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in the
15#    documentation and/or other materials provided with the distribution.
16# 3. The name of the author may not be used to endorse or promote products
17#    derived from this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31import smtplib
32import getpass
33import subprocess
34import logging
35
36try:
37    import keyring
38except ImportError:
39    keyring = None
40
41import email.mime.multipart
42import email.mime.text
43import email.utils
44
45logger = logging.getLogger(__name__)
46
47
48class Mailer(object):
49    def send(self, msg):
50        raise NotImplementedError
51
52    def msg_plain(self, from_email, to_email, subject, body):
53        msg = email.mime.text.MIMEText(body, 'plain', 'utf-8')
54        msg['Subject'] = subject
55        msg['From'] = from_email
56        msg['To'] = to_email
57        msg['Date'] = email.utils.formatdate()
58
59        return msg
60
61    def msg_html(self, from_email, to_email, subject, body_text, body_html):
62        msg = email.mime.multipart.MIMEMultipart('alternative')
63        msg['Subject'] = subject
64        msg['From'] = from_email
65        msg['To'] = to_email
66        msg['Date'] = email.utils.formatdate()
67
68        msg.attach(email.mime.text.MIMEText(body_text, 'plain', 'utf-8'))
69        msg.attach(email.mime.text.MIMEText(body_html, 'html', 'utf-8'))
70
71        return msg
72
73
74class SMTPMailer(Mailer):
75    def __init__(self, smtp_user, smtp_server, smtp_port, tls, auth, insecure_password=None):
76        self.smtp_server = smtp_server
77        self.smtp_user = smtp_user
78        self.smtp_port = smtp_port
79        self.tls = tls
80        self.auth = auth
81        self.insecure_password = insecure_password
82
83    def send(self, msg):
84        s = smtplib.SMTP(self.smtp_server, self.smtp_port)
85        s.ehlo()
86
87        if self.tls:
88            s.starttls()
89
90        if self.auth:
91            if self.insecure_password:
92                passwd = self.insecure_password
93            elif keyring is not None:
94                passwd = keyring.get_password(self.smtp_server, self.smtp_user)
95                if passwd is None:
96                    raise ValueError('No password available in keyring for {}, {}'
97                                     .format(self.smtp_server, self.smtp_user))
98            else:
99                raise ValueError('SMTP auth is enabled, but insecure_password is not set and keyring is not available')
100            s.login(self.smtp_user, passwd)
101
102        s.sendmail(msg['From'], msg['To'].split(','), msg.as_string())
103        s.quit()
104
105
106class SendmailMailer(Mailer):
107    def __init__(self, sendmail_path):
108        self.sendmail_path = sendmail_path
109
110    def send(self, msg):
111        p = subprocess.Popen([self.sendmail_path, '-oi', '-f', msg['From'], msg['To']],
112                             stdin=subprocess.PIPE,
113                             stderr=subprocess.PIPE,
114                             universal_newlines=True)
115        result = p.communicate(msg.as_string())
116        if p.returncode:
117            logger.error('Sendmail failed with {result}'.format(result=result))
118
119
120def have_password(smtp_server, from_email):
121    return keyring.get_password(smtp_server, from_email) is not None
122
123
124def set_password(smtp_server, from_email):
125    ''' Set the keyring password for the mail connection. Interactive.'''
126    if keyring is None:
127        raise ImportError('keyring module missing - service unsupported')
128
129    password = getpass.getpass(prompt='Enter password for {} using {}: '.format(from_email, smtp_server))
130    keyring.set_password(smtp_server, from_email, password)
131