#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Calculates the hash that MySQL uses for passwords since version 4.1.
You can use the outcome in a (generated) SQL script or feed it directly to the MySQL command-line client.
Using the hash prevents passwords to be logged as plain text in the MySQL query log
or to be sent as plain text over a network.
"""
# $URL: svn+ssh://w-p@shell.dds.nl/nfs/vsp/dds.nl/w/w-p/repos/weggeefwinkel/MysqlPasswordHash/trunk/python/mysqlpasswordhash.py $
__author__ = '$Author: w-p $'[9:-2]
__version__ = '$Rev: 48 $'[6:-2]
__date__ = '$Date: 2012-11-28 10:05:51 +0100 (Wed, 28 Nov 2012) $'[7:-2]

import sys, hashlib
try:
    import gettext
    gettext.textdomain(sys.argv[0])
    _ = gettext.gettext
except:
    def _(strText):return strText
try:
    from getpass import getpass
except:
    getpass = raw_input

class MysqlPasswordHasher(object):
 """Calculates the hash that MySQL uses for passwords since version 4.1.
You can use the outcome in a (generated) SQL script or feed it directly to the MySQL command-line client.
Using the hash prevents passwords to be logged as plain text in the MySQL query log
or to be sent as plain text over a network."""
 def __init__(self):
     self._objOptions = None
 def ToHash(self, strPassword):
     """Returns the MySQL-formatted hash"""
     return "*" + hashlib.sha1(hashlib.sha1(strPassword).digest()).hexdigest().upper()
 def _ParseOptions(self, itrOptions):
     """Parse the command-line options into the self._objOptions property."""
     strEpilog = ""
     try:
         import argparse
         objParser = argparse.ArgumentParser(description=_(__doc__), version=_("%(prog)s version ") + __version__, epilog=strEpilog)
         objParser.add_argument("-a", "--ask-password", dest="askforpassword", help=_("Prompts for a password. This is the quickest way to calculate a password hash without the risk of accidentally logging the password in a history file."), action="store_true")
         objParser.add_argument("--create-user-statement", dest="createuser", help=_("Outputs a complete CREATE USER statement."), action="store_true")
         objParser.add_argument("-d", "--defaults-file", "--defaults-extra-file", dest="credentials", help=_("Read host, user and password from CREDENTIALSFILE. Such a file is often used to allow non-interactive scripts to connect to MySQL. The file must be an \"ini\" format configuration file with entries \"user\", \"password\" and optionally \"host\" in section \"mysql\" or in section \"client\"."), metavar=_("CREDENTIALSFILE"))
         objParser.add_argument("--grant-usage-on", dest="grantusage", help=_("Outputs a GRANT statement for usage of the supplied DATABASE."), metavar=_("DATABASE"))
         objParser.add_argument("-H", "--host", help=_("host name or address. May contain wildcards, like '10.77.77.%%'. Defaults to localhost (which is not the same as 127.0.0.1 to MySQL). When a host is given both in a credentials file and as an argument, the argument is used. This allows you to use wildcards."), metavar=_("HOST"), default="localhost")
         objParser.add_argument("-p", "--password", help=_("supply the password as an argument. BE CAREFUL: command-lines are often logged in a history file if not called from within a script."), metavar=_("PASSWORD"))
         objParser.add_argument("-s", "--script", help=_("Replaces usages of plain-text passwords in SQL_SCRIPT by hashes and outputs the modified SQL script on stdout. Specifically, the clauses \"IDENTIFIED BY '...'\" and \"PASSWORD('...')\"are replaced by their hashed counterparts. If you pass a hyphen (-) as a file name, stdin is used."), metavar=_("SQL_SCRIPT"))
         objParser.add_argument("--set-password-statement", dest="setpassword", help=_("Outputs a complete SET PASSWORD statement."), action="store_true")
         objParser.add_argument("-u", "--user", help=_("The username to be used in the generated SQL."), metavar=_("USER"))
         objParser.add_argument("--user-clause", dest="userclause", help=_("Outputs a complete user clause in the form of 'USER'@'HOST' IDENTIFIED BY PASSWORD 'HASH'."), action="store_true")
         objParser.add_argument("positionalpassword", nargs="?", metavar=_("PASSWORD"))
         self._objOptions = objParser.parse_args()
         if isinstance(self._objOptions.positionalpassword, str) and (self._objOptions.password==None):
            self._objOptions.password = self._objOptions.positionalpassword
     except SystemExit:
         raise
     except:
         print sys.exc_info() 
         import optparse
         objParser = optparse.OptionParser(description=__doc__, version=__version__, epilog=strEpilog)
         objParser.add_option("-a", "--ask-password", dest="askforpassword", help=_("Prompts for a password. This is the quickest way to calculate a password hash without the risk of accidentally logging the password in a history file."), action="store_true")
         objParser.add_option("--create-user-statement", dest="createuser", help=_("Outputs a complete CREATE USER statement."), action="store_true")
         objParser.add_option("-d", "--defaults-file", "--defaults-extra-file", dest="credentials", help=_("Read host, user and password from CREDENTIALSFILE. Such a file is often used to allow non-interactive scripts to connect to MySQL. The file must be an \"ini\" format configuration file with entries \"user\", \"password\" and optionally \"host\" in section \"mysql\" or in section \"client\"."), metavar=_("CREDENTIALSFILE"))
         objParser.add_option("--grant-usage-on", dest="grantusage", help=_("Outputs a GRANT statement for usage of the supplied DATABASE."), metavar=_("DATABASE"))
         objParser.add_option("-H", "--host", help="host name or address. May contain wildcards, like '10.77.77.%%'. Defaults to localhost (which is not the same as 127.0.0.1 to MySQL). When a host is given both in a credentials file and as an argument, the argument is used. This allows you to use wildcards.", metavar=_("HOST"), default="localhost")
         objParser.add_option("-p", "--password", help="supply the password as an argument. BE CAREFUL: command-lines are often logged in a history file if not called from within a script.", metavar=_("PASSWORD"))
         objParser.add_option("-s", "--script", help="Replaces usages of plain-text passwords in SQL_SCRIPT by hashes and outputs the modified SQL script on stdout. Specifically, the clauses \"IDENTIFIED BY '...'\" and \"PASSWORD('...')\"are replaced by their hashed counterparts. If you pass a hyphen (-) as a file name, stdin is used.", metavar="SQL_SCRIPT")
         objParser.add_option("--set-password-statement", dest="setpassword", help="Outputs a complete SET PASSWORD statement.", action="store_true")
         objParser.add_option("-u", "--user", help="The username to be used in the generated SQL.", metavar=_("USER"))
         objParser.add_option("--user-clause", dest="userclause", help="Outputs a complete user clause in the form of 'USER'@'HOST' IDENTIFIED BY PASSWORD 'HASH'.", action="store_true")
         (self._objOptions, itrArguments) = objParser.parse_args()
         if len(itrArguments)>0 and not hasattr(self._objOptions, "password"):
            self._objOptions.password = itrArguments[0]
     if self._objOptions.askforpassword:
        self._objOptions.password = getpass(_("password: "))
     if len(sys.argv)<2:
        objParser.print_usage()
 def ReadCredentials(self):
     """Read username and password from a "defaults" file."""
     try:
         import ConfigParser as configparser
     except:
         import configparser
     objParser = configparser.RawConfigParser()
     objParser.read(self._objOptions.credentials)
     for strSection in ("mysql", "client"):
         if objParser.has_section(strSection):
            if objParser.has_option(strSection, "password"):
               self._objOptions.user = ""
               self._objOptions.password = self._WithoutQuotes(objParser.get(strSection, "password"))
               if objParser.has_option(strSection, "user"):
                  self._objOptions.user = self._WithoutQuotes(objParser.get(strSection, "user"))
                  if objParser.has_option(strSection, "host") and self._objOptions.host!='localhost':
                     self._objOptions.host = self._WithoutQuotes(objParser.get(strSection, "host"))
 def IdentifiedReplacer(self, objMatch):
     """Used in Regular expression replacement. Handles the IDENTIFIED BY clause."""
     strPassword = objMatch.group(1)[1:-1] # strip the quotes
     return "IDENTIFIED BY PASSWORD '" + self.ToHash(strPassword) + "'"
 def PasswordReplacer(self, objMatch):
     """Used in Regular expression replacement. Handles the PASSWORD() function."""
     strPassword = objMatch.group(1)[1:-1] # strip the quotes
     return "'" + self.ToHash(strPassword) + "'"
 def ProcessScript(self):
     """Replace all passwords by hashes in an SQL script."""
     if self._objOptions.script=="-":
        objFile = sys.stdin
     else:
        objFile = open(self._objOptions.script, "r")
     import re
     lstIdentifies = []
     lstPasswordFuncs = []
     lstIdentifies.append(re.compile(r"\bIDENTIFIED\s+BY\s+('[^\']+')", re.IGNORECASE))
     lstIdentifies.append(re.compile(r'\bIDENTIFIED\s+BY\s+("[^\"]+")', re.IGNORECASE))
     lstPasswordFuncs.append(re.compile(r"\bPASSWORD\s*\(\s*('[^\']+')\s*\)", re.IGNORECASE))
     lstPasswordFuncs.append(re.compile(r'\bPASSWORD\s*\(\s*("[^\"]+")\s*\)', re.IGNORECASE))
     for strLine in objFile:
         for rex in lstIdentifies:
             strLine = rex.sub(self.IdentifiedReplacer, strLine)
         for rex in lstPasswordFuncs:
             strLine = rex.sub(self.PasswordReplacer, strLine)
         sys.stdout.write(strLine)
 def ReadOptions(self, itrOptions=sys.argv[1:]):
     """Parse the command-line options"""
     self._ParseOptions(itrOptions)
     if self._objOptions.credentials!=None:
        self.ReadCredentials()
#      print self._objOptions
     if self._objOptions.script==None:
        if self._objOptions.userclause:
           print "".join(["'", self._objOptions.user, "'@'", self._objOptions.host, "' IDENTIFIED BY PASSWORD '", self.ToHash(self._objOptions.password), "'"])
        elif self._objOptions.createuser:
           print "".join(["CREATE USER '", self._objOptions.user, "'@'", self._objOptions.host, "' IDENTIFIED BY PASSWORD '", self.ToHash(self._objOptions.password), "';"])
        elif self._objOptions.setpassword:
           print "".join(["SET PASSWORD FOR '", self._objOptions.user, "'@'", self._objOptions.host, "' = '", self.ToHash(self._objOptions.password), "';"])
        elif self._objOptions.grantusage!=None:
           print "".join(["GRANT USAGE ON `", self._objOptions.grantusage, "`.* TO '", self._objOptions.user, "'@'", self._objOptions.host, "' IDENTIFIED BY PASSWORD '", self.ToHash(self._objOptions.password), "';"])
        elif self._objOptions.password!=None:
           print self.ToHash(self._objOptions.password)
     else:
        self.ProcessScript()
 def _WithoutQuotes(self, strIniValue):
     """Strip the quotes that may optionally surround an ini value."""
     strResult = strIniValue
     if strIniValue[0]=="\"" and strIniValue[-1]=="\"":
        strResult = strIniValue[1:-1]
     return strResult

if __name__=="__main__":
   MysqlPasswordHasher().ReadOptions()
