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_system_replacemsg_mms
27short_description: Replacement messages 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 system_replacemsg feature and mms 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    state:
68        description:
69            - Indicates whether to create or remove the object.
70        type: str
71        required: true
72        choices:
73            - present
74            - absent
75    system_replacemsg_mms:
76        description:
77            - Replacement messages.
78        default: null
79        type: dict
80        suboptions:
81            buffer:
82                description:
83                    - Message string.
84                type: str
85            charset:
86                description:
87                    - character encoding used for replacement message
88                type: str
89                choices:
90                    - utf-8
91                    - us-ascii
92            format:
93                description:
94                    - Format flag.
95                type: str
96                choices:
97                    - none
98                    - text
99                    - html
100                    - wml
101            header:
102                description:
103                    - Header flag.
104                type: str
105                choices:
106                    - none
107                    - http
108                    - 8bit
109            image:
110                description:
111                    - Message string. Source system.replacemsg-image.name.
112                type: str
113            msg_type:
114                description:
115                    - Message type.
116                type: str
117'''
118
119EXAMPLES = '''
120- hosts: fortigates
121  collections:
122    - fortinet.fortios
123  connection: httpapi
124  vars:
125   vdom: "root"
126   ansible_httpapi_use_ssl: yes
127   ansible_httpapi_validate_certs: no
128   ansible_httpapi_port: 443
129  tasks:
130  - name: Replacement messages.
131    fortios_system_replacemsg_mms:
132      vdom:  "{{ vdom }}"
133      state: "present"
134      access_token: "<your_own_value>"
135      system_replacemsg_mms:
136        buffer: "<your_own_value>"
137        charset: "utf-8"
138        format: "none"
139        header: "none"
140        image: "<your_own_value> (source system.replacemsg-image.name)"
141        msg_type: "<your_own_value>"
142
143'''
144
145RETURN = '''
146build:
147  description: Build number of the fortigate image
148  returned: always
149  type: str
150  sample: '1547'
151http_method:
152  description: Last method used to provision the content into FortiGate
153  returned: always
154  type: str
155  sample: 'PUT'
156http_status:
157  description: Last result given by FortiGate on last operation applied
158  returned: always
159  type: str
160  sample: "200"
161mkey:
162  description: Master key (id) used in the last call to FortiGate
163  returned: success
164  type: str
165  sample: "id"
166name:
167  description: Name of the table used to fulfill the request
168  returned: always
169  type: str
170  sample: "urlfilter"
171path:
172  description: Path of the table used to fulfill the request
173  returned: always
174  type: str
175  sample: "webfilter"
176revision:
177  description: Internal revision number
178  returned: always
179  type: str
180  sample: "17.0.2.10658"
181serial:
182  description: Serial number of the unit
183  returned: always
184  type: str
185  sample: "FGVMEVYYQT3AB5352"
186status:
187  description: Indication of the operation's result
188  returned: always
189  type: str
190  sample: "success"
191vdom:
192  description: Virtual domain used
193  returned: always
194  type: str
195  sample: "root"
196version:
197  description: Version of the FortiGate
198  returned: always
199  type: str
200  sample: "v5.6.3"
201
202'''
203from ansible.module_utils.basic import AnsibleModule
204from ansible.module_utils.connection import Connection
205from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
206from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
207from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
208from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
209from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
210from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
211from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
212
213
214def filter_system_replacemsg_mms_data(json):
215    option_list = ['buffer', 'charset', 'format',
216                   'header', 'image', 'msg_type']
217    dictionary = {}
218
219    for attribute in option_list:
220        if attribute in json and json[attribute] is not None:
221            dictionary[attribute] = json[attribute]
222
223    return dictionary
224
225
226def underscore_to_hyphen(data):
227    if isinstance(data, list):
228        for i, elem in enumerate(data):
229            data[i] = underscore_to_hyphen(elem)
230    elif isinstance(data, dict):
231        new_data = {}
232        for k, v in data.items():
233            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
234        data = new_data
235
236    return data
237
238
239def system_replacemsg_mms(data, fos, check_mode=False):
240
241    vdom = data['vdom']
242
243    state = data['state']
244
245    system_replacemsg_mms_data = data['system_replacemsg_mms']
246    filtered_data = underscore_to_hyphen(filter_system_replacemsg_mms_data(system_replacemsg_mms_data))
247
248    # check_mode starts from here
249    if check_mode:
250        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
251        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
252        is_existed = current_data and current_data.get('http_status') == 200 \
253            and isinstance(current_data.get('results'), list) \
254            and len(current_data['results']) > 0
255
256        # 2. if it exists and the state is 'present' then compare current settings with desired
257        if state == 'present' or state is True:
258            if mkey is None:
259                return False, True, filtered_data
260
261            # if mkey exists then compare each other
262            # record exits and they're matched or not
263            if is_existed:
264                is_same = is_same_comparison(
265                    serialize(current_data['results'][0]), serialize(filtered_data))
266                return False, not is_same, filtered_data
267
268            # record does not exist
269            return False, True, filtered_data
270
271        if state == 'absent':
272            if mkey is None:
273                return False, False, filtered_data
274
275            if is_existed:
276                return False, True, filtered_data
277            return False, False, filtered_data
278
279        return True, False, {'reason: ': 'Must provide state parameter'}
280
281    if state == "present" or state is True:
282        return fos.set('system.replacemsg',
283                       'mms',
284                       data=filtered_data,
285                       vdom=vdom)
286
287    elif state == "absent":
288        return fos.delete('system.replacemsg',
289                          'mms',
290                          mkey=filtered_data['msg-type'],
291                          vdom=vdom)
292    else:
293        fos._module.fail_json(msg='state must be present or absent!')
294
295
296def is_successful_status(status):
297    return status['status'] == "success" or \
298        status['http_method'] == "DELETE" and status['http_status'] == 404
299
300
301def fortios_system_replacemsg(data, fos, check_mode):
302
303    if data['system_replacemsg_mms']:
304        resp = system_replacemsg_mms(data, fos, check_mode)
305    else:
306        fos._module.fail_json(msg='missing task body: %s' % ('system_replacemsg_mms'))
307    if check_mode:
308        return resp
309    return not is_successful_status(resp), \
310        resp['status'] == "success" and \
311        (resp['revision_changed'] if 'revision_changed' in resp else True), \
312        resp
313
314
315versioned_schema = {
316    "type": "list",
317    "children": {
318        "msg_type": {
319            "type": "string",
320            "revisions": {
321                "v6.0.0": True,
322                "v6.0.5": True,
323                "v6.2.0": True,
324                "v6.2.3": True,
325                "v6.2.5": True,
326                "v6.2.7": True,
327                "v6.0.11": True
328            }
329        },
330        "format": {
331            "type": "string",
332            "options": [
333                {
334                    "value": "none",
335                    "revisions": {
336                        "v6.0.0": True,
337                        "v6.0.5": True,
338                        "v6.2.0": True,
339                        "v6.2.3": True,
340                        "v6.2.5": True,
341                        "v6.2.7": True,
342                        "v6.0.11": True
343                    }
344                },
345                {
346                    "value": "text",
347                    "revisions": {
348                        "v6.0.0": True,
349                        "v6.0.5": True,
350                        "v6.2.0": True,
351                        "v6.2.3": True,
352                        "v6.2.5": True,
353                        "v6.2.7": True,
354                        "v6.0.11": True
355                    }
356                },
357                {
358                    "value": "html",
359                    "revisions": {
360                        "v6.0.0": True,
361                        "v6.0.5": True,
362                        "v6.2.0": True,
363                        "v6.2.3": True,
364                        "v6.2.5": True,
365                        "v6.2.7": True,
366                        "v6.0.11": True
367                    }
368                },
369                {
370                    "value": "wml",
371                    "revisions": {
372                        "v6.0.0": True,
373                        "v6.0.5": True,
374                        "v6.2.0": True,
375                        "v6.2.3": True,
376                        "v6.2.5": True,
377                        "v6.2.7": True,
378                        "v6.0.11": True
379                    }
380                }
381            ],
382            "revisions": {
383                "v6.0.0": True,
384                "v6.0.5": True,
385                "v6.2.0": True,
386                "v6.2.3": True,
387                "v6.2.5": True,
388                "v6.2.7": True,
389                "v6.0.11": True
390            }
391        },
392        "buffer": {
393            "type": "string",
394            "revisions": {
395                "v6.0.0": True,
396                "v6.0.5": True,
397                "v6.2.0": True,
398                "v6.2.3": True,
399                "v6.2.5": True,
400                "v6.2.7": True,
401                "v6.0.11": True
402            }
403        },
404        "image": {
405            "type": "string",
406            "revisions": {
407                "v6.0.0": True,
408                "v6.0.5": True,
409                "v6.2.0": True,
410                "v6.2.3": True,
411                "v6.2.5": True,
412                "v6.2.7": True,
413                "v6.0.11": True
414            }
415        },
416        "charset": {
417            "type": "string",
418            "options": [
419                {
420                    "value": "utf-8",
421                    "revisions": {
422                        "v6.0.0": True,
423                        "v6.0.5": True,
424                        "v6.2.0": True,
425                        "v6.2.3": True,
426                        "v6.2.5": True,
427                        "v6.2.7": True,
428                        "v6.0.11": True
429                    }
430                },
431                {
432                    "value": "us-ascii",
433                    "revisions": {
434                        "v6.0.0": True,
435                        "v6.0.5": True,
436                        "v6.2.0": True,
437                        "v6.2.3": True,
438                        "v6.2.5": True,
439                        "v6.2.7": True,
440                        "v6.0.11": True
441                    }
442                }
443            ],
444            "revisions": {
445                "v6.0.0": True,
446                "v6.0.5": True,
447                "v6.2.0": True,
448                "v6.2.3": True,
449                "v6.2.5": True,
450                "v6.2.7": True,
451                "v6.0.11": True
452            }
453        },
454        "header": {
455            "type": "string",
456            "options": [
457                {
458                    "value": "none",
459                    "revisions": {
460                        "v6.0.0": True,
461                        "v6.0.5": True,
462                        "v6.2.0": True,
463                        "v6.2.3": True,
464                        "v6.2.5": True,
465                        "v6.2.7": True,
466                        "v6.0.11": True
467                    }
468                },
469                {
470                    "value": "http",
471                    "revisions": {
472                        "v6.0.0": True,
473                        "v6.0.5": True,
474                        "v6.2.0": True,
475                        "v6.2.3": True,
476                        "v6.2.5": True,
477                        "v6.2.7": True,
478                        "v6.0.11": True
479                    }
480                },
481                {
482                    "value": "8bit",
483                    "revisions": {
484                        "v6.0.0": True,
485                        "v6.0.5": True,
486                        "v6.2.0": True,
487                        "v6.2.3": True,
488                        "v6.2.5": True,
489                        "v6.2.7": True,
490                        "v6.0.11": True
491                    }
492                }
493            ],
494            "revisions": {
495                "v6.0.0": True,
496                "v6.0.5": True,
497                "v6.2.0": True,
498                "v6.2.3": True,
499                "v6.2.5": True,
500                "v6.2.7": True,
501                "v6.0.11": True
502            }
503        }
504    },
505    "revisions": {
506        "v6.0.0": True,
507        "v6.0.5": True,
508        "v6.2.0": True,
509        "v6.2.3": True,
510        "v6.2.5": True,
511        "v6.2.7": True,
512        "v6.0.11": True
513    }
514}
515
516
517def main():
518    module_spec = schema_to_module_spec(versioned_schema)
519    mkeyname = 'msg-type'
520    fields = {
521        "access_token": {"required": False, "type": "str", "no_log": True},
522        "enable_log": {"required": False, "type": bool},
523        "vdom": {"required": False, "type": "str", "default": "root"},
524        "state": {"required": True, "type": "str",
525                  "choices": ["present", "absent"]},
526        "system_replacemsg_mms": {
527            "required": False, "type": "dict", "default": None,
528            "options": {
529            }
530        }
531    }
532    for attribute_name in module_spec['options']:
533        fields["system_replacemsg_mms"]['options'][attribute_name] = module_spec['options'][attribute_name]
534        if mkeyname and mkeyname == attribute_name:
535            fields["system_replacemsg_mms"]['options'][attribute_name]['required'] = True
536
537    check_legacy_fortiosapi()
538    module = AnsibleModule(argument_spec=fields,
539                           supports_check_mode=True)
540
541    versions_check_result = None
542    if module._socket_path:
543        connection = Connection(module._socket_path)
544        if 'access_token' in module.params:
545            connection.set_option('access_token', module.params['access_token'])
546
547        if 'enable_log' in module.params:
548            connection.set_option('enable_log', module.params['enable_log'])
549        else:
550            connection.set_option('enable_log', False)
551        fos = FortiOSHandler(connection, module, mkeyname)
552        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_replacemsg_mms")
553
554        is_error, has_changed, result = fortios_system_replacemsg(module.params, fos, module.check_mode)
555
556    else:
557        module.fail_json(**FAIL_SOCKET_MSG)
558
559    if versions_check_result and versions_check_result['matched'] is False:
560        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
561
562    if not is_error:
563        if versions_check_result and versions_check_result['matched'] is False:
564            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
565        else:
566            module.exit_json(changed=has_changed, meta=result)
567    else:
568        if versions_check_result and versions_check_result['matched'] is False:
569            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
570        else:
571            module.fail_json(msg="Error in repo", meta=result)
572
573
574if __name__ == '__main__':
575    main()
576