Fix salt rabbitmq module incompatibility with rabbitmq-server 3.8+

It was an issue with check regex against newer RabbitMQ version in
check_password function in rabbitmq module.
Now it uses rabbitmq_custom module instead of rabbitmq for
check_password function.

PROD-35125

Change-Id: Icead56cbc0ae9f52e53f3978636a0d687dd92942
diff --git a/_modules/rabbitmq_custom.py b/_modules/rabbitmq_custom.py
new file mode 100644
index 0000000..182ca49
--- /dev/null
+++ b/_modules/rabbitmq_custom.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+
+# This file is copy of original /usr/lib/python2.7/dist-packages/salt/modules/rabbitmq.py
+# with fix applied for PROD-35125 - rabbitmq module incompatible with rabbitmq-server 3.8+
+# It fix native check_password function by override it here and call it from
+# _states/rabbitmq_user_common.py which also introduced in this patch.
+# Fix source: https://github.com/saltstack/salt/commit/d0e0ed6b60f4de6d2b2551cad2fc7a553efe0274
+
+from __future__ import absolute_import
+
+# Import python libs
+import json
+import re
+import logging
+import os
+import os.path
+import random
+import string
+
+# Import salt libs
+import salt.utils
+import salt.utils.itertools
+import salt.ext.six as six
+from salt.exceptions import SaltInvocationError
+from salt.ext.six.moves import range
+from salt.exceptions import CommandExecutionError
+
+log = logging.getLogger(__name__)
+
+RABBITMQCTL = None
+RABBITMQ_PLUGINS = None
+
+
+def __virtual__():
+    '''
+    Verify RabbitMQ is installed.
+    '''
+    global RABBITMQCTL
+    global RABBITMQ_PLUGINS
+
+    if salt.utils.is_windows():
+        from salt.ext.six.moves import winreg
+        key = None
+        try:
+            key = winreg.OpenKeyEx(
+                winreg.HKEY_LOCAL_MACHINE,
+                'SOFTWARE\\VMware, Inc.\\RabbitMQ Server',
+                0,
+                winreg.KEY_READ | winreg.KEY_WOW64_32KEY
+            )
+            (dir_path, value_type) = winreg.QueryValueEx(
+                key,
+                'Install_Dir'
+            )
+            if value_type != winreg.REG_SZ:
+                raise TypeError('Invalid RabbitMQ Server directory type: {0}'.format(value_type))
+            if not os.path.isdir(dir_path):
+                raise IOError('RabbitMQ directory not found: {0}'.format(dir_path))
+            subdir_match = ''
+            for name in os.listdir(dir_path):
+                if name.startswith('rabbitmq_server-'):
+                    subdir_path = os.path.join(dir_path, name)
+                    # Get the matching entry that is last in ASCII order.
+                    if os.path.isdir(subdir_path) and subdir_path > subdir_match:
+                        subdir_match = subdir_path
+            if not subdir_match:
+                raise IOError('"rabbitmq_server-*" subdirectory not found in: {0}'.format(dir_path))
+            RABBITMQCTL = os.path.join(subdir_match, 'sbin', 'rabbitmqctl.bat')
+            RABBITMQ_PLUGINS = os.path.join(subdir_match, 'sbin', 'rabbitmq-plugins.bat')
+        except Exception:
+            pass
+        finally:
+            if key is not None:
+                winreg.CloseKey(key)
+    else:
+        RABBITMQCTL = salt.utils.which('rabbitmqctl')
+        RABBITMQ_PLUGINS = salt.utils.which('rabbitmq-plugins')
+
+    if not RABBITMQCTL:
+        return (False, 'Module rabbitmq: module only works when RabbitMQ is installed')
+    return True
+
+
+def _format_response(response, msg):
+    if isinstance(response, dict):
+        if response['retcode'] != 0 or response['stderr']:
+            raise CommandExecutionError(
+                'RabbitMQ command failed: {0}'.format(response['stderr'])
+            )
+        else:
+            response = response['stdout']
+    else:
+        if 'Error' in response:
+            raise CommandExecutionError(
+                'RabbitMQ command failed: {0}'.format(response)
+            )
+    return {
+        msg: response
+    }
+
+
+def check_password(name, password, runas=None):
+    '''
+    .. versionadded:: 2016.3.0
+
+    Checks if a user's password is valid.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' rabbitmq.check_password rabbit_user password
+    '''
+    # try to get the rabbitmq-version - adapted from _get_rabbitmq_plugin
+
+    if runas is None and not salt.utils.is_windows():
+        runas = salt.utils.get_user()
+
+    try:
+        res = __salt__['cmd.run']([RABBITMQCTL, 'status'], reset_system_locale=False, runas=runas, python_shell=False)
+
+        # Fix for PROD-35125 - rabbitmq module incompatible with rabbitmq-server 3.8+
+        # https://github.com/saltstack/salt/commit/d0e0ed6b60f4de6d2b2551cad2fc7a553efe0274
+
+        # Check regex against older RabbitMQ version status output
+        old_server_version = re.search(r'\{rabbit,"RabbitMQ","(.+)"\}', res)
+        # Check regex against newer RabbitMQ version status output
+        server_version = re.search(r"RabbitMQ version:\s*(.+)", res)
+
+        if server_version is None and old_server_version is None:
+            raise ValueError
+
+        if old_server_version:
+            server_version = old_server_version
+        server_version = server_version.group(1).split("-")[0]
+        version = [int(i) for i in server_version.split(".")]
+
+    except ValueError:
+        version = (0, 0, 0)
+    if len(version) < 3:
+        version = (0, 0, 0)
+
+    # rabbitmq introduced a native api to check a username and password in version 3.5.7.
+    if tuple(version) >= (3, 5, 7):
+        if salt.utils.is_windows():
+            # On Windows, if the password contains a special character
+            # such as '|', normal execution will fail. For example:
+            # cmd: rabbitmq.add_user abc "asdf|def"
+            # stderr: 'def' is not recognized as an internal or external
+            #         command,\r\noperable program or batch file.
+            # Work around this by using a shell and a quoted command.
+            python_shell = True
+            cmd = '"{0}" authenticate_user "{1}" "{2}"'.format(
+                RABBITMQCTL, name, password
+            )
+        else:
+            python_shell = False
+            cmd = [RABBITMQCTL, 'authenticate_user', name, password]
+
+        res = __salt__['cmd.run_all'](
+            cmd,
+            reset_system_locale=False,
+            runas=runas,
+            output_loglevel='quiet',
+            python_shell=python_shell)
+
+        if res['retcode'] != 0 or res['stderr']:
+            return False
+        return True
+
+    cmd = ('rabbit_auth_backend_internal:check_user_login'
+        '(<<"{0}">>, [{{password, <<"{1}">>}}]).').format(
+        name.replace('"', '\\"'),
+        password.replace('"', '\\"'))
+
+    res = __salt__['cmd.run_all'](
+        [RABBITMQCTL, 'eval', cmd],
+        reset_system_locale=False,
+        runas=runas,
+        output_loglevel='quiet',
+        python_shell=False)
+    msg = 'password-check'
+
+    _response = _format_response(res, msg)
+    _key = _response.keys()[0]
+
+    if 'invalid credentials' in _response[_key]:
+        return False
+
+    return True