summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/mail.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/mail.py')
-rw-r--r--ansible_collections/community/general/plugins/modules/mail.py418
1 files changed, 418 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/mail.py b/ansible_collections/community/general/plugins/modules/mail.py
new file mode 100644
index 000000000..feaac6923
--- /dev/null
+++ b/ansible_collections/community/general/plugins/modules/mail.py
@@ -0,0 +1,418 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+---
+author:
+- Dag Wieers (@dagwieers)
+module: mail
+short_description: Send an email
+description:
+- This module is useful for sending emails from playbooks.
+- One may wonder why automate sending emails? In complex environments
+ there are from time to time processes that cannot be automated, either
+ because you lack the authority to make it so, or because not everyone
+ agrees to a common approach.
+- If you cannot automate a specific step, but the step is non-blocking,
+ sending out an email to the responsible party to make them perform their
+ part of the bargain is an elegant way to put the responsibility in
+ someone else's lap.
+- Of course sending out a mail can be equally useful as a way to notify
+ one or more people in a team that a specific action has been
+ (successfully) taken.
+extends_documentation_fragment:
+- community.general.attributes
+attributes:
+ check_mode:
+ support: none
+ diff_mode:
+ support: none
+options:
+ sender:
+ description:
+ - The email-address the mail is sent from. May contain address and phrase.
+ type: str
+ default: root
+ aliases: [ from ]
+ to:
+ description:
+ - The email-address(es) the mail is being sent to.
+ - This is a list, which may contain address and phrase portions.
+ type: list
+ elements: str
+ default: root
+ aliases: [ recipients ]
+ cc:
+ description:
+ - The email-address(es) the mail is being copied to.
+ - This is a list, which may contain address and phrase portions.
+ type: list
+ elements: str
+ default: []
+ bcc:
+ description:
+ - The email-address(es) the mail is being 'blind' copied to.
+ - This is a list, which may contain address and phrase portions.
+ type: list
+ elements: str
+ default: []
+ subject:
+ description:
+ - The subject of the email being sent.
+ required: true
+ type: str
+ aliases: [ msg ]
+ body:
+ description:
+ - The body of the email being sent.
+ type: str
+ username:
+ description:
+ - If SMTP requires username.
+ type: str
+ password:
+ description:
+ - If SMTP requires password.
+ type: str
+ host:
+ description:
+ - The mail server.
+ type: str
+ default: localhost
+ port:
+ description:
+ - The mail server port.
+ - This must be a valid integer between 1 and 65534
+ type: int
+ default: 25
+ attach:
+ description:
+ - A list of pathnames of files to attach to the message.
+ - Attached files will have their content-type set to C(application/octet-stream).
+ type: list
+ elements: path
+ default: []
+ headers:
+ description:
+ - A list of headers which should be added to the message.
+ - Each individual header is specified as C(header=value) (see example below).
+ type: list
+ elements: str
+ default: []
+ charset:
+ description:
+ - The character set of email being sent.
+ type: str
+ default: utf-8
+ subtype:
+ description:
+ - The minor mime type, can be either C(plain) or C(html).
+ - The major type is always C(text).
+ type: str
+ choices: [ html, plain ]
+ default: plain
+ secure:
+ description:
+ - If C(always), the connection will only send email if the connection is Encrypted.
+ If the server doesn't accept the encrypted connection it will fail.
+ - If C(try), the connection will attempt to setup a secure SSL/TLS session, before trying to send.
+ - If C(never), the connection will not attempt to setup a secure SSL/TLS session, before sending
+ - If C(starttls), the connection will try to upgrade to a secure SSL/TLS connection, before sending.
+ If it is unable to do so it will fail.
+ type: str
+ choices: [ always, never, starttls, try ]
+ default: try
+ timeout:
+ description:
+ - Sets the timeout in seconds for connection attempts.
+ type: int
+ default: 20
+ ehlohost:
+ description:
+ - Allows for manual specification of host for EHLO.
+ type: str
+ version_added: 3.8.0
+'''
+
+EXAMPLES = r'''
+- name: Example playbook sending mail to root
+ community.general.mail:
+ subject: System {{ ansible_hostname }} has been successfully provisioned.
+ delegate_to: localhost
+
+- name: Sending an e-mail using Gmail SMTP servers
+ community.general.mail:
+ host: smtp.gmail.com
+ port: 587
+ username: username@gmail.com
+ password: mysecret
+ to: John Smith <john.smith@example.com>
+ subject: Ansible-report
+ body: System {{ ansible_hostname }} has been successfully provisioned.
+ delegate_to: localhost
+
+- name: Send e-mail to a bunch of users, attaching files
+ community.general.mail:
+ host: 127.0.0.1
+ port: 2025
+ subject: Ansible-report
+ body: Hello, this is an e-mail. I hope you like it ;-)
+ from: jane@example.net (Jane Jolie)
+ to:
+ - John Doe <j.d@example.org>
+ - Suzie Something <sue@example.com>
+ cc: Charlie Root <root@localhost>
+ attach:
+ - /etc/group
+ - /tmp/avatar2.png
+ headers:
+ - Reply-To=john@example.com
+ - X-Special="Something or other"
+ charset: us-ascii
+ delegate_to: localhost
+
+- name: Sending an e-mail using the remote machine, not the Ansible controller node
+ community.general.mail:
+ host: localhost
+ port: 25
+ to: John Smith <john.smith@example.com>
+ subject: Ansible-report
+ body: System {{ ansible_hostname }} has been successfully provisioned.
+
+- name: Sending an e-mail using Legacy SSL to the remote machine
+ community.general.mail:
+ host: localhost
+ port: 25
+ to: John Smith <john.smith@example.com>
+ subject: Ansible-report
+ body: System {{ ansible_hostname }} has been successfully provisioned.
+ secure: always
+
+- name: Sending an e-mail using StartTLS to the remote machine
+ community.general.mail:
+ host: localhost
+ port: 25
+ to: John Smith <john.smith@example.com>
+ subject: Ansible-report
+ body: System {{ ansible_hostname }} has been successfully provisioned.
+ secure: starttls
+
+- name: Sending an e-mail using StartTLS, remote server, custom EHLO
+ community.general.mail:
+ host: some.smtp.host.tld
+ port: 25
+ ehlohost: my-resolvable-hostname.tld
+ to: John Smith <john.smith@example.com>
+ subject: Ansible-report
+ body: System {{ ansible_hostname }} has been successfully provisioned.
+ secure: starttls
+'''
+
+import os
+import smtplib
+import ssl
+import traceback
+from email import encoders
+from email.utils import parseaddr, formataddr, formatdate
+from email.mime.base import MIMEBase
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from email.header import Header
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import PY3
+from ansible.module_utils.common.text.converters import to_native
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ username=dict(type='str'),
+ password=dict(type='str', no_log=True),
+ host=dict(type='str', default='localhost'),
+ port=dict(type='int', default=25),
+ ehlohost=dict(type='str', default=None),
+ sender=dict(type='str', default='root', aliases=['from']),
+ to=dict(type='list', elements='str', default=['root'], aliases=['recipients']),
+ cc=dict(type='list', elements='str', default=[]),
+ bcc=dict(type='list', elements='str', default=[]),
+ subject=dict(type='str', required=True, aliases=['msg']),
+ body=dict(type='str'),
+ attach=dict(type='list', elements='path', default=[]),
+ headers=dict(type='list', elements='str', default=[]),
+ charset=dict(type='str', default='utf-8'),
+ subtype=dict(type='str', default='plain', choices=['html', 'plain']),
+ secure=dict(type='str', default='try', choices=['always', 'never', 'starttls', 'try']),
+ timeout=dict(type='int', default=20),
+ ),
+ required_together=[['password', 'username']],
+ )
+
+ username = module.params.get('username')
+ password = module.params.get('password')
+ host = module.params.get('host')
+ port = module.params.get('port')
+ local_hostname = module.params.get('ehlohost')
+ sender = module.params.get('sender')
+ recipients = module.params.get('to')
+ copies = module.params.get('cc')
+ blindcopies = module.params.get('bcc')
+ subject = module.params.get('subject')
+ body = module.params.get('body')
+ attach_files = module.params.get('attach')
+ headers = module.params.get('headers')
+ charset = module.params.get('charset')
+ subtype = module.params.get('subtype')
+ secure = module.params.get('secure')
+ timeout = module.params.get('timeout')
+
+ code = 0
+ secure_state = False
+ sender_phrase, sender_addr = parseaddr(sender)
+
+ if not body:
+ body = subject
+
+ try:
+ if secure != 'never':
+ try:
+ if PY3:
+ smtp = smtplib.SMTP_SSL(host=host, port=port, local_hostname=local_hostname, timeout=timeout)
+ else:
+ smtp = smtplib.SMTP_SSL(local_hostname=local_hostname, timeout=timeout)
+ code, smtpmessage = smtp.connect(host, port)
+ secure_state = True
+ except ssl.SSLError as e:
+ if secure == 'always':
+ module.fail_json(rc=1, msg='Unable to start an encrypted session to %s:%s: %s' %
+ (host, port, to_native(e)), exception=traceback.format_exc())
+ except Exception:
+ pass
+
+ if not secure_state:
+ if PY3:
+ smtp = smtplib.SMTP(host=host, port=port, local_hostname=local_hostname, timeout=timeout)
+ else:
+ smtp = smtplib.SMTP(local_hostname=local_hostname, timeout=timeout)
+ code, smtpmessage = smtp.connect(host, port)
+
+ except smtplib.SMTPException as e:
+ module.fail_json(rc=1, msg='Unable to Connect %s:%s: %s' % (host, port, to_native(e)), exception=traceback.format_exc())
+
+ try:
+ smtp.ehlo()
+ except smtplib.SMTPException as e:
+ module.fail_json(rc=1, msg='Helo failed for host %s:%s: %s' % (host, port, to_native(e)), exception=traceback.format_exc())
+
+ if int(code) > 0:
+ if not secure_state and secure in ('starttls', 'try'):
+ if smtp.has_extn('STARTTLS'):
+ try:
+ smtp.starttls()
+ secure_state = True
+ except smtplib.SMTPException as e:
+ module.fail_json(rc=1, msg='Unable to start an encrypted session to %s:%s: %s' %
+ (host, port, to_native(e)), exception=traceback.format_exc())
+ try:
+ smtp.ehlo()
+ except smtplib.SMTPException as e:
+ module.fail_json(rc=1, msg='Helo failed for host %s:%s: %s' % (host, port, to_native(e)), exception=traceback.format_exc())
+ else:
+ if secure == 'starttls':
+ module.fail_json(rc=1, msg='StartTLS is not offered on server %s:%s' % (host, port))
+
+ if username and password:
+ if smtp.has_extn('AUTH'):
+ try:
+ smtp.login(username, password)
+ except smtplib.SMTPAuthenticationError:
+ module.fail_json(rc=1, msg='Authentication to %s:%s failed, please check your username and/or password' % (host, port))
+ except smtplib.SMTPException:
+ module.fail_json(rc=1, msg='No Suitable authentication method was found on %s:%s' % (host, port))
+ else:
+ module.fail_json(rc=1, msg="No Authentication on the server at %s:%s" % (host, port))
+
+ if not secure_state and (username and password):
+ module.warn('Username and Password was sent without encryption')
+
+ msg = MIMEMultipart(_charset=charset)
+ msg['From'] = formataddr((sender_phrase, sender_addr))
+ msg['Date'] = formatdate(localtime=True)
+ msg['Subject'] = Header(subject, charset)
+ msg.preamble = "Multipart message"
+
+ for header in headers:
+ # NOTE: Backward compatible with old syntax using '|' as delimiter
+ for hdr in [x.strip() for x in header.split('|')]:
+ try:
+ h_key, h_val = hdr.split('=')
+ h_val = to_native(Header(h_val, charset))
+ msg.add_header(h_key, h_val)
+ except Exception:
+ module.warn("Skipping header '%s', unable to parse" % hdr)
+
+ if 'X-Mailer' not in msg:
+ msg.add_header('X-Mailer', 'Ansible mail module')
+
+ addr_list = []
+ for addr in [x.strip() for x in blindcopies]:
+ addr_list.append(parseaddr(addr)[1]) # address only, w/o phrase
+
+ to_list = []
+ for addr in [x.strip() for x in recipients]:
+ to_list.append(formataddr(parseaddr(addr)))
+ addr_list.append(parseaddr(addr)[1]) # address only, w/o phrase
+ msg['To'] = ", ".join(to_list)
+
+ cc_list = []
+ for addr in [x.strip() for x in copies]:
+ cc_list.append(formataddr(parseaddr(addr)))
+ addr_list.append(parseaddr(addr)[1]) # address only, w/o phrase
+ msg['Cc'] = ", ".join(cc_list)
+
+ part = MIMEText(body + "\n\n", _subtype=subtype, _charset=charset)
+ msg.attach(part)
+
+ # NOTE: Backware compatibility with old syntax using space as delimiter is not retained
+ # This breaks files with spaces in it :-(
+ for filename in attach_files:
+ try:
+ part = MIMEBase('application', 'octet-stream')
+ with open(filename, 'rb') as fp:
+ part.set_payload(fp.read())
+ encoders.encode_base64(part)
+ part.add_header('Content-disposition', 'attachment', filename=os.path.basename(filename))
+ msg.attach(part)
+ except Exception as e:
+ module.fail_json(rc=1, msg="Failed to send community.general.mail: can't attach file %s: %s" %
+ (filename, to_native(e)), exception=traceback.format_exc())
+
+ composed = msg.as_string()
+
+ try:
+ result = smtp.sendmail(sender_addr, set(addr_list), composed)
+ except Exception as e:
+ module.fail_json(rc=1, msg="Failed to send mail to '%s': %s" %
+ (", ".join(set(addr_list)), to_native(e)), exception=traceback.format_exc())
+
+ smtp.quit()
+
+ if result:
+ for key in result:
+ module.warn("Failed to send mail to '%s': %s %s" % (key, result[key][0], result[key][1]))
+ module.exit_json(msg='Failed to send mail to at least one recipient', result=result)
+
+ module.exit_json(msg='Mail sent successfully', result=result)
+
+
+if __name__ == '__main__':
+ main()