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