#!/usr/bin/env python3 import os import sys import re import configparser import getpass from collections import namedtuple try: import pymysql HAS_PYMYSQL = True except ImportError: HAS_PYMYSQL = False MySQLDBInfo = namedtuple('MySQLDBInfo', 'host port username password db') class EnvManager(object): def __init__(self): self.upgrade_dir = os.path.abspath(os.path.dirname(__file__)) self.install_path = os.path.dirname(self.upgrade_dir) self.top_dir = os.path.dirname(self.install_path) self.ccnet_dir = os.environ['CCNET_CONF_DIR'] self.seafile_dir = os.environ['SEAFILE_CONF_DIR'] env_mgr = EnvManager() class Utils(object): @staticmethod def highlight(content, is_error=False): '''Add ANSI color to content to get it highlighted on terminal''' if is_error: return '\x1b[1;31m%s\x1b[m' % content else: return '\x1b[1;32m%s\x1b[m' % content @staticmethod def info(msg): print(Utils.highlight('[INFO] ') + msg) @staticmethod def error(msg): print(Utils.highlight('[ERROR] ') + msg) sys.exit(1) @staticmethod def read_config(config_path, defaults): cp = configparser.ConfigParser(defaults) cp.read(config_path) return cp def get_ccnet_mysql_info(): seafile_conf = os.path.join(env_mgr.ccnet_dir, 'seafile.conf') defaults = { 'HOST': '127.0.0.1', 'PORT': '3306', } config = Utils.read_config(seafile_conf, defaults) db_section = 'database' if not config.has_section(db_section): return None type = config.get(db_section, 'type') if type != 'mysql': return None try: host = config.get(db_section, 'host') port = config.getint(db_section, 'port') username = config.get(db_section, 'user') password = config.get(db_section, 'password') db = os.environ.get('SEAFILE_MYSQL_DB_CCNET_DB_NAME', '') or 'ccnet_db' except configparser.NoOptionError as e: Utils.error('Database config in seafile.conf is invalid: %s' % e) info = MySQLDBInfo(host, port, username, password, db) return info def get_seafile_mysql_info(): seafile_conf = os.path.join(env_mgr.seafile_dir, 'seafile.conf') defaults = { 'HOST': '127.0.0.1', 'PORT': '3306', } config = Utils.read_config(seafile_conf, defaults) db_section = 'database' if not config.has_section(db_section): return None type = config.get(db_section, 'type') if type != 'mysql': return None try: host = config.get(db_section, 'host') port = config.getint(db_section, 'port') username = config.get(db_section, 'user') password = config.get(db_section, 'password') db = config.get(db_section, 'db_name') except configparser.NoOptionError as e: Utils.error('Database config in seafile.conf is invalid: %s' % e) info = MySQLDBInfo(host, port, username, password, db) return info def get_seahub_mysql_info(): sys.path.insert(0, env_mgr.top_dir) try: import seahub_settings# pylint: disable=F0401 except ImportError as e: Utils.error('Failed to import seahub_settings.py: %s' % e) if not hasattr(seahub_settings, 'DATABASES'): return None try: d = seahub_settings.DATABASES['default'] if d['ENGINE'] != 'django.db.backends.mysql': return None host = d.get('HOST', '127.0.0.1') port = int(d.get('PORT', 3306)) username = d['USER'] password = d['PASSWORD'] db = d['NAME'] except KeyError: Utils.error('Database config in seahub_settings.py is invalid: %s' % e) info = MySQLDBInfo(host, port, username, password, db) return info def get_seafile_db_infos(): ccnet_db_info = get_ccnet_mysql_info() seafile_db_info = get_seafile_mysql_info() seahub_db_info = get_seahub_mysql_info() infos = [ccnet_db_info, seafile_db_info, seahub_db_info] for info in infos: if info is None: return None if info.host not in ('localhost', '127.0.0.1'): return None return infos def ask_root_password(port): while True: desc = 'What is the root password for mysql? ' password = getpass.getpass(desc).strip() if password: try: return check_mysql_user('root', password, port) except InvalidAnswer as e: print('\n%s\n' % e) continue class InvalidAnswer(Exception): def __init__(self, msg): Exception.__init__(self) self.msg = msg def __str__(self): return self.msg def check_mysql_user(user, password, port): print('\nverifying password of root user %s ... ' % user, end=' ') kwargs = dict(host='localhost', port=port, user=user, passwd=password) try: conn = pymysql.connect(**kwargs) except Exception as e: if isinstance(e, pymysql.err.OperationalError): raise InvalidAnswer('Failed to connect to mysql server using user "%s" and password "***": %s' % (user, e.args[1])) else: raise InvalidAnswer('Failed to connect to mysql server using user "%s" and password "***": %s' % (user, e)) print('done') return conn def apply_fix(root_conn, user, dbs): for db in dbs: grant_db_permission(root_conn, user, db) cursor = root_conn.cursor() sql = """ SELECT * FROM mysql.user WHERE Host = '%%' AND password = '' AND User = '%s' """ % user cursor.execute(sql) if cursor.rowcount > 0: sql = 'DROP USER `%s`@`%%`' % user cursor.execute(sql) def grant_db_permission(conn, user, db): cursor = conn.cursor() sql = '''GRANT ALL PRIVILEGES ON `%s`.* to `%s`@localhost ''' \ % (db, user) try: cursor.execute(sql) except Exception as e: if isinstance(e, pymysql.err.OperationalError): Utils.error('Failed to grant permission of database %s: %s' % (db, e.args[1])) else: Utils.error('Failed to grant permission of database %s: %s' % (db, e)) finally: cursor.close() def main(): dbinfos = get_seafile_db_infos() if not dbinfos: return if dbinfos[0].username == 'root': return if not HAS_PYMYSQL: Utils.error('Python pymysql module is not found') root_conn = ask_root_password(dbinfos[0].port) apply_fix(root_conn, dbinfos[0].username, [info.db for info in dbinfos]) if __name__ == '__main__': main()