1#!/usr/local/bin/python3.8
2from __future__ import (absolute_import, division, print_function)
3# Copyright 2019-2020 Fortinet, Inc.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18__metaclass__ = type
19
20ANSIBLE_METADATA = {'status': ['preview'],
21                    'supported_by': 'community',
22                    'metadata_version': '1.1'}
23
24DOCUMENTATION = '''
25---
26module: fortios_spamfilter_fortishield
27short_description: Configure FortiGuard - AntiSpam in Fortinet's FortiOS and FortiGate.
28description:
29    - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the
30      user to set and modify spamfilter feature and fortishield category.
31      Examples include all parameters and values need to be adjusted to datasources before usage.
32      Tested with FOS v6.0.0
33version_added: "2.10"
34author:
35    - Link Zheng (@chillancezen)
36    - Jie Xue (@JieX19)
37    - Hongbin Lu (@fgtdev-hblu)
38    - Frank Shen (@frankshen01)
39    - Miguel Angel Munoz (@mamunozgonzalez)
40    - Nicolas Thomas (@thomnico)
41notes:
42    - Legacy fortiosapi has been deprecated, httpapi is the preferred way to run playbooks
43
44requirements:
45    - ansible>=2.9.0
46options:
47    access_token:
48        description:
49            - Token-based authentication.
50              Generated from GUI of Fortigate.
51        type: str
52        required: false
53    enable_log:
54        description:
55            - Enable/Disable logging for task.
56        type: bool
57        required: false
58        default: false
59    vdom:
60        description:
61            - Virtual domain, among those defined previously. A vdom is a
62              virtual instance of the FortiGate that can be configured and
63              used as a different unit.
64        type: str
65        default: root
66
67    spamfilter_fortishield:
68        description:
69            - Configure FortiGuard - AntiSpam.
70        default: null
71        type: dict
72        suboptions:
73            spam_submit_force:
74                description:
75                    - Enable/disable force insertion of a new mime entity for the submission text.
76                type: str
77                choices:
78                    - enable
79                    - disable
80            spam_submit_srv:
81                description:
82                    - Hostname of the spam submission server.
83                type: str
84            spam_submit_txt2htm:
85                description:
86                    - Enable/disable conversion of text email to HTML email.
87                type: str
88                choices:
89                    - enable
90                    - disable
91'''
92
93EXAMPLES = '''
94- hosts: fortigates
95  collections:
96    - fortinet.fortios
97  connection: httpapi
98  vars:
99   vdom: "root"
100   ansible_httpapi_use_ssl: yes
101   ansible_httpapi_validate_certs: no
102   ansible_httpapi_port: 443
103  tasks:
104  - name: Configure FortiGuard - AntiSpam.
105    fortios_spamfilter_fortishield:
106      vdom:  "{{ vdom }}"
107      spamfilter_fortishield:
108        spam_submit_force: "enable"
109        spam_submit_srv: "<your_own_value>"
110        spam_submit_txt2htm: "enable"
111
112'''
113
114RETURN = '''
115build:
116  description: Build number of the fortigate image
117  returned: always
118  type: str
119  sample: '1547'
120http_method:
121  description: Last method used to provision the content into FortiGate
122  returned: always
123  type: str
124  sample: 'PUT'
125http_status:
126  description: Last result given by FortiGate on last operation applied
127  returned: always
128  type: str
129  sample: "200"
130mkey:
131  description: Master key (id) used in the last call to FortiGate
132  returned: success
133  type: str
134  sample: "id"
135name:
136  description: Name of the table used to fulfill the request
137  returned: always
138  type: str
139  sample: "urlfilter"
140path:
141  description: Path of the table used to fulfill the request
142  returned: always
143  type: str
144  sample: "webfilter"
145revision:
146  description: Internal revision number
147  returned: always
148  type: str
149  sample: "17.0.2.10658"
150serial:
151  description: Serial number of the unit
152  returned: always
153  type: str
154  sample: "FGVMEVYYQT3AB5352"
155status:
156  description: Indication of the operation's result
157  returned: always
158  type: str
159  sample: "success"
160vdom:
161  description: Virtual domain used
162  returned: always
163  type: str
164  sample: "root"
165version:
166  description: Version of the FortiGate
167  returned: always
168  type: str
169  sample: "v5.6.3"
170
171'''
172from ansible.module_utils.basic import AnsibleModule
173from ansible.module_utils.connection import Connection
174from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
175from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
176from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
177from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
178from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
179from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
180from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
181
182
183def filter_spamfilter_fortishield_data(json):
184    option_list = ['spam_submit_force', 'spam_submit_srv', 'spam_submit_txt2htm']
185    dictionary = {}
186
187    for attribute in option_list:
188        if attribute in json and json[attribute] is not None:
189            dictionary[attribute] = json[attribute]
190
191    return dictionary
192
193
194def underscore_to_hyphen(data):
195    if isinstance(data, list):
196        for i, elem in enumerate(data):
197            data[i] = underscore_to_hyphen(elem)
198    elif isinstance(data, dict):
199        new_data = {}
200        for k, v in data.items():
201            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
202        data = new_data
203
204    return data
205
206
207def spamfilter_fortishield(data, fos):
208    vdom = data['vdom']
209    spamfilter_fortishield_data = data['spamfilter_fortishield']
210    filtered_data = underscore_to_hyphen(filter_spamfilter_fortishield_data(spamfilter_fortishield_data))
211
212    return fos.set('spamfilter',
213                   'fortishield',
214                   data=filtered_data,
215                   vdom=vdom)
216
217
218def is_successful_status(status):
219    return status['status'] == "success" or \
220        status['http_method'] == "DELETE" and status['http_status'] == 404
221
222
223def fortios_spamfilter(data, fos):
224
225    if data['spamfilter_fortishield']:
226        resp = spamfilter_fortishield(data, fos)
227    else:
228        fos._module.fail_json(msg='missing task body: %s' % ('spamfilter_fortishield'))
229
230    return not is_successful_status(resp), \
231        resp['status'] == "success" and \
232        (resp['revision_changed'] if 'revision_changed' in resp else True), \
233        resp
234
235
236versioned_schema = {
237    "type": "dict",
238    "children": {
239        "spam_submit_srv": {
240            "type": "string",
241            "revisions": {
242                "v6.0.11": True,
243                "v6.0.0": True,
244                "v6.0.5": True
245            }
246        },
247        "spam_submit_txt2htm": {
248            "type": "string",
249            "options": [
250                {
251                    "value": "enable",
252                    "revisions": {
253                        "v6.0.11": True,
254                        "v6.0.0": True,
255                        "v6.0.5": True
256                    }
257                },
258                {
259                    "value": "disable",
260                    "revisions": {
261                        "v6.0.11": True,
262                        "v6.0.0": True,
263                        "v6.0.5": True
264                    }
265                }
266            ],
267            "revisions": {
268                "v6.0.11": True,
269                "v6.0.0": True,
270                "v6.0.5": True
271            }
272        },
273        "spam_submit_force": {
274            "type": "string",
275            "options": [
276                {
277                    "value": "enable",
278                    "revisions": {
279                        "v6.0.11": True,
280                        "v6.0.0": True,
281                        "v6.0.5": True
282                    }
283                },
284                {
285                    "value": "disable",
286                    "revisions": {
287                        "v6.0.11": True,
288                        "v6.0.0": True,
289                        "v6.0.5": True
290                    }
291                }
292            ],
293            "revisions": {
294                "v6.0.11": True,
295                "v6.0.0": True,
296                "v6.0.5": True
297            }
298        }
299    },
300    "revisions": {
301        "v6.0.11": True,
302        "v6.0.0": True,
303        "v6.0.5": True
304    }
305}
306
307
308def main():
309    module_spec = schema_to_module_spec(versioned_schema)
310    mkeyname = None
311    fields = {
312        "access_token": {"required": False, "type": "str", "no_log": True},
313        "enable_log": {"required": False, "type": bool},
314        "vdom": {"required": False, "type": "str", "default": "root"},
315        "spamfilter_fortishield": {
316            "required": False, "type": "dict", "default": None,
317            "options": {
318            }
319        }
320    }
321    for attribute_name in module_spec['options']:
322        fields["spamfilter_fortishield"]['options'][attribute_name] = module_spec['options'][attribute_name]
323        if mkeyname and mkeyname == attribute_name:
324            fields["spamfilter_fortishield"]['options'][attribute_name]['required'] = True
325
326    check_legacy_fortiosapi()
327    module = AnsibleModule(argument_spec=fields,
328                           supports_check_mode=False)
329
330    versions_check_result = None
331    if module._socket_path:
332        connection = Connection(module._socket_path)
333        if 'access_token' in module.params:
334            connection.set_option('access_token', module.params['access_token'])
335
336        if 'enable_log' in module.params:
337            connection.set_option('enable_log', module.params['enable_log'])
338        else:
339            connection.set_option('enable_log', False)
340        fos = FortiOSHandler(connection, module, mkeyname)
341        versions_check_result = check_schema_versioning(fos, versioned_schema, "spamfilter_fortishield")
342
343        is_error, has_changed, result = fortios_spamfilter(module.params, fos)
344
345    else:
346        module.fail_json(**FAIL_SOCKET_MSG)
347
348    if versions_check_result and versions_check_result['matched'] is False:
349        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
350
351    if not is_error:
352        if versions_check_result and versions_check_result['matched'] is False:
353            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
354        else:
355            module.exit_json(changed=has_changed, meta=result)
356    else:
357        if versions_check_result and versions_check_result['matched'] is False:
358            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
359        else:
360            module.fail_json(msg="Error in repo", meta=result)
361
362
363if __name__ == '__main__':
364    main()
365