1#!/usr/bin/python 2from __future__ import (absolute_import, division, print_function) 3# Copyright 2019 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_profile 27short_description: Configure AntiSpam profiles 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 profile category. 31 Examples include all parameters and values need to be adjusted to datasources before usage. 32 Tested with FOS v6.0.5 33version_added: "2.8" 34author: 35 - Miguel Angel Munoz (@mamunozgonzalez) 36 - Nicolas Thomas (@thomnico) 37notes: 38 - Requires fortiosapi library developed by Fortinet 39 - Run as a local_action in your playbook 40requirements: 41 - fortiosapi>=0.9.8 42options: 43 host: 44 description: 45 - FortiOS or FortiGate IP address. 46 type: str 47 required: false 48 username: 49 description: 50 - FortiOS or FortiGate username. 51 type: str 52 required: false 53 password: 54 description: 55 - FortiOS or FortiGate password. 56 type: str 57 default: "" 58 vdom: 59 description: 60 - Virtual domain, among those defined previously. A vdom is a 61 virtual instance of the FortiGate that can be configured and 62 used as a different unit. 63 type: str 64 default: root 65 https: 66 description: 67 - Indicates if the requests towards FortiGate must use HTTPS protocol. 68 type: bool 69 default: true 70 ssl_verify: 71 description: 72 - Ensures FortiGate certificate must be verified by a proper CA. 73 type: bool 74 default: true 75 version_added: 2.9 76 state: 77 description: 78 - Indicates whether to create or remove the object. 79 This attribute was present already in previous version in a deeper level. 80 It has been moved out to this outer level. 81 type: str 82 required: false 83 choices: 84 - present 85 - absent 86 version_added: 2.9 87 spamfilter_profile: 88 description: 89 - Configure AntiSpam profiles. 90 default: null 91 type: dict 92 suboptions: 93 state: 94 description: 95 - B(Deprecated) 96 - Starting with Ansible 2.9 we recommend using the top-level 'state' parameter. 97 - HORIZONTALLINE 98 - Indicates whether to create or remove the object. 99 type: str 100 required: false 101 choices: 102 - present 103 - absent 104 comment: 105 description: 106 - Comment. 107 type: str 108 external: 109 description: 110 - Enable/disable external Email inspection. 111 type: str 112 choices: 113 - enable 114 - disable 115 flow_based: 116 description: 117 - Enable/disable flow-based spam filtering. 118 type: str 119 choices: 120 - enable 121 - disable 122 gmail: 123 description: 124 - Gmail. 125 type: dict 126 suboptions: 127 log: 128 description: 129 - Enable/disable logging. 130 type: str 131 choices: 132 - enable 133 - disable 134 imap: 135 description: 136 - IMAP. 137 type: dict 138 suboptions: 139 action: 140 description: 141 - Action for spam email. 142 type: str 143 choices: 144 - pass 145 - tag 146 log: 147 description: 148 - Enable/disable logging. 149 type: str 150 choices: 151 - enable 152 - disable 153 tag_msg: 154 description: 155 - Subject text or header added to spam email. 156 type: str 157 tag_type: 158 description: 159 - Tag subject or header for spam email. 160 type: list 161 choices: 162 - subject 163 - header 164 - spaminfo 165 mapi: 166 description: 167 - MAPI. 168 type: dict 169 suboptions: 170 action: 171 description: 172 - Action for spam email. 173 type: str 174 choices: 175 - pass 176 - discard 177 log: 178 description: 179 - Enable/disable logging. 180 type: str 181 choices: 182 - enable 183 - disable 184 msn_hotmail: 185 description: 186 - MSN Hotmail. 187 type: dict 188 suboptions: 189 log: 190 description: 191 - Enable/disable logging. 192 type: str 193 choices: 194 - enable 195 - disable 196 name: 197 description: 198 - Profile name. 199 required: true 200 type: str 201 options: 202 description: 203 - Options. 204 type: list 205 choices: 206 - bannedword 207 - spambwl 208 - spamfsip 209 - spamfssubmit 210 - spamfschksum 211 - spamfsurl 212 - spamhelodns 213 - spamraddrdns 214 - spamrbl 215 - spamhdrcheck 216 - spamfsphish 217 pop3: 218 description: 219 - POP3. 220 type: dict 221 suboptions: 222 action: 223 description: 224 - Action for spam email. 225 type: str 226 choices: 227 - pass 228 - tag 229 log: 230 description: 231 - Enable/disable logging. 232 type: str 233 choices: 234 - enable 235 - disable 236 tag_msg: 237 description: 238 - Subject text or header added to spam email. 239 type: str 240 tag_type: 241 description: 242 - Tag subject or header for spam email. 243 type: list 244 choices: 245 - subject 246 - header 247 - spaminfo 248 replacemsg_group: 249 description: 250 - Replacement message group. Source system.replacemsg-group.name. 251 type: str 252 smtp: 253 description: 254 - SMTP. 255 type: dict 256 suboptions: 257 action: 258 description: 259 - Action for spam email. 260 type: str 261 choices: 262 - pass 263 - tag 264 - discard 265 hdrip: 266 description: 267 - Enable/disable SMTP email header IP checks for spamfsip, spamrbl and spambwl filters. 268 type: str 269 choices: 270 - disable 271 - enable 272 local_override: 273 description: 274 - Enable/disable local filter to override SMTP remote check result. 275 type: str 276 choices: 277 - disable 278 - enable 279 log: 280 description: 281 - Enable/disable logging. 282 type: str 283 choices: 284 - enable 285 - disable 286 tag_msg: 287 description: 288 - Subject text or header added to spam email. 289 type: str 290 tag_type: 291 description: 292 - Tag subject or header for spam email. 293 type: list 294 choices: 295 - subject 296 - header 297 - spaminfo 298 spam_bwl_table: 299 description: 300 - Anti-spam black/white list table ID. Source spamfilter.bwl.id. 301 type: int 302 spam_bword_table: 303 description: 304 - Anti-spam banned word table ID. Source spamfilter.bword.id. 305 type: int 306 spam_bword_threshold: 307 description: 308 - Spam banned word threshold. 309 type: int 310 spam_filtering: 311 description: 312 - Enable/disable spam filtering. 313 type: str 314 choices: 315 - enable 316 - disable 317 spam_iptrust_table: 318 description: 319 - Anti-spam IP trust table ID. Source spamfilter.iptrust.id. 320 type: int 321 spam_log: 322 description: 323 - Enable/disable spam logging for email filtering. 324 type: str 325 choices: 326 - disable 327 - enable 328 spam_log_fortiguard_response: 329 description: 330 - Enable/disable logging FortiGuard spam response. 331 type: str 332 choices: 333 - disable 334 - enable 335 spam_mheader_table: 336 description: 337 - Anti-spam MIME header table ID. Source spamfilter.mheader.id. 338 type: int 339 spam_rbl_table: 340 description: 341 - Anti-spam DNSBL table ID. Source spamfilter.dnsbl.id. 342 type: int 343 yahoo_mail: 344 description: 345 - Yahoo! Mail. 346 type: dict 347 suboptions: 348 log: 349 description: 350 - Enable/disable logging. 351 type: str 352 choices: 353 - enable 354 - disable 355''' 356 357EXAMPLES = ''' 358- hosts: localhost 359 vars: 360 host: "192.168.122.40" 361 username: "admin" 362 password: "" 363 vdom: "root" 364 ssl_verify: "False" 365 tasks: 366 - name: Configure AntiSpam profiles. 367 fortios_spamfilter_profile: 368 host: "{{ host }}" 369 username: "{{ username }}" 370 password: "{{ password }}" 371 vdom: "{{ vdom }}" 372 https: "False" 373 state: "present" 374 spamfilter_profile: 375 comment: "Comment." 376 external: "enable" 377 flow_based: "enable" 378 gmail: 379 log: "enable" 380 imap: 381 action: "pass" 382 log: "enable" 383 tag_msg: "<your_own_value>" 384 tag_type: "subject" 385 mapi: 386 action: "pass" 387 log: "enable" 388 msn_hotmail: 389 log: "enable" 390 name: "default_name_18" 391 options: "bannedword" 392 pop3: 393 action: "pass" 394 log: "enable" 395 tag_msg: "<your_own_value>" 396 tag_type: "subject" 397 replacemsg_group: "<your_own_value> (source system.replacemsg-group.name)" 398 smtp: 399 action: "pass" 400 hdrip: "disable" 401 local_override: "disable" 402 log: "enable" 403 tag_msg: "<your_own_value>" 404 tag_type: "subject" 405 spam_bwl_table: "33 (source spamfilter.bwl.id)" 406 spam_bword_table: "34 (source spamfilter.bword.id)" 407 spam_bword_threshold: "35" 408 spam_filtering: "enable" 409 spam_iptrust_table: "37 (source spamfilter.iptrust.id)" 410 spam_log: "disable" 411 spam_log_fortiguard_response: "disable" 412 spam_mheader_table: "40 (source spamfilter.mheader.id)" 413 spam_rbl_table: "41 (source spamfilter.dnsbl.id)" 414 yahoo_mail: 415 log: "enable" 416''' 417 418RETURN = ''' 419build: 420 description: Build number of the fortigate image 421 returned: always 422 type: str 423 sample: '1547' 424http_method: 425 description: Last method used to provision the content into FortiGate 426 returned: always 427 type: str 428 sample: 'PUT' 429http_status: 430 description: Last result given by FortiGate on last operation applied 431 returned: always 432 type: str 433 sample: "200" 434mkey: 435 description: Master key (id) used in the last call to FortiGate 436 returned: success 437 type: str 438 sample: "id" 439name: 440 description: Name of the table used to fulfill the request 441 returned: always 442 type: str 443 sample: "urlfilter" 444path: 445 description: Path of the table used to fulfill the request 446 returned: always 447 type: str 448 sample: "webfilter" 449revision: 450 description: Internal revision number 451 returned: always 452 type: str 453 sample: "17.0.2.10658" 454serial: 455 description: Serial number of the unit 456 returned: always 457 type: str 458 sample: "FGVMEVYYQT3AB5352" 459status: 460 description: Indication of the operation's result 461 returned: always 462 type: str 463 sample: "success" 464vdom: 465 description: Virtual domain used 466 returned: always 467 type: str 468 sample: "root" 469version: 470 description: Version of the FortiGate 471 returned: always 472 type: str 473 sample: "v5.6.3" 474 475''' 476 477from ansible.module_utils.basic import AnsibleModule 478from ansible.module_utils.connection import Connection 479from ansible.module_utils.network.fortios.fortios import FortiOSHandler 480from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG 481 482 483def login(data, fos): 484 host = data['host'] 485 username = data['username'] 486 password = data['password'] 487 ssl_verify = data['ssl_verify'] 488 489 fos.debug('on') 490 if 'https' in data and not data['https']: 491 fos.https('off') 492 else: 493 fos.https('on') 494 495 fos.login(host, username, password, verify=ssl_verify) 496 497 498def filter_spamfilter_profile_data(json): 499 option_list = ['comment', 'external', 'flow_based', 500 'gmail', 'imap', 'mapi', 501 'msn_hotmail', 'name', 'options', 502 'pop3', 'replacemsg_group', 'smtp', 503 'spam_bwl_table', 'spam_bword_table', 'spam_bword_threshold', 504 'spam_filtering', 'spam_iptrust_table', 'spam_log', 505 'spam_log_fortiguard_response', 'spam_mheader_table', 'spam_rbl_table', 506 'yahoo_mail'] 507 dictionary = {} 508 509 for attribute in option_list: 510 if attribute in json and json[attribute] is not None: 511 dictionary[attribute] = json[attribute] 512 513 return dictionary 514 515 516def flatten_multilists_attributes(data): 517 multilist_attrs = [[u'options'], [u'imap', u'tag_type'], [u'pop3', u'tag_type'], [u'smtp', u'tag_type']] 518 519 for attr in multilist_attrs: 520 try: 521 path = "data['" + "']['".join(elem for elem in attr) + "']" 522 current_val = eval(path) 523 flattened_val = ' '.join(elem for elem in current_val) 524 exec(path + '= flattened_val') 525 except BaseException: 526 pass 527 528 return data 529 530 531def underscore_to_hyphen(data): 532 if isinstance(data, list): 533 for elem in data: 534 elem = underscore_to_hyphen(elem) 535 elif isinstance(data, dict): 536 new_data = {} 537 for k, v in data.items(): 538 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 539 data = new_data 540 541 return data 542 543 544def spamfilter_profile(data, fos): 545 vdom = data['vdom'] 546 if 'state' in data and data['state']: 547 state = data['state'] 548 elif 'state' in data['spamfilter_profile'] and data['spamfilter_profile']: 549 state = data['spamfilter_profile']['state'] 550 else: 551 state = True 552 spamfilter_profile_data = data['spamfilter_profile'] 553 spamfilter_profile_data = flatten_multilists_attributes(spamfilter_profile_data) 554 filtered_data = underscore_to_hyphen(filter_spamfilter_profile_data(spamfilter_profile_data)) 555 556 if state == "present": 557 return fos.set('spamfilter', 558 'profile', 559 data=filtered_data, 560 vdom=vdom) 561 562 elif state == "absent": 563 return fos.delete('spamfilter', 564 'profile', 565 mkey=filtered_data['name'], 566 vdom=vdom) 567 568 569def is_successful_status(status): 570 return status['status'] == "success" or \ 571 status['http_method'] == "DELETE" and status['http_status'] == 404 572 573 574def fortios_spamfilter(data, fos): 575 576 if data['spamfilter_profile']: 577 resp = spamfilter_profile(data, fos) 578 579 return not is_successful_status(resp), \ 580 resp['status'] == "success", \ 581 resp 582 583 584def main(): 585 fields = { 586 "host": {"required": False, "type": "str"}, 587 "username": {"required": False, "type": "str"}, 588 "password": {"required": False, "type": "str", "default": "", "no_log": True}, 589 "vdom": {"required": False, "type": "str", "default": "root"}, 590 "https": {"required": False, "type": "bool", "default": True}, 591 "ssl_verify": {"required": False, "type": "bool", "default": True}, 592 "state": {"required": False, "type": "str", 593 "choices": ["present", "absent"]}, 594 "spamfilter_profile": { 595 "required": False, "type": "dict", "default": None, 596 "options": { 597 "state": {"required": False, "type": "str", 598 "choices": ["present", "absent"]}, 599 "comment": {"required": False, "type": "str"}, 600 "external": {"required": False, "type": "str", 601 "choices": ["enable", "disable"]}, 602 "flow_based": {"required": False, "type": "str", 603 "choices": ["enable", "disable"]}, 604 "gmail": {"required": False, "type": "dict", 605 "options": { 606 "log": {"required": False, "type": "str", 607 "choices": ["enable", "disable"]} 608 }}, 609 "imap": {"required": False, "type": "dict", 610 "options": { 611 "action": {"required": False, "type": "str", 612 "choices": ["pass", "tag"]}, 613 "log": {"required": False, "type": "str", 614 "choices": ["enable", "disable"]}, 615 "tag_msg": {"required": False, "type": "str"}, 616 "tag_type": {"required": False, "type": "list", 617 "choices": ["subject", "header", "spaminfo"]} 618 }}, 619 "mapi": {"required": False, "type": "dict", 620 "options": { 621 "action": {"required": False, "type": "str", 622 "choices": ["pass", "discard"]}, 623 "log": {"required": False, "type": "str", 624 "choices": ["enable", "disable"]} 625 }}, 626 "msn_hotmail": {"required": False, "type": "dict", 627 "options": { 628 "log": {"required": False, "type": "str", 629 "choices": ["enable", "disable"]} 630 }}, 631 "name": {"required": True, "type": "str"}, 632 "options": {"required": False, "type": "list", 633 "choices": ["bannedword", "spambwl", "spamfsip", 634 "spamfssubmit", "spamfschksum", "spamfsurl", 635 "spamhelodns", "spamraddrdns", "spamrbl", 636 "spamhdrcheck", "spamfsphish"]}, 637 "pop3": {"required": False, "type": "dict", 638 "options": { 639 "action": {"required": False, "type": "str", 640 "choices": ["pass", "tag"]}, 641 "log": {"required": False, "type": "str", 642 "choices": ["enable", "disable"]}, 643 "tag_msg": {"required": False, "type": "str"}, 644 "tag_type": {"required": False, "type": "list", 645 "choices": ["subject", "header", "spaminfo"]} 646 }}, 647 "replacemsg_group": {"required": False, "type": "str"}, 648 "smtp": {"required": False, "type": "dict", 649 "options": { 650 "action": {"required": False, "type": "str", 651 "choices": ["pass", "tag", "discard"]}, 652 "hdrip": {"required": False, "type": "str", 653 "choices": ["disable", "enable"]}, 654 "local_override": {"required": False, "type": "str", 655 "choices": ["disable", "enable"]}, 656 "log": {"required": False, "type": "str", 657 "choices": ["enable", "disable"]}, 658 "tag_msg": {"required": False, "type": "str"}, 659 "tag_type": {"required": False, "type": "list", 660 "choices": ["subject", "header", "spaminfo"]} 661 }}, 662 "spam_bwl_table": {"required": False, "type": "int"}, 663 "spam_bword_table": {"required": False, "type": "int"}, 664 "spam_bword_threshold": {"required": False, "type": "int"}, 665 "spam_filtering": {"required": False, "type": "str", 666 "choices": ["enable", "disable"]}, 667 "spam_iptrust_table": {"required": False, "type": "int"}, 668 "spam_log": {"required": False, "type": "str", 669 "choices": ["disable", "enable"]}, 670 "spam_log_fortiguard_response": {"required": False, "type": "str", 671 "choices": ["disable", "enable"]}, 672 "spam_mheader_table": {"required": False, "type": "int"}, 673 "spam_rbl_table": {"required": False, "type": "int"}, 674 "yahoo_mail": {"required": False, "type": "dict", 675 "options": { 676 "log": {"required": False, "type": "str", 677 "choices": ["enable", "disable"]} 678 }} 679 680 } 681 } 682 } 683 684 module = AnsibleModule(argument_spec=fields, 685 supports_check_mode=False) 686 687 # legacy_mode refers to using fortiosapi instead of HTTPAPI 688 legacy_mode = 'host' in module.params and module.params['host'] is not None and \ 689 'username' in module.params and module.params['username'] is not None and \ 690 'password' in module.params and module.params['password'] is not None 691 692 if not legacy_mode: 693 if module._socket_path: 694 connection = Connection(module._socket_path) 695 fos = FortiOSHandler(connection) 696 697 is_error, has_changed, result = fortios_spamfilter(module.params, fos) 698 else: 699 module.fail_json(**FAIL_SOCKET_MSG) 700 else: 701 try: 702 from fortiosapi import FortiOSAPI 703 except ImportError: 704 module.fail_json(msg="fortiosapi module is required") 705 706 fos = FortiOSAPI() 707 708 login(module.params, fos) 709 is_error, has_changed, result = fortios_spamfilter(module.params, fos) 710 fos.logout() 711 712 if not is_error: 713 module.exit_json(changed=has_changed, meta=result) 714 else: 715 module.fail_json(msg="Error in repo", meta=result) 716 717 718if __name__ == '__main__': 719 main() 720