#!/usr/bin/python3 -Es
# -*- coding: UTF-8 -*-
#
#  Copyright © 2013 Marc-André Pelletier <mpelletier@wikimedia.org>
#  Copyright © 2017 Wikimedia Foundation and contributors
#
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#
#  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
#  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
#  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
#  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
#  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
#  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import argparse
import configparser
import re

import mysql.connector

GRANT_REPLICA = "GRANT SELECT, SHOW VIEW ON `%\\_p`.* TO '{}'@'%'"
GRANT_ANY_RE = (
    r"^GRANT (SELECT, )?SHOW VIEW ON \*\.\* TO '{}'@'%'"
    r"(?: IDENTIFIED BY PASSWORD '\*[0-9A-F]{{40}}')?$"
)
GRANT_ALL_RE = (
    r"^GRANT ALL PRIVILEGES ON `([^`]+)`\.\* TO '{}'@'%'"
    r"(?: WITH GRANT OPTION)?$"
)

# Get list of defaults files to extract user information from.
parser = argparse.ArgumentParser(
    description=(
        'List databases owned by users, '
        'given by the credentials files.')
)
parser.add_argument(
    '-H', '--host',
    action='append',
    required=True,
    help='database hosts to check')
parser.add_argument(
    'mycnffilenames',
    metavar='MYCNFFILENAME',
    nargs='+',
    help='name of MySQL defaults file')
args = parser.parse_args()

for mycnffilename in args.mycnffilenames:
    c = configparser.ConfigParser()
    c.read(mycnffilename)
    dbuser = c['client']['user'].strip("'")
    dbpass = c['client']['password'].strip("'")
    databases_found = []
    for server in args.host:
        conn = mysql.connector.connect(
            database='mysql',
            host=server,
            user=dbuser,
            password=dbpass,
            charset='utf8mb4')
        cur = conn.cursor()
        cur.execute('SHOW GRANTS')
        database_patterns = []
        for (stmt, ) in cur.fetchall():
            # Ignore general grants for replica views.
            if stmt == GRANT_REPLICA.format(dbuser):
                continue
            if re.match(GRANT_ANY_RE.format(re.escape(dbuser)), stmt):
                continue

            # Gather grants for user-specific databases.
            m = re.match(GRANT_ALL_RE.format(re.escape(dbuser)), stmt)
            if m:
                database_patterns.append(m.group(1))
            else:
                raise Exception('Cannot parse ' + repr(stmt))

        for database_pattern in database_patterns:
            cur.execute('SHOW DATABASES LIKE %s', (database_pattern, ))
            for r in cur.fetchall():
                databases_found.append([server, r[0]])

        conn.close()

    if databases_found:
        print('Databases found for user {} ...'.format(dbuser))
        for database_found in databases_found:
            print('... on server {} database {}'.format(
                database_found[0], database_found[1]))
    else:
        print('No databases found for user {}'.format(dbuser))
