3939from prompt_toolkit .lexers import PygmentsLexer
4040from prompt_toolkit .shortcuts import CompleteStyle , PromptSession
4141import pymysql
42+ from pymysql .constants .ER import HANDSHAKE_ERROR
4243from pymysql .cursors import Cursor
4344import sqlglot
4445import sqlparse
@@ -156,6 +157,14 @@ def __init__(
156157 self .post_redirect_command = c ['main' ].get ('post_redirect_command' )
157158 self .null_string = c ['main' ].get ('null_string' )
158159
160+ # set ssl_mode if a valid option is provided in a config file, otherwise None
161+ ssl_mode = c ["main" ].get ("ssl_mode" , None )
162+ if ssl_mode not in ("auto" , "on" , "off" , None ):
163+ self .echo (f"Invalid config option provided for ssl_mode ({ ssl_mode } ); ignoring." , err = True , fg = "red" )
164+ self .ssl_mode = None
165+ else :
166+ self .ssl_mode = ssl_mode
167+
159168 # read from cli argument or user config file
160169 self .auto_vertical_output = auto_vertical_output or c ["main" ].as_bool ("auto_vertical_output" )
161170 self .show_warnings = show_warnings or c ["main" ].as_bool ("show_warnings" )
@@ -568,6 +577,24 @@ def _connect() -> None:
568577 ssh_key_filename ,
569578 init_command ,
570579 )
580+ elif e .args [0 ] == HANDSHAKE_ERROR and ssl is not None and ssl .get ("mode" , None ) == "auto" :
581+ self .sqlexecute = SQLExecute (
582+ database ,
583+ user ,
584+ passwd ,
585+ host ,
586+ int_port ,
587+ socket ,
588+ charset ,
589+ use_local_infile ,
590+ None ,
591+ ssh_user ,
592+ ssh_host ,
593+ int (ssh_port ) if ssh_port else None ,
594+ ssh_password ,
595+ ssh_key_filename ,
596+ init_command ,
597+ )
571598 else :
572599 raise e
573600
@@ -1414,7 +1441,13 @@ def get_last_query(self) -> str | None:
14141441@click .option ("--ssh-key-filename" , help = "Private key filename (identify file) for the ssh connection." )
14151442@click .option ("--ssh-config-path" , help = "Path to ssh configuration." , default = os .path .expanduser ("~" ) + "/.ssh/config" )
14161443@click .option ("--ssh-config-host" , help = "Host to connect to ssh server reading from ssh configuration." )
1417- @click .option ("--ssl" , "ssl_enable" , is_flag = True , help = "Enable SSL for connection (automatically enabled with other flags)." )
1444+ @click .option (
1445+ "--ssl-mode" ,
1446+ "ssl_mode" ,
1447+ help = "Set desired SSL behavior. auto=preferred, on=required, off=off." ,
1448+ type = click .Choice (["auto" , "on" , "off" ]),
1449+ )
1450+ @click .option ("--ssl/--no-ssl" , "ssl_enable" , default = None , help = "Enable SSL for connection (automatically enabled with other flags)." )
14181451@click .option ("--ssl-ca" , help = "CA file in PEM format." , type = click .Path (exists = True ))
14191452@click .option ("--ssl-capath" , help = "CA directory." )
14201453@click .option ("--ssl-cert" , help = "X509 cert in PEM format." , type = click .Path (exists = True ))
@@ -1430,8 +1463,6 @@ def get_last_query(self) -> str | None:
14301463 is_flag = True ,
14311464 help = ("""Verify server's "Common Name" in its cert against hostname used when connecting. This option is disabled by default.""" ),
14321465)
1433- # as of 2016-02-15 revocation list is not supported by underling PyMySQL
1434- # library (--ssl-crl and --ssl-crlpath options in vanilla mysql client)
14351466@click .version_option (__version__ , "-V" , "--version" , help = "Output mycli's version." )
14361467@click .option ("-v" , "--verbose" , is_flag = True , help = "Verbose output." )
14371468@click .option ("-D" , "--database" , "dbname" , help = "Database to use." )
@@ -1480,6 +1511,7 @@ def cli(
14801511 auto_vertical_output : bool ,
14811512 show_warnings : bool ,
14821513 local_infile : bool ,
1514+ ssl_mode : str | None ,
14831515 ssl_enable : bool ,
14841516 ssl_ca : str | None ,
14851517 ssl_capath : str | None ,
@@ -1526,6 +1558,15 @@ def cli(
15261558 warn = warn ,
15271559 myclirc = myclirc ,
15281560 )
1561+
1562+ if ssl_enable is not None :
1563+ click .secho (
1564+ "Warning: The --ssl/--no-ssl CLI options are deprecated and will be removed in a future release. "
1565+ "Please use the ssl_mode config or --ssl-mode CLI options instead." ,
1566+ err = True ,
1567+ fg = "yellow" ,
1568+ )
1569+
15291570 if list_dsn :
15301571 try :
15311572 alias_dsn = mycli .config ["alias_dsn" ]
@@ -1622,19 +1663,36 @@ def cli(
16221663 ssl_verify_server_cert = ssl_verify_server_cert or (params [0 ].lower () == 'true' )
16231664 ssl_enable = True
16241665
1625- ssl = {
1626- "enable" : ssl_enable ,
1627- "ca" : ssl_ca and os .path .expanduser (ssl_ca ),
1628- "cert" : ssl_cert and os .path .expanduser (ssl_cert ),
1629- "key" : ssl_key and os .path .expanduser (ssl_key ),
1630- "capath" : ssl_capath ,
1631- "cipher" : ssl_cipher ,
1632- "tls_version" : tls_version ,
1633- "check_hostname" : ssl_verify_server_cert ,
1634- }
1635-
1636- # remove empty ssl options
1637- ssl = {k : v for k , v in ssl .items () if v is not None }
1666+ ssl_mode = ssl_mode or mycli .ssl_mode # cli option or config option
1667+
1668+ # if there is a mismatch between the ssl_mode value and other sources of ssl config, show a warning
1669+ # specifically using "is False" to not pickup the case where ssl_enable is None (not set by the user)
1670+ if ssl_enable and ssl_mode == "off" or ssl_enable is False and ssl_mode in ("auto" , "on" ):
1671+ click .secho (
1672+ f"Warning: The current ssl_mode value of '{ ssl_mode } ' is overriding the value provided by "
1673+ f"either the --ssl/--no-ssl CLI options or a DSN URI parameter (ssl={ ssl_enable } )." ,
1674+ err = True ,
1675+ fg = "yellow" ,
1676+ )
1677+
1678+ # configure SSL if ssl_mode is auto/on or if
1679+ # ssl_enable = True (from --ssl or a DSN URI) and ssl_mode is None
1680+ if ssl_mode in ("auto" , "on" ) or (ssl_enable and ssl_mode is None ):
1681+ ssl = {
1682+ "mode" : ssl_mode ,
1683+ "enable" : ssl_enable ,
1684+ "ca" : ssl_ca and os .path .expanduser (ssl_ca ),
1685+ "cert" : ssl_cert and os .path .expanduser (ssl_cert ),
1686+ "key" : ssl_key and os .path .expanduser (ssl_key ),
1687+ "capath" : ssl_capath ,
1688+ "cipher" : ssl_cipher ,
1689+ "tls_version" : tls_version ,
1690+ "check_hostname" : ssl_verify_server_cert ,
1691+ }
1692+ # remove empty ssl options
1693+ ssl = {k : v for k , v in ssl .items () if v is not None }
1694+ else :
1695+ ssl = None
16381696
16391697 if ssh_config_host :
16401698 ssh_config = read_ssh_config (ssh_config_path ).lookup (ssh_config_host )
0 commit comments