diff --git a/doc/internal/ProxySQL_Authentication.md b/doc/internal/ProxySQL_Authentication.md
new file mode 100644
index 0000000000..8cb1fe50d5
--- /dev/null
+++ b/doc/internal/ProxySQL_Authentication.md
@@ -0,0 +1,798 @@
+# Sequence Diagrams during authentication
+
+The initial handshake is what described in the MySQL protocol.
+
+```mermaid
+---
+title: Initial (not complete) Handshake
+---
+
+sequenceDiagram
+autonumber
+participant C as Client
+participant S as Session
+S ->> C: InitialHandshake
+opt SSL handshake
+C ->> S: SSL request
+C -> S: SSL handshake
+end
+C ->> S: HandshakeResponse
+Note over C, S: What happens next is described in
"flowchart after Initial Handshake"
+```
+
+1. implemented in `MySQL_Protocol::generate_pkt_initial_handshake()`
+2. performed by the client
+3. implemented in `handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE()` and `MySQL_Protocol::process_pkt_handshake_response()`
+4. performed by the client
+
+
+## flowchart after Initial Handshake
+
+After the Initial Handshake (described above) different sequences are possible. In here we can distinguish sequences based on the authentication plugin based by ProxySQL and the Client, resulting in 4 different sequences.
+
+```mermaid
+flowchart
+A[Initial Handshake]
+PAM{proxysql auth}
+CAM1{client auth}
+CAM2{client auth}
+A --> PAM
+SA[Sequence A]
+SB[Sequence B]
+SC[Sequence C]
+SD[Sequence D]
+PAM -->|mysql_native_password| CAM1
+PAM -->|caching_sha2_password| CAM2
+CAM1 -->|mysql_native_password| SA
+CAM1 -->|caching_sha2_password| SB
+CAM2 -->|mysql_native_password| SC
+CAM2 -->|caching_sha2_password| SD
+
+```
+
+
+## Sequence A
+ProxySQL: mysql_native_password
+
+client: mysql_native_password
+
+```mermaid
+---
+title: "Sequence A"
+---
+sequenceDiagram
+autonumber
+participant C as Client
+participant S as Session
+S ->> C: InitialHandshake
+opt SSL handshake
+C ->> S: SSL request
+C -> S: SSL handshake
+end
+C ->> S: HandshakeResponse
+alt valid credential
+S ->> C: OK
+else invalid credential
+S ->> C: Error
+end
+```
+
+
+
+## Sequence B
+ProxySQL: mysql_native_password
+
+client: caching_sha2_password
+
+
+When ProxySQL uses `mysql_native_password` but client uses `caching_sha2_password` , ProxySQL askes the client to switch to `mysql_native_password`.
+The client can either:
+* perform the authentication using `mysql_native_password` (point 6)
+* disconnect (point 9)
+
+```mermaid
+---
+title: "Sequence B"
+---
+sequenceDiagram
+autonumber
+participant C as Client
+participant S as Session
+S ->> C: InitialHandshake
+opt SSL handshake
+C ->> S: SSL request
+C -> S: SSL handshake
+end
+C ->> S: HandshakeResponse
+S ->> C: Authentication method switch
to mysql_native_password
+alt client agrees to switch
+C ->> S: hash (password + scramble)
+alt valid credential
+S ->> C: OK
+else invalid credential
+S ->> C: Error
+end
+else
+C --x S: disconnect
+end
+```
+
+
+
+
+## State Diagram of MySQL_Session during authentication
+
+When a new session is created the status `session_status___NONE` is assigned by default.
+After the initial handshake is sent, the status is set to `CONNECTING_CLIENT`.
+The status is finally changed to `WAITING_CLIENT_DATA` if the authentication is completed. If the authentication is not successful the session is simply destroyed.
+
+
+```mermaid
+---
+title: MySQL_Session status during authentication
+---
+stateDiagram-v2
+session_status___NONE --> CONNECTING_CLIENT CONNECTING_CLIENT --> WAITING_CLIENT_DATA
+```
+
+
+
+
+## Generic flowchart of authentication (without subgraphs)
+```mermaid
+flowchart
+A["generate_pkt_initial_handshake()"]
+A1["status=CONNECTING_CLIENT"]
+B["get_pkts_from_client()"]
+C{status}
+C1{client_myds->DSS}
+D["handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE()"]
+A --> A1
+A1 --> B
+B --> C
+C -->|CONNECTING_CLIENT| C1
+C1 -->|STATE_SERVER_HANDSHAKE| D
+C1 -->|STATE_SSL_INIT| D
+E["handshake_response_return = process_pkt_handshake_response()"]
+D --> E
+F{"handshake_response_return"}
+F1{"client_myds->auth_in_progress != 0"}
+E --> F
+F -->|false| F1
+F1 -->|Yes| B
+F2{"is_encrypted == false
+&&
+client_myds->encrypted == true"}
+F3[Initialize SSL]
+F1 -->|No| F2
+F2 -->|Yes| F3
+F3 --> B
+F2 -->|No| W
+G{correct session_type}
+F -->|true| G
+G -->|false| W
+OK["status=WAITING_CLIENT_DATA"]
+SOK["send OK to client"]
+G -->|true| SOK
+SOK --> OK
+OK --> B
+W[Disconnect]
+```
+
+
+After the initial handshake is sent and , `status` is set to `CONNECTING_CLIENT`.
+The main routine in `MySQL_Session` is `handler()` , and one of the main function it calls is `get_pkts_from_client()`.
+As the name suggests, `get_pkts_from_client()` is responsible from retrieving packets sent by the client: then it performs actions based on `status`.
+During authentication only `status==CONNECTING_CLIENT` is relevant.
+If `status==CONNECTING_CLIENT` and `client_myds->DSS` (`client_myds` represents the Client `MySQL_Data_Stream` , and `DSS` respesents its status) is either `STATE_SERVER_HANDSHAKE` or `STATE_SSL_INIT` , then `handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE()` is executed.
+`handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE()` calls `process_pkt_handshake_response()` and performs actions based on its return code.
+
+`process_pkt_handshake_response()` historically was responsible for only processing **HandshakeResponse** packet, but over time became more complex to also handle **SSL Handshake** , **Authentication method switch** , **Fast Authentication** and **Full Authentication** . The details of `process_pkt_handshake_response()` will be described in more detailed flowcharts and diagrams.
+For now it is worth to note that `process_pkt_handshake_response()` returns:
+* `true` when authentication succeeded
+* `false` when authentication failed or it is not completed yet (other status variables needs to ne evaluated)
+
+If `handshake_response_return`:
+* `true` : if `session_type` is correct (for example, a user defined in `mysql_users` table is not trying to connect to Admin, or viceversa) , the authentication succeeded, an OK packet is sent to the client, and status is changed to `WAITING_CLIENT_DATA`
+* `false` :
+ * if authentication is still in progress : continue
+ * if SSL has been required: initialize SSL and: continue
+ * else: wrong credentials, disconnect
+
+Below is the same flowchart with subgraphs.
+
+
+
+## Generic flowchart of authentication (with subgraphs)
+```mermaid
+flowchart
+subgraph sub0 [" "]
+A["generate_pkt_initial_handshake()"]
+A1["status=CONNECTING_CLIENT"]
+A --> A1
+end
+A1 --> sub1
+subgraph sub1 ["get_pkts_from_client()"]
+B["get_pkts_from_client()"]
+C{status}
+C1{client_myds->DSS}
+B --> C
+C -->|CONNECTING_CLIENT| C1
+end
+subgraph sub2 ["handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE()"]
+D["handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE()"]
+D --> E
+E["handshake_response_return = process_pkt_handshake_response()"]
+F{"handshake_response_return"}
+F1{"client_myds->auth_in_progress != 0"}
+E --> F
+F -->|false| F1
+F1 -->|Yes| B
+F2{"is_encrypted == false
+&&
+client_myds->encrypted == true"}
+F3[Initialize SSL]
+F1 -->|No| F2
+F2 -->|Yes| F3
+F3 --> B
+F2 -->|No| W
+G{correct session_type}
+F -->|true| G
+G -->|false| W
+W[Disconnect]
+OK["status=WAITING_CLIENT_DATA"]
+SOK["send OK to client"]
+G -->|true| SOK
+SOK --> OK
+OK --> B
+end
+C1 -->|STATE_SERVER_HANDSHAKE| D
+C1 -->|STATE_SSL_INIT| D
+```
+
+See description in previous flowchart.
+
+
+## Details about `MySQL_Protocol::process_pkt_handshake_response()`
+
+Because `MySQL_Protocol::process_pkt_handshake_response()` grew over time and was then split into multiple methods, variables are passed to and from methods using 2 objects of the following 2 classes:
+
+```mermaid
+---
+title: classes used by authentication functions
+---
+classDiagram
+class MyProt_tmp_auth_vars{
+ unsigned char *user
+ char *db
+ char *db_tmp
+ unsigned char *pass
+ char *password
+ unsigned char *auth_plugin
+ void *sha1_pass=NULL
+ unsigned char *_ptr
+ unsigned int charset
+ uint32_t capabilities
+ uint32_t max_pkt
+ uint32_t pass_len
+ bool use_ssl
+ enum proxysql_session_type session_type
+}
+class MyProt_tmp_auth_attrs {
+ char *default_schema
+ char *attributes
+ int default_hostgroup
+ int max_connections
+ bool schema_locked
+ bool transaction_persistent
+ bool fast_forward
+ bool _ret_use_ssl
+}
+```
+
+### Flowchart of `MySQL_Protocol::process_pkt_handshake_response()`
+
+
+```mermaid
+flowchart
+A[Read packet header]
+B{"username is
+already known
+from aprevious
+packet"}
+PPHR_1{"rc =
+PPHR_1()"}
+DOAUTH[__do_auth]
+EXITDOAUTH[__exit_do_auth]
+EXIT[__exit_process_pkt_handshake_response]
+ASSERT["assert(0)"]
+A --> B
+PPHR_2{"bool_rc =
+PPHR_2()"}
+subgraph 16
+
+B -->|Yes| PPHR_1
+PPHR_1 -->|default| ASSERT
+B -->|No| PPHR_2
+PPHR_2 -->|true| PPHR_3["PPHR_3()"]
+end
+
+PPHR_1 -->|1| EXIT
+PPHR_1 -->|2| DOAUTH
+
+
+PPHR_2 -->|false| EXIT
+
+SAPID{sent_auth_plugin_id}
+PPHR_3 --> SAPID
+APID1{auth_plugin_id}
+APID2{auth_plugin_id}
+SAPID -->|AUTH_MYSQL_NATIVE_PASSWORD| APID1
+SAPID -->|AUTH_MYSQL_CACHING_SHA2_PASSWORD| APID2
+
+
+subgraph 13
+APID1_-1{"bool_rc =
+PPHR_4auth0()"}
+APID1 -->|AUTH_UNKNOWN_PLUGIN| APID1_-1
+APID1_0{"bool_rc =
+PPHR_4auth1()"}
+APID1 -->|AUTH_MYSQL_NATIVE_PASSWORD| APID1_0
+APID1 -->|default| a2["assert(0)"]
+end
+APID1_-1 -->|false| EXIT
+APID1_-1 -->|true| DOAUTH
+APID1_0 -->|false| EXIT
+APID1_0 -->|true| DOAUTH
+APID1 -->|AUTH_MYSQL_CLEAR_PASSWORD| DOAUTH
+
+
+APID2_0{"bool_rc =
+PPHR_4auth0()"}
+
+subgraph 14
+APID2 -->|AUTH_UNKNOWN_PLUGIN| APID2_0
+APID2 -->|AUTH_MYSQL_NATIVE_PASSWORD| APID2_0
+APID2_sha2_a{auth_in_progress}
+APID2_sha2_s{switching_auth_stage}
+APID2 -->|AUTH_MYSQL_CACHING_SHA2_PASSWORD| APID2_sha2_a
+APID2_sha2_a -->|0| APID2_sha2_s
+APID2_sha2_a -->|default| a3["assert(0)"]
+end
+APID2 -->|AUTH_MYSQL_CLEAR_PASSWORD| DOAUTH
+APID2_0 -->|true| DOAUTH
+APID2_0 -->|false| EXIT
+APID2_sha2_s -->|0| DOAUTH
+APID2_sha2_s -->|default| a3
+
+getCharset[get client charset/collation]
+DOAUTH --> getCharset
+
+
+
+subgraph 18 [" "]
+gcv{valid collation}
+getCharset --> gcv
+
+sessionType1{session_type}
+gcv -->|true| sessionType1
+P1Click["password = GloClickHouseAuth->lookup()"]
+P1MySQL["password = GloMyAuth->lookup()"]
+
+
+sessionType1 -->|PROXYSQL_SESSION_CLICKHOUSE| P1Click
+sessionType1 -->|default| P1MySQL
+P1NULL{password == NULL}
+P1Click --> P1NULL
+P1MySQL --> P1NULL
+end
+gcv -->|false| EXITDOAUTH
+
+
+sessionType2{session_type}
+PPHR_5passwordFalse_0["
+PPHR_5passwordFalse_0()
+
+If username and password are the one used
+by MySQL_Monitor, set ret=true .
+This allows connections from MySQL Monitor module.
+"]
+
+P1NULL -->|true| sessionType2
+subgraph sub2
+
+sessionType2 -->|PROXYSQL_SESSION_ADMIN| PPHR_5passwordFalse_0
+sessionType2 -->|PROXYSQL_SESSION_STATS| PPHR_5passwordFalse_0
+
+APID3{auth_plugin_id}
+sessionType2 -->|default| APID3
+PPHR_5passwordFalse_auth2["
+PPHR_5passwordFalse_auth2()
+
+Relevant only for LDAP Authentication.
+TODO: Document it properly.
+"]
+APID3 -->|AUTH_MYSQL_CLEAR_PASSWORD| PPHR_5passwordFalse_auth2
+
+
+end
+PPHR_5passwordFalse_0 --> EXITDOAUTH
+APID3 -->|default| EXITDOAUTH
+PPHR_5passwordFalse_auth2 --> EXITDOAUTH
+
+PPHR_5passwordTrue["PPHR_5passwordTrue()
+
+Assignes all the variables retrieved
+from the Authentication module to
+related MySQL_Session object.
+"]
+P1NULL -->|false| PPHR_5passwordTrue
+EP{"password == ''"}
+PPHR_5passwordTrue --> EP
+EP -->|true| RET1[ret=true]
+RET1 --> EXITDOAUTH
+APID_SHA2_PASS{"auth_plugin_id ==
+AUTH_MYSQL_CACHING_SHA2_PASSWORD
+&&
+password in SHA2 format"}
+EP -->|false| APID_SHA2_PASS
+
+subgraph sub3
+APID_SHA2_PASS -->|false| PASSCT
+APID_SHA2_PASS -->|true| PPHR_sha2full1["PPHR_sha2full(AUTH_MYSQL_CACHING_SHA2_PASSWORD)"]
+end
+
+PPHR_sha2full1 --> EXITDOAUTH
+PASSCT{"password in
+cleartext format"}
+
+subgraph sub4
+PASSCT -->|true| APID4{auth_plugin_id}
+APID4 -->|AUTH_MYSQL_NATIVE_PASSWORD| SM{scrambles match}
+
+SM -->|true| RET2["ret=true"]
+APID4 -->|AUTH_MYSQL_CLEAR_PASSWORD| PM{passwords match}
+
+PM -->|true| RET3["ret=true"]
+PPHR_6auth2["PPHR_6auth2()
+
+Performs a fast authentication using
+caching_sha2_password. Fast authentication
+means that we already have the password in
+clear text and we can compare the hash of
+password and scramble generated by the client.
+
+Sets ret=true if the hashes match.
+"]
+APID4 -->|AUTH_MYSQL_CACHING_SHA2_PASSWORD| PPHR_6auth2
+PPHR_6auth2 --> RET4{ret == true}
+RET4 -->|true| SFAS["send fast_auth_success"]
+end
+
+SM -->|false| EXITDOAUTH
+PM -->|false| EXITDOAUTH
+RET4 -->|false| EXITDOAUTH
+SFAS --> EXITDOAUTH
+RET2 --> EXITDOAUTH
+RET3 --> EXITDOAUTH
+
+
+PASS_SHA1{"password in
+sha1 format"}
+PASSCT -->|false| PASS_SHA1
+subgraph sub5
+
+PASS_SHA1 -->|true| APID5{auth_plugin_id}
+PPHR_7auth1["PPHR_7auth1()
+
+Used in mysql_native_password.
+If SHA1 of password and scramble match:
+ret = true
+If sha1 wasn't available, save it in GloMyAuth
+"]
+PPHR_7auth2["PPHR_7auth2()
+
+Used in mysql_clear_password and when
+only double SHA1 is known.
+If double SHA1 of password match:
+ret = true
+If sha1 wasn't available, save it in GloMyAuth
+"]
+APID5 -->|AUTH_MYSQL_NATIVE_PASSWORD| PPHR_7auth1
+APID5 -->|AUTH_MYSQL_CLEAR_PASSWORD| PPHR_7auth2
+APID5 -->|AUTH_MYSQL_CACHING_SHA2_PASSWORD| PPHR_sha2full2["PPHR_sha2full(AUTH_MYSQL_NATIVE_PASSWORD)"]
+end
+
+PASS_SHA1 -->|false| EXITDOAUTH
+PPHR_7auth1 --> EXITDOAUTH
+PPHR_7auth2 --> EXITDOAUTH
+PPHR_sha2full2 --> EXITDOAUTH
+
+EXITDOAUTH --> PPHR_SetConnAttrs["PPHR_SetConnAttrs()"]
+v1use_ssl{vars1.use_ssl}
+PPHR_SetConnAttrs --> v1use_ssl
+v1use_ssl -->|true| RET5[ret=true]
+RET5 --> EXIT
+rc5{ret}
+v1use_ssl -->|false| rc5
+subgraph subrc5
+rc5 -->|true| SETCC[Set correct credentials in userinfo]
+rc5 -->|false| SETEC[Set empty credentials in userinfo]
+SETCC --> CH["compute_hash()"]
+SETEC --> CH["compute_hash()"]
+end
+
+CH --> EXIT
+EXIT --> Cleanup["Perform cleanup of temporary variables"]
+Cleanup --> rc6{ret}
+rc6 -->|true| verify_user_attributes["verify_user_attributes()"]
+rc6 -->|false| RetRet["return ret"]
+verify_user_attributes --> RetRet
+```
+
+
+### Flowchart of `MySQL_Protocol::PPHR_1()`
+
+
+```mermaid
+flowchart
+SAS1{switching_auth_stage}
+SAS1 -->|1| sas2["switching_auth_stage = 2
+It means: stage1 (used by MYSQL_NATIVE_PASSWORD)
+is completed"]
+SAS1 -->|4| sas5["switching_auth_stage = 5
+It means: stage4 (used by CACHING_SHA2_PASSWORD)
+is completed"]
+AIP["auth_in_progress = 0
+
+This signals that the authorization should complete now
+"]
+SAS1 --> AIP
+sas2 --> AIP
+sas5 --> AIP
+PL{packet len}
+AIP --> PL
+PL -->|5| cd["Client disconnected
+without performing the switch
+"] --> ret1[return 1]
+APID["
+We previously stored the auth_plugin_id in myds->switching_auth_type
+auth_plugin_id = myds->switching_auth_type
+"]
+PL --> APID
+auth_plugin_id{auth_plugin_id}
+APID --> auth_plugin_id
+auth_plugin_id -->|AUTH_MYSQL_NATIVE_PASSWORD| PL1[password = the rest of the packet]
+auth_plugin_id -->|default| PL2[password = NULL terminated C string]
+Con1["Retrieve previously stored variables
+from myds , userinfo, and myconn "]
+PL1 --> Con1
+PL2 --> Con1
+Con1 --> ret2[return 2]
+```
+
+
+### Flowchart of `MySQL_Protocol::PPHR_2()`
+
+This method is the one responsible for parsing the very first Handshake Response from the client.
+
+```mermaid
+flowchart
+A["Parse capabilities and max_allowed_pkt,
+and save them in myconn->options
+"]
+STS{"encrypted == false
+&&
+packet_length == header + 32
+"}
+A --> STS
+SV["This is an SSLRequest.
+Client wants to switch to SSL
+
+encrypted = true
+use_ssl = true
+ret = false
+"]
+STS -->|Yes| SV
+SV --> RF["return false"]
+cv{charset == 0}
+STS -->|No| cv
+SDC["set charset = default SQL_CHARACTER_SET
+See bug #810"]
+cv -->|Yes| SDC
+GetUser["Parse username"]
+cv -->|No| GetUser
+SDC --> GetUser
+GetUser --> GetPass["Parse authentication data"] -- on error --> E1["ret = false"] --> RF
+GetPass --> GetDB["Parse database name"]
+GetDB --> GetAuthPlugin["Parse authentication plugin"]
+GetAuthPlugin --> RT["return true"]
+```
+
+### Flowchart of `MySQL_Protocol::PPHR_3()`
+
+This method is the one responsible for detecting the authentication plugin to use .
+It opererates on three variables with similar names:
+* `vars1.auth_plugin` : the plugin that the client wish to use
+* `sent_auth_plugin_id` : member of `MySQL_Protocol` . It defines which default plugin was sent by ProxySQL to the client
+* `auth_plugin_id` : member of `MySQL_Protocol` . It defines which plugin is being used
+
+It is worth noticing that any unknown plugin is threated as unknown.
+Also, if ProxySQL sends `mysql_native_password` and the client sends `caching_sha2_password` , ProxySQL will threat it as unknown, then forcing the client to switch to `mysql_native_password`.
+
+
+```mermaid
+flowchart
+AP1{"vars1.auth_plugin
+==
+NULL"}
+B["vars1.auth_plugin = mysql_native_password
+auth_plugin_id = AUTH_MYSQL_NATIVE_PASSWORD
+"]
+AP1 -->|Yes| B
+B -->APID
+AP1 -->|No| APID
+APID{"auth_plugin_id
+==
+AUTH_UNKNOWN_PLUGIN"}
+APID -->|No| return
+AP2{"vars1.auth_plugin"}
+APID -->|Yes| AP2
+AP2 -->|mysql_native_password| S1["auth_plugin_id =
+AUTH_MYSQL_NATIVE_PASSWORD"]
+AP2 -->|mysql_clear_password| S2["auth_plugin_id =
+AUTH_MYSQL_CLEAR_PASSWORD"]
+SAPID{sent_auth_plugin_id}
+AP2 -->|caching_sha2_password| SAPID
+SAPID -->|AUTH_MYSQL_NATIVE_PASSWORD| S3["auth_plugin_id =
+AUTH_UNKNOWN_PLUGIN"]
+SAPID -->|AUTH_MYSQL_CACHING_SHA2_PASSWORD| S4["auth_plugin_id =
+AUTH_MYSQL_CACHING_SHA2_PASSWORD"]
+S1 --> return
+S2 --> return
+S3 --> return
+S4 --> return
+```
+
+
+### Flowchart of `MySQL_Protocol::PPHR_4auth0()`
+
+TODO
+
+
+### Flowchart of `MySQL_Protocol::PPHR_4auth1()`
+
+This method is the one responsible for determining if ProxySQL can switch authentication to `mysql_clear_password` for LDAP plugin.
+At its core, it verify that the requested user doesn't exist.
+
+```mermaid
+---
+title: MySQL_Protocol::PPHR_4auth1()
+---
+flowchart
+A{"LDAP
+Authentication
+enabled"}
+SAS{"switching_auth_stage
+==
+0"}
+UE{"
+user_exists
+=
+GloMyAuth->exists
+"}
+A -->|Yes| SAS
+A -->|No| RT
+SAS -->|Yes| UE
+SAS -->|No| RT
+UE -->|No| RT
+C1["
+switching_auth_type = AUTH_MYSQL_CLEAR_PASSWORD
+switching_auth_stage = 1
+auth_in_progress = 1
+"]
+UE --> C1
+C1 --> G["generate_pkt_auth_switch_request()"]
+G --> RRF[ret = false]
+RRF --> RF[return false]
+RT[return true]
+```
+
+
+### `MySQL_Protocol::PPHR_5passwordTrue()`
+
+Give all the attributes received from the Authentication module in `MyProt_tmp_auth_attrs& attr1` , `MySQL_Protocol::PPHR_5passwordTrue()` is responsible for assigning all the variables to related `MySQL_Session` object.
+
+
+### `MySQL_Protocol::PPHR_5passwordFalse_0()`
+
+If `username` and `password` are the one used by `MySQL_Monitor` , set `ret=true` .
+This allows connections from MySQL Monitor module.
+
+
+### `MySQL_Protocol::PPHR_5passwordFalse_auth2()`
+
+TODO: document
+
+### `MySQL_Protocol::PPHR_6auth2()`
+
+Documented in the flowchart
+
+
+### `MySQL_Protocol::PPHR_7auth1()`
+
+Used for `mysql_native_password` authentication.
+If SHA1 of password and scramble match, then sets `ret=true`.
+If sha1 wasn't previous available, save it in `GloMyAuth` calling `GloMyAuth->set_SHA1()` .
+Also set it in `userinfo->sha1_pass`.
+
+### `MySQL_Protocol::PPHR_7auth2()`
+
+Used for `mysql_clear_password` authentication when password is saved as double SHA1.
+If the double SHA1 password match then sets `ret=true`.
+If sha1 wasn't previous available, save it in `GloMyAuth` calling `GloMyAuth->set_SHA1()` .
+Also set it in `userinfo->sha1_pass`.
+
+### Flowchart of `MySQL_Protocol::PPHR_sha2full()`
+
+This method is the one responsible to perform (start, or continue/complete) `caching_sha2_password` full authentication.
+If `switching_auth_stage`:
+* 0 : set it to 4, and **start** full authentication
+* 5 : **continue/complete** full authentication
+
+This function receives in `passformat` the format of the known password.
+
+
+```mermaid
+---
+title: MySQL_Protocol::PPHR_sha2full()
+---
+flowchart
+return
+SAS{switching_auth_stage}
+GOBP["generate_one_byte_pkt(perform_full_authentication)"]
+SV["switching_auth_type = auth_plugin_id
+switching_auth_stage = 4
+auth_in_progress = 1
+"]
+SAS -->|0| GOBP --> SV --> return
+PF1{"passformat"}
+SAS -->|5| PF1
+SAS -->|default| a1["assert(0)"]
+B5N1[Generate double SHA1]
+B5N2{"double
+SHA1s
+match"}
+PF1 -->|AUTH_MYSQL_NATIVE_PASSWORD| B5N1 --> B5N2
+
+B5C1["
+Extract salt and rounds
+of SHA256() from
+encoded hashed password
+"]
+B5C2["
+Run SHA256()
+rounds times on
+cleartext password"]
+B5C3{"encoded hashed
+passwords match"}
+PF1 -->|AUTH_MYSQL_CACHING_SHA2_PASSWORD| B5C1 --> B5C2 --> B5C3
+PF1 -->|default| a1
+SRT["ret = true"]
+B5N2 -->|No| RT
+B5N2 -->|Yes| SRT
+B5C3 -->|Yes| SRT
+RT{ret}
+SRT --> RT
+B5C3 -->|No| RT
+SCT["GloMyAuth->set_clear_text_password()
+
+Save (cache) clear text password in
+order to perform fast authentication.
+This is exactly what 'caching' means
+in caching_sha2_password
+"]
+RT -->|false| return
+RT -->|true| SCT
+SCT --> return
+```
diff --git a/include/MySQL_Authentication.hpp b/include/MySQL_Authentication.hpp
index 5947a9165f..fbd70bed03 100644
--- a/include/MySQL_Authentication.hpp
+++ b/include/MySQL_Authentication.hpp
@@ -12,9 +12,10 @@ typedef struct _account_details_t {
char *username;
char *password;
void *sha1_pass;
- bool use_ssl;
- int default_hostgroup;
+ char *clear_text_password;
char *default_schema;
+ int default_hostgroup;
+ bool use_ssl;
bool schema_locked;
bool transaction_persistent;
bool fast_forward;
@@ -79,6 +80,7 @@ class MySQL_Authentication {
void set_all_inactive(enum cred_username_type usertype);
void remove_inactives(enum cred_username_type usertype);
bool set_SHA1(char *username, enum cred_username_type usertype, void *sha_pass);
+ bool set_clear_text_password(char *username, enum cred_username_type usertype, const char *clear_text_password);
unsigned int memory_usage();
uint64_t get_runtime_checksum();
/**
diff --git a/include/MySQL_Data_Stream.h b/include/MySQL_Data_Stream.h
index 85c2b91280..489c9cf974 100644
--- a/include/MySQL_Data_Stream.h
+++ b/include/MySQL_Data_Stream.h
@@ -158,7 +158,10 @@ class MySQL_Data_Stream
int status; // status . FIXME: make it a ORable variable
int switching_auth_stage;
- int switching_auth_type;
+ enum proxysql_auth_plugins switching_auth_type;
+ // Updated **only** when an 'auth_switch' has been sent to client
+ enum proxysql_auth_plugins switching_auth_sent;
+ int auth_in_progress; // if 0 , no authentication is in progress. Any value greater than 0 depends from the implementation
unsigned int tmp_charset;
short revents;
diff --git a/include/MySQL_Protocol.h b/include/MySQL_Protocol.h
index ad849a124f..0d30b223bf 100644
--- a/include/MySQL_Protocol.h
+++ b/include/MySQL_Protocol.h
@@ -16,6 +16,14 @@ extern MySQL_Variables mysql_variables;
#define CLIENT_DEPRECATE_EOF (1UL << 24)
#endif
+
+enum proxysql_auth_plugins {
+ AUTH_UNKNOWN_PLUGIN = -1,
+ AUTH_MYSQL_NATIVE_PASSWORD = 0,
+ AUTH_MYSQL_CLEAR_PASSWORD,
+ AUTH_MYSQL_CACHING_SHA2_PASSWORD
+};
+
class MySQL_ResultSet {
private:
bool deprecate_eof_active;
@@ -91,6 +99,36 @@ uint8_t mysql_decode_length(unsigned char *ptr, uint64_t *len);
*/
my_bool proxy_mysql_stmt_close(MYSQL_STMT* mysql_stmt);
+class MyProt_tmp_auth_vars {
+ public:
+ unsigned char *user = NULL;
+ char *db = NULL;
+ char *db_tmp = NULL;
+ unsigned char *pass = NULL;
+ char *password = NULL;
+ unsigned char *auth_plugin = NULL;
+ void *sha1_pass=NULL;
+ unsigned char *_ptr = NULL;;
+ unsigned int charset;
+ uint32_t capabilities = 0;
+ uint32_t max_pkt;
+ uint32_t pass_len;
+ bool use_ssl = false;
+ enum proxysql_session_type session_type;
+};
+
+class MyProt_tmp_auth_attrs {
+ public:
+ char *default_schema = NULL;
+ char *attributes = NULL;
+ int default_hostgroup=-1;
+ int max_connections;
+ bool schema_locked;
+ bool transaction_persistent = true;
+ bool fast_forward = false;
+ bool _ret_use_ssl = false;
+};
+
class MySQL_Protocol {
private:
MySQL_Connection_userinfo *userinfo;
@@ -101,10 +139,16 @@ class MySQL_Protocol {
bool dump_pkt;
#endif
MySQL_Prepared_Stmt_info *current_PreStmt;
+ enum proxysql_auth_plugins sent_auth_plugin_id;
+ enum proxysql_auth_plugins auth_plugin_id;
uint16_t prot_status;
+ bool more_data_needed;
MySQL_Data_Stream *get_myds() { return *myds; }
MySQL_Protocol() {
+ sent_auth_plugin_id = AUTH_MYSQL_NATIVE_PASSWORD;
+ auth_plugin_id = AUTH_UNKNOWN_PLUGIN;
prot_status=0;
+ more_data_needed = false;
}
void init(MySQL_Data_Stream **, MySQL_Connection_userinfo *, MySQL_Session *);
@@ -139,6 +183,24 @@ class MySQL_Protocol {
// - pointer to the packet
// - size of the packet
bool process_pkt_handshake_response(unsigned char *pkt, unsigned int len);
+
+ // all the following functions were inline inside process_pkt_handshake_response() , but it was split for readibility
+ int PPHR_1(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1);
+ bool PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1);
+ void PPHR_3(MyProt_tmp_auth_vars& vars1);
+ bool PPHR_4auth0(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1);
+ bool PPHR_4auth1(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1);
+ void PPHR_5passwordTrue(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1);
+ void PPHR_5passwordFalse_0(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1);
+ void PPHR_5passwordFalse_auth2(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1 , void *& sha1_pass);
+ void PPHR_6auth2(bool& ret, MyProt_tmp_auth_vars& vars1);
+ void PPHR_sha2full(bool& ret, MyProt_tmp_auth_vars& vars1, enum proxysql_auth_plugins passformat);
+ void PPHR_7auth1(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1 , void *& sha1_pass);
+ void PPHR_7auth2(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1 , void *& sha1_pass);
+ void PPHR_SetConnAttrs(MyProt_tmp_auth_vars& vars1, MyProt_tmp_auth_attrs& attr1);
+
+ void generate_one_byte_pkt(unsigned char b);
+
bool process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned int len);
void * Query_String_to_packet(uint8_t sid, std::string *s, unsigned int *l);
/**
diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h
index 316d4e459c..86fdd26348 100644
--- a/include/MySQL_Thread.h
+++ b/include/MySQL_Thread.h
@@ -447,8 +447,10 @@ class MySQL_Threads_Handler
char *interfaces;
char *server_version;
char *keep_multiplexing_variables;
+ char *default_authentication_plugin;
//unsigned int default_charset; // removed in 2.0.13 . Obsoleted previously using MySQL_Variables instead
int handle_unknown_charset;
+ int default_authentication_plugin_int;
bool servers_stats;
bool commands_stats;
bool query_digests;
diff --git a/include/SQLite3_Server.h b/include/SQLite3_Server.h
index fe304c9a94..6b0526983e 100644
--- a/include/SQLite3_Server.h
+++ b/include/SQLite3_Server.h
@@ -35,7 +35,7 @@ class SQLite3_Server {
char *telnet_admin_ifaces;
char *telnet_stats_ifaces;
bool read_only;
- bool hash_passwords;
+// bool hash_passwords;
char * admin_version;
#ifdef DEBUG
bool debug;
diff --git a/include/proxysql_admin.h b/include/proxysql_admin.h
index d1a2741863..471dde2a4d 100644
--- a/include/proxysql_admin.h
+++ b/include/proxysql_admin.h
@@ -226,7 +226,7 @@ class ProxySQL_Admin {
char *telnet_admin_ifaces;
char *telnet_stats_ifaces;
bool admin_read_only;
- bool hash_passwords;
+// bool hash_passwords;
bool vacuum_stats;
char * admin_version;
char * cluster_username;
diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h
index 86141a9178..be78d1c69c 100644
--- a/include/proxysql_structs.h
+++ b/include/proxysql_structs.h
@@ -776,10 +776,12 @@ MySQL_HostGroups_Manager *MyHGM;
__thread char *mysql_thread___default_schema;
__thread char *mysql_thread___server_version;
__thread char *mysql_thread___keep_multiplexing_variables;
+__thread char *mysql_thread___default_authentication_plugin;
__thread char *mysql_thread___init_connect;
__thread char *mysql_thread___ldap_user_variable;
__thread char *mysql_thread___default_session_track_gtids;
__thread char *mysql_thread___firewall_whitelist_errormsg;
+__thread int mysql_thread___default_authentication_plugin_int;
__thread int mysql_thread___max_allowed_packet;
__thread bool mysql_thread___automatic_detect_sqli;
__thread bool mysql_thread___firewall_whitelist_enabled;
@@ -945,10 +947,12 @@ extern MySQL_HostGroups_Manager *MyHGM;
extern __thread char *mysql_thread___default_schema;
extern __thread char *mysql_thread___server_version;
extern __thread char *mysql_thread___keep_multiplexing_variables;
+extern __thread char *mysql_thread___default_authentication_plugin;
extern __thread char *mysql_thread___init_connect;
extern __thread char *mysql_thread___ldap_user_variable;
extern __thread char *mysql_thread___default_session_track_gtids;
extern __thread char *mysql_thread___firewall_whitelist_errormsg;
+extern __thread int mysql_thread___default_authentication_plugin_int;
extern __thread int mysql_thread___max_allowed_packet;
extern __thread bool mysql_thread___automatic_detect_sqli;
extern __thread bool mysql_thread___firewall_whitelist_enabled;
diff --git a/lib/Makefile b/lib/Makefile
index 032dea04b1..c752ff254c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -122,6 +122,7 @@ default: libproxysql.a
.PHONY: default
_OBJ_CXX := ProxySQL_GloVars.oo network.oo debug.oo configfile.oo Query_Cache.oo SpookyV2.oo MySQL_Authentication.oo gen_utils.oo sqlite3db.oo mysql_connection.oo MySQL_HostGroups_Manager.oo mysql_data_stream.oo MySQL_Thread.oo MySQL_Session.oo MySQL_Protocol.oo mysql_backend.oo Query_Processor.oo ProxySQL_Admin.oo ProxySQL_Config.oo ProxySQL_Restapi.oo MySQL_Monitor.oo MySQL_Logger.oo thread.oo MySQL_PreparedStatement.oo ProxySQL_Cluster.oo ClickHouse_Authentication.oo ClickHouse_Server.oo ProxySQL_Statistics.oo Chart_bundle_js.oo ProxySQL_HTTP_Server.oo ProxySQL_RESTAPI_Server.oo font-awesome.min.css.oo main-bundle.min.css.oo set_parser.oo MySQL_Variables.oo c_tokenizer.oo proxysql_utils.oo proxysql_coredump.oo proxysql_sslkeylog.oo \
+ sha256crypt.oo \
proxysql_find_charset.oo ProxySQL_Poll.oo
OBJ_CXX := $(patsubst %,$(ODIR)/%,$(_OBJ_CXX))
HEADERS := ../include/*.h ../include/*.hpp
diff --git a/lib/MySQL_Authentication.cpp b/lib/MySQL_Authentication.cpp
index e06bc8a003..6ce2497d3e 100644
--- a/lib/MySQL_Authentication.cpp
+++ b/lib/MySQL_Authentication.cpp
@@ -104,12 +104,18 @@ bool MySQL_Authentication::add(char * username, char * password, enum cred_usern
if (lookup != cg.bt_map.end()) {
ad=lookup->second;
if (strcmp(ad->password,password)) {
+ // the password has changed
free(ad->password);
ad->password=strdup(password);
if (ad->sha1_pass) {
free(ad->sha1_pass);
ad->sha1_pass=NULL;
}
+ if (ad->clear_text_password) {
+ free(ad->clear_text_password);
+ ad->clear_text_password=NULL;
+ }
+ // FIXME: if the password is a clear text password, automatically generate sha1_pass and clear_text_password
}
if (strcmp(ad->default_schema,default_schema)) {
free(ad->default_schema);
@@ -195,6 +201,8 @@ bool MySQL_Authentication::add(char * username, char * password, enum cred_usern
new_ad=true;
ad->sha1_pass=NULL;
ad->num_connections_used=0;
+ ad->clear_text_password = NULL;
+ // FIXME: if the password is a clear text password, automatically generate sha1_pass and clear_text_password
}
ad->use_ssl=use_ssl;
@@ -233,6 +241,7 @@ unsigned int MySQL_Authentication::memory_usage() {
if (ado->username) ret += strlen(ado->username) + 1;
if (ado->password) ret += strlen(ado->password) + 1;
if (ado->sha1_pass) ret += SHA_DIGEST_LENGTH;
+ if (ado->clear_text_password) ret += strlen(ado->clear_text_password) + 1;
if (ado->default_schema) ret += strlen(ado->default_schema) + 1;
if (ado->comment) ret += strlen(ado->comment) + 1;
if (ado->attributes) ret += strlen(ado->attributes) + 1;
@@ -246,6 +255,7 @@ unsigned int MySQL_Authentication::memory_usage() {
if (ado->username) ret += strlen(ado->username) + 1;
if (ado->password) ret += strlen(ado->password) + 1;
if (ado->sha1_pass) ret += SHA_DIGEST_LENGTH;
+ if (ado->clear_text_password) ret += strlen(ado->clear_text_password) + 1;
if (ado->default_schema) ret += strlen(ado->default_schema) + 1;
if (ado->comment) ret += strlen(ado->comment) + 1;
if (ado->attributes) ret += strlen(ado->attributes) + 1;
@@ -297,6 +307,7 @@ int MySQL_Authentication::dump_all_users(account_details_t ***ads, bool _complet
ad->num_connections_used=ado->num_connections_used;
ad->password=strdup(ado->password);
ad->sha1_pass=NULL;
+ ad->clear_text_password = NULL;
ad->use_ssl=ado->use_ssl;
ad->default_schema=strdup(ado->default_schema);
ad->attributes=strdup(ado->attributes);
@@ -318,6 +329,7 @@ int MySQL_Authentication::dump_all_users(account_details_t ***ads, bool _complet
ad->username=strdup(ado->username);
ad->password=strdup(ado->password);
ad->sha1_pass=NULL;
+ ad->clear_text_password = NULL;
ad->use_ssl=ado->use_ssl;
ad->default_hostgroup=ado->default_hostgroup;
ad->default_schema=strdup(ado->default_schema);
@@ -434,6 +446,7 @@ bool MySQL_Authentication::del(char * username, enum cred_username_type usertype
free(ad->username);
free(ad->password);
if (ad->sha1_pass) { free(ad->sha1_pass); ad->sha1_pass=NULL; }
+ if (ad->clear_text_password) { free(ad->clear_text_password); ad->clear_text_password=NULL; }
free(ad->default_schema);
free(ad->attributes);
free(ad->comment);
@@ -484,6 +497,40 @@ bool MySQL_Authentication::set_SHA1(char * username, enum cred_username_type use
return ret;
};
+bool MySQL_Authentication::set_clear_text_password(char * username, enum cred_username_type usertype, const char *clear_text_password) {
+ bool ret=false;
+ uint64_t hash1, hash2;
+ SpookyHash *myhash=new SpookyHash();
+ myhash->Init(1,2);
+ myhash->Update(username,strlen(username));
+ myhash->Final(&hash1,&hash2);
+ delete myhash;
+
+ creds_group_t &cg=(usertype==USERNAME_BACKEND ? creds_backends : creds_frontends);
+
+#ifdef PROXYSQL_AUTH_PTHREAD_MUTEX
+ pthread_rwlock_wrlock(&cg.lock);
+#else
+ spin_wrlock(&cg.lock);
+#endif
+ std::map::iterator lookup;
+ lookup = cg.bt_map.find(hash1);
+ if (lookup != cg.bt_map.end()) {
+ account_details_t *ad=lookup->second;
+ if (ad->clear_text_password) { free(ad->clear_text_password); ad->clear_text_password=NULL; }
+ if (clear_text_password) {
+ ad->clear_text_password = strdup(clear_text_password);
+ }
+ ret=true;
+ }
+#ifdef PROXYSQL_AUTH_PTHREAD_MUTEX
+ pthread_rwlock_unlock(&cg.lock);
+#else
+ spin_wrunlock(&cg.lock);
+#endif
+ return ret;
+};
+
bool MySQL_Authentication::exists(char * username) {
bool ret = false;
uint64_t hash1, hash2;
@@ -522,7 +569,13 @@ char * MySQL_Authentication::lookup(char * username, enum cred_username_type use
lookup = cg.bt_map.find(hash1);
if (lookup != cg.bt_map.end()) {
account_details_t *ad=lookup->second;
- ret=l_strdup(ad->password);
+ if (ad->clear_text_password == NULL) {
+ ret=strdup(ad->password);
+ } else {
+ // we return the best password we have
+ // if we were able to derive the clear text password, we provide that
+ ret=strdup(ad->clear_text_password);
+ }
if (use_ssl) *use_ssl=ad->use_ssl;
if (default_hostgroup) *default_hostgroup=ad->default_hostgroup;
if (default_schema) *default_schema=l_strdup(ad->default_schema);
@@ -565,6 +618,7 @@ bool MySQL_Authentication::_reset(enum cred_username_type usertype) {
free(ad->username);
free(ad->password);
if (ad->sha1_pass) { free(ad->sha1_pass); ad->sha1_pass=NULL; }
+ if (ad->clear_text_password) { free(ad->clear_text_password); ad->clear_text_password=NULL; }
free(ad->default_schema);
free(ad->comment);
free(ad->attributes);
diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp
index c2d4828e21..aa5d247d29 100644
--- a/lib/MySQL_Protocol.cpp
+++ b/lib/MySQL_Protocol.cpp
@@ -34,6 +34,15 @@ extern ClickHouse_Authentication *GloClickHouseAuth;
#include "proxysql_find_charset.h"
+
+char * sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen);
+
+static const char *plugins[3] = {
+ "mysql_native_password",
+ "mysql_clear_password",
+ "caching_sha2_password",
+};
+
#ifdef DEBUG
static void __dump_pkt(const char *func, unsigned char *_ptr, unsigned int len) {
@@ -457,6 +466,25 @@ bool MySQL_Protocol::generate_pkt_ERR(bool send, void **ptr, unsigned int *len,
return true;
}
+void MySQL_Protocol::generate_one_byte_pkt(unsigned char b) {
+ assert((*myds) != NULL);
+ uint8_t sequence_id;
+ sequence_id = (*myds)->pkt_sid;
+ sequence_id++;
+ mysql_hdr myhdr;
+ myhdr.pkt_id=sequence_id;
+ myhdr.pkt_length=2;
+ unsigned int size=myhdr.pkt_length+sizeof(mysql_hdr);
+ unsigned char *_ptr=(unsigned char *)l_alloc(size);
+ memcpy(_ptr, &myhdr, sizeof(mysql_hdr));
+ int l=sizeof(mysql_hdr);
+ _ptr[l]=1;
+ l++;
+ _ptr[l]=b;
+ (*myds)->PSarrayOUT->add((void *)_ptr,size);
+ (*myds)->pkt_sid=sequence_id;
+}
+
bool MySQL_Protocol::generate_pkt_OK(bool send, void **ptr, unsigned int *len, uint8_t sequence_id, unsigned int affected_rows, uint64_t last_insert_id, uint16_t status, uint16_t warnings, char *msg, bool eof_identifier) {
if ((*myds)->sess->mirror==true) {
return true;
@@ -1101,6 +1129,10 @@ uint8_t MySQL_Protocol::generate_pkt_row3(MySQL_ResultSet *myrs, unsigned int *l
bool MySQL_Protocol::generate_pkt_auth_switch_request(bool send, void **ptr, unsigned int *len) {
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Generating auth switch request pkt\n");
+ const char *plugins_names[3] = { "mysql_native_password", "mysql_clear_password", "caching_sha2_password" };
+ size_t plugins_lens[3];
+ for (int i=0; i<3; i++)
+ plugins_lens[i] = strlen(plugins_names[i]);
mysql_hdr myhdr;
myhdr.pkt_id=2;
if ((*myds)->encrypted) {
@@ -1113,15 +1145,21 @@ bool MySQL_Protocol::generate_pkt_auth_switch_request(bool send, void **ptr, uns
}
switch((*myds)->switching_auth_type) {
- case 1:
+ case AUTH_MYSQL_NATIVE_PASSWORD:
myhdr.pkt_length=1 // fe
- + (strlen("mysql_native_password")+1)
+ + (plugins_lens[0]+1)
+ 20 // scramble
+ 1; // 00
break;
- case 2:
+ case AUTH_MYSQL_CLEAR_PASSWORD:
myhdr.pkt_length=1 // fe
- + (strlen("mysql_clear_password")+1)
+ + (plugins_lens[1]+1)
+ + 1; // 00
+ break;
+ case AUTH_MYSQL_CACHING_SHA2_PASSWORD:
+ myhdr.pkt_length=1 // fe
+ + (plugins_lens[2]+1)
+ + 20 // scramble
+ 1; // 00
break;
default:
@@ -1140,17 +1178,23 @@ bool MySQL_Protocol::generate_pkt_auth_switch_request(bool send, void **ptr, uns
_ptr[l]=0xfe; l++; //0xfe
switch((*myds)->switching_auth_type) {
- case 1:
- memcpy(_ptr+l,"mysql_native_password",strlen("mysql_native_password"));
- l+=strlen("mysql_native_password");
+ case AUTH_MYSQL_NATIVE_PASSWORD:
+ memcpy(_ptr+l,plugins_names[0],plugins_lens[0]);
+ l+=plugins_lens[0];
_ptr[l]=0x00; l++;
memcpy(_ptr+l, (*myds)->myconn->scramble_buff+0, 20); l+=20;
break;
- case 2:
- memcpy(_ptr+l,"mysql_clear_password",strlen("mysql_clear_password"));
- l+=strlen("mysql_clear_password");
+ case AUTH_MYSQL_CLEAR_PASSWORD:
+ memcpy(_ptr+l,plugins_names[1],plugins_lens[1]);
+ l+=plugins_lens[1];
_ptr[l]=0x00; l++;
break;
+ case AUTH_MYSQL_CACHING_SHA2_PASSWORD:
+ memcpy(_ptr+l,plugins_names[2],plugins_lens[2]);
+ l+=plugins_lens[2];
+ _ptr[l]=0x00; l++;
+ memcpy(_ptr+l, (*myds)->myconn->scramble_buff+0, 20); l+=20;
+ break;
default:
// LCOV_EXCL_START
assert(0);
@@ -1163,6 +1207,8 @@ bool MySQL_Protocol::generate_pkt_auth_switch_request(bool send, void **ptr, uns
(*myds)->DSS=STATE_SERVER_HANDSHAKE;
(*myds)->sess->status=CONNECTING_CLIENT;
}
+ (*myds)->switching_auth_sent = (*myds)->switching_auth_type;
+
if (len) { *len=size; }
if (ptr) { *ptr=(void *)_ptr; }
#ifdef DEBUG
@@ -1172,7 +1218,9 @@ bool MySQL_Protocol::generate_pkt_auth_switch_request(bool send, void **ptr, uns
}
bool MySQL_Protocol::generate_pkt_initial_handshake(bool send, void **ptr, unsigned int *len, uint32_t *_thread_id, bool deprecate_eof_active) {
+ int use_plugin_id = mysql_thread___default_authentication_plugin_int;
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Generating handshake pkt\n");
+ assert(use_plugin_id == 0 || use_plugin_id == 2 ); // mysql_native_password or caching_sha2_password
mysql_hdr myhdr;
myhdr.pkt_id=0;
myhdr.pkt_length=sizeof(protocol_version)
@@ -1190,7 +1238,9 @@ bool MySQL_Protocol::generate_pkt_initial_handshake(bool send, void **ptr, unsig
+ 10 // filler
+ 12 // scramble2
+ 1 // 0x00
- + (strlen("mysql_native_password")+1);
+// + (strlen("mysql_native_password")+1);
+ + (strlen(plugins[use_plugin_id])+1);
+ sent_auth_plugin_id = (enum proxysql_auth_plugins)use_plugin_id;
unsigned int size=myhdr.pkt_length+sizeof(mysql_hdr);
unsigned char *_ptr=(unsigned char *)malloc(size);
@@ -1235,7 +1285,10 @@ bool MySQL_Protocol::generate_pkt_initial_handshake(bool send, void **ptr, unsig
} else {
mysql_thread___server_capabilities &= ~CLIENT_COMPRESS;
}
- if (mysql_thread___have_ssl) {
+ if (mysql_thread___have_ssl==true || mysql_thread___default_authentication_plugin_int==2) {
+ // we enable SSL for client connections for either of these 2 conditions:
+ // - have_ssl is enabled
+ // - default_authentication_plugin=caching_sha2_password
mysql_thread___server_capabilities |= CLIENT_SSL;
} else {
mysql_thread___server_capabilities &= ~CLIENT_SSL;
@@ -1296,7 +1349,8 @@ bool MySQL_Protocol::generate_pkt_initial_handshake(bool send, void **ptr, unsig
memcpy(_ptr+l, (*myds)->myconn->scramble_buff+8, 12); l+=12;
l+=1; //0x00
- memcpy(_ptr+l,"mysql_native_password",strlen("mysql_native_password"));
+ //memcpy(_ptr+l,"mysql_native_password",strlen("mysql_native_password"));
+ memcpy(_ptr+l,plugins[use_plugin_id],strlen(plugins[use_plugin_id]));
if (send==true) {
(*myds)->PSarrayOUT->add((void *)_ptr,size);
@@ -1384,28 +1438,36 @@ bool MySQL_Protocol::verify_user_pass(
char reply[SHA_DIGEST_LENGTH+1];
reply[SHA_DIGEST_LENGTH]='\0';
- int auth_plugin_id = 0;
+ auth_plugin_id = AUTH_UNKNOWN_PLUGIN; // default
- if (strncmp((char *)auth_plugin,(char *)"mysql_native_password",strlen((char *)"mysql_native_password"))==0) {
- auth_plugin_id = 1;
- }
- if (strncmp((char *)auth_plugin,(char *)"mysql_clear_password",strlen((char *)"mysql_clear_password"))==0) {
- auth_plugin_id = 2;
+ if (strncmp((char *)auth_plugin,plugins[0],strlen(plugins[0]))==0) { // mysql_native_password
+ auth_plugin_id = AUTH_MYSQL_NATIVE_PASSWORD;
+ } else if (strncmp((char *)auth_plugin,plugins[1],strlen(plugins[1]))==0) { // mysql_clear_password
+ auth_plugin_id = AUTH_MYSQL_CLEAR_PASSWORD;
+ } else if (strncmp((char *)auth_plugin,plugins[2],strlen(plugins[2]))==0) { // caching_sha2_password
+ //auth_plugin_id = 2; // FIXME: this is temporary, because yet not supported
+ auth_plugin_id = AUTH_MYSQL_CACHING_SHA2_PASSWORD; // FIXME: this is temporary, because yet not supported . It must become 3
}
if (password[0]!='*') { // clear text password
- if (auth_plugin_id == 1) { // mysql_native_password
+ if (auth_plugin_id == 0) { // mysql_native_password
proxy_scramble(reply, (*myds)->myconn->scramble_buff, password);
if (memcmp(reply, pass, SHA_DIGEST_LENGTH)==0) {
ret=true;
}
- } else { // mysql_clear_password
+ } else if (auth_plugin_id == 1) { // mysql_clear_password
if (strncmp(password,(char *)pass,strlen(password))==0) {
ret=true;
}
+ } else if (auth_plugin_id == 2) { // caching_sha2_password
+ // FIXME: not supported yet
+ // we assert() here because auth_plugin_id should never be 3 unless it is fully implemented
+ assert(0);
+ } else {
+ ret = false;
}
} else {
- if (auth_plugin_id == 1) {
+ if (auth_plugin_id == 2) {
if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE) {
ret=proxy_scramble_sha1((char *)pass,(*myds)->myconn->scramble_buff,password+1, reply);
if (ret) {
@@ -1419,26 +1481,20 @@ bool MySQL_Protocol::verify_user_pass(
} else {
if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE || session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , username='%s' , session_type=%d\n", (*myds), (*myds)->sess, user, session_type);
- uint8_t hash_stage1[SHA_DIGEST_LENGTH];
- uint8_t hash_stage2[SHA_DIGEST_LENGTH];
- SHA_CTX sha1_context;
- SHA1_Init(&sha1_context);
- SHA1_Update(&sha1_context, pass, pass_len);
- SHA1_Final(hash_stage1, &sha1_context);
- SHA1_Init(&sha1_context);
- SHA1_Update(&sha1_context,hash_stage1,SHA_DIGEST_LENGTH);
- SHA1_Final(hash_stage2, &sha1_context);
- // note that sha1_pass_hex() returns a new buffer
- char *double_hashed_password = sha1_pass_hex((char *)hash_stage2);
+ unsigned char md1_buf[SHA_DIGEST_LENGTH];
+ unsigned char md2_buf[SHA_DIGEST_LENGTH];
+ SHA1((const unsigned char *)pass,pass_len,md1_buf);
+ SHA1(md1_buf,SHA_DIGEST_LENGTH,md2_buf);
+ char *double_hashed_password = sha1_pass_hex((char *)md2_buf); // note that sha1_pass_hex() returns a new buffer
if (strcasecmp(double_hashed_password,password)==0) {
ret = true;
if (sha1_pass==NULL) {
- GloMyAuth->set_SHA1((char *)user, USERNAME_FRONTEND,hash_stage1);
+ GloMyAuth->set_SHA1((char *)user, USERNAME_FRONTEND,md1_buf);
}
if (userinfo->sha1_pass)
free(userinfo->sha1_pass);
- userinfo->sha1_pass=sha1_pass_hex((char *)hash_stage1);
+ userinfo->sha1_pass=sha1_pass_hex((char *)md1_buf);
} else {
ret = false;
}
@@ -1508,6 +1564,11 @@ bool MySQL_Protocol::process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned in
// FIXME: add support for default schema and fast forward, see issue #255 and #256
(*myds)->sess->default_hostgroup=default_hostgroup;
(*myds)->sess->transaction_persistent=transaction_persistent;
+ // Could be reached several times before auth completion; allocating attributes should be reset
+ if ((*myds)->sess->user_attributes) {
+ free((*myds)->sess->user_attributes);
+ (*myds)->sess->user_attributes = nullptr;
+ }
(*myds)->sess->user_attributes=user_attributes;
if (password==NULL) {
ret=false;
@@ -1519,7 +1580,7 @@ bool MySQL_Protocol::process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned in
// is required. We default to 'mysql_native_password'. See #3504 for more context.
if (pass_len == 0) {
// mysql_native_password
- (*myds)->switching_auth_type = 1;
+ (*myds)->switching_auth_type = AUTH_MYSQL_NATIVE_PASSWORD;
// started 'Auth Switch Request' for 'CHANGE_USER' in MySQL_Session.
(*myds)->sess->change_user_auth_switch = true;
@@ -1643,89 +1704,53 @@ bool MySQL_Protocol::process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned in
return ret;
}
-bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned int len) {
-#ifdef DEBUG
- if (dump_pkt) { __dump_pkt(__func__,pkt,len); }
-#endif
- bool ret = false;
- unsigned int charset;
- uint32_t capabilities = 0;
- uint32_t max_pkt;
- uint32_t pass_len;
- unsigned char *user = NULL;
- char *db = NULL;
- char *db_tmp = NULL;
- unsigned char *pass = NULL;
- MySQL_Connection *myconn = NULL;
- char *password = NULL;
- bool use_ssl = false;
- bool _ret_use_ssl = false;
- unsigned char *auth_plugin = NULL;
- int auth_plugin_id = 0;
-
- char reply[SHA_DIGEST_LENGTH+1] = { 0 };
- int default_hostgroup=-1;
- char *default_schema = NULL;
- char *attributes = NULL;
- bool schema_locked;
- bool transaction_persistent = true;
- bool fast_forward = false;
- int max_connections;
- enum proxysql_session_type session_type = (*myds)->sess->session_type;
-
- void *sha1_pass=NULL;
-//#ifdef DEBUG
- unsigned char *_ptr=pkt;
-//#endif
- mysql_hdr hdr;
- memcpy(&hdr,pkt,sizeof(mysql_hdr));
- //Copy4B(&hdr,pkt);
- pkt += sizeof(mysql_hdr);
-
- // NOTE: 'mysqlsh' sends a 'COM_INIT_DB' as soon as the connection is openned
- // before ProxySQL has sent 'Server Greeting' messsage. Because this packet is
- // unexpected, we simple return 'false' and exit.
- if (hdr.pkt_id == 0 && *pkt == 2) {
- ret = false;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . Client is disconnecting\n", (*myds), (*myds)->sess, user);
- goto __exit_process_pkt_handshake_response;
- }
-
- if ((*myds)->myconn->userinfo->username) {
+// this function was inline in process_pkt_handshake_response() , split for readibility
+int MySQL_Protocol::PPHR_1(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1) { // process_pkt_handshake_response inner 1
+ if ((*myds)->switching_auth_stage == 1) {
+ // this was set in PPHR_4auth0() or PPHR_4auth1()
(*myds)->switching_auth_stage=2;
- if (len==5) {
- ret = false;
- user = (unsigned char *)(*myds)->myconn->userinfo->username;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . Client is disconnecting\n", (*myds), (*myds)->sess, user);
- proxy_error("User '%s'@'%s' is disconnecting during switch auth\n", user, (*myds)->addr.addr);
- goto __exit_process_pkt_handshake_response;
- }
- auth_plugin_id = (*myds)->switching_auth_type;
- if (auth_plugin_id==1) {
- pass_len = len - sizeof(mysql_hdr);
- } else {
- pass_len=strlen((char *)pkt);
- }
- pass = (unsigned char *)malloc(pass_len+1);
- memcpy(pass, pkt, pass_len);
- pass[pass_len] = 0;
- user = (unsigned char *)(*myds)->myconn->userinfo->username;
- db = (*myds)->myconn->userinfo->schemaname;
- //(*myds)->switching_auth_stage=2;
- charset=(*myds)->tmp_charset;
- proxy_debug(PROXY_DEBUG_MYSQL_PROTOCOL,2,"Session=%p , DS=%p . Encrypted: %d , switching_auth: %d, auth_plugin_id: %d\n", (*myds)->sess, (*myds), (*myds)->encrypted, (*myds)->switching_auth_stage, auth_plugin_id);
- capabilities = (*myds)->myconn->options.client_flag;
- goto __do_auth;
}
+ if ((*myds)->switching_auth_stage == 4) {
+ // this was set in PPHR_sha2full()
+ (*myds)->switching_auth_stage=5;
+ }
+ (*myds)->auth_in_progress = 0;
+ if (len==5) {
+ ret = false;
+ vars1.user = (unsigned char *)(*myds)->myconn->userinfo->username;
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . Client is disconnecting\n", (*myds), (*myds)->sess, vars1.user);
+ proxy_error("User '%s'@'%s' is disconnecting during switch auth\n", vars1.user, (*myds)->addr.addr);
+ (*myds)->auth_in_progress = 0;
+ return 1;
+ }
+ auth_plugin_id = (*myds)->switching_auth_type;
+ if (auth_plugin_id == AUTH_MYSQL_NATIVE_PASSWORD) {
+ vars1.pass_len = len - sizeof(mysql_hdr);
+ } else {
+ vars1.pass_len=strlen((char *)pkt);
+ }
+ vars1.pass = (unsigned char *)malloc(vars1.pass_len+1);
+ memcpy(vars1.pass, pkt, vars1.pass_len);
+ vars1.pass[vars1.pass_len] = 0;
+ vars1.user = (unsigned char *)(*myds)->myconn->userinfo->username;
+ vars1.db = (*myds)->myconn->userinfo->schemaname;
+ //(*myds)->switching_auth_stage=2;
+ vars1.charset=(*myds)->tmp_charset;
+ proxy_debug(PROXY_DEBUG_MYSQL_PROTOCOL,2,"Session=%p , DS=%p . Encrypted: %d , switching_auth: %d, auth_plugin_id: %d\n", (*myds)->sess, (*myds), (*myds)->encrypted, (*myds)->switching_auth_stage, auth_plugin_id);
+ vars1.capabilities = (*myds)->myconn->options.client_flag;
+ return 2;
+}
- capabilities = CPY4(pkt);
+// this function was inline in process_pkt_handshake_response() , split for readibility
+bool MySQL_Protocol::PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1) { // process_pkt_handshake_response inner 2
+ vars1.capabilities = CPY4(pkt);
// see bug #2916. If CLIENT_MULTI_STATEMENTS is set by the client
// we enforce setting CLIENT_MULTI_RESULTS, this is the proper and expected
// behavior (refer to 'https://dev.mysql.com/doc/c-api/8.0/en/c-api-multiple-queries.html').
// Don't enforcing this would cause a mismatch between client and backend
// connections flags.
- if (capabilities & CLIENT_MULTI_STATEMENTS) {
- capabilities |= CLIENT_MULTI_RESULTS;
+ if (vars1.capabilities & CLIENT_MULTI_STATEMENTS) {
+ vars1.capabilities |= CLIENT_MULTI_RESULTS;
}
// we enforce disabling 'CLIENT_DEPRECATE_EOF' from the supported capabilities
// in case it's explicitly disabled by global variable 'mysql_thread___enable_client_deprecate_eof'.
@@ -1737,25 +1762,25 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned
// First step is replying to client during initial handshake (in 'generate_pkt_initial_handshake')
// specifying no 'CLIENT_DEPRECATE_EOF' support in 'server_capabilities'.
if (!mysql_thread___enable_client_deprecate_eof) {
- capabilities &= ~CLIENT_DEPRECATE_EOF;
- }
- (*myds)->myconn->options.client_flag = capabilities;
- pkt += sizeof(uint32_t);
- max_pkt = CPY4(pkt);
- (*myds)->myconn->options.max_allowed_pkt = max_pkt;
- pkt += sizeof(uint32_t);
- charset = *(uint8_t *)pkt;
+ vars1.capabilities &= ~CLIENT_DEPRECATE_EOF;
+ }
+ (*myds)->myconn->options.client_flag = vars1.capabilities;
+ pkt += sizeof(uint32_t);
+ vars1.max_pkt = CPY4(pkt);
+ (*myds)->myconn->options.max_allowed_pkt = vars1.max_pkt;
+ pkt += sizeof(uint32_t);
+ vars1.charset = *(uint8_t *)pkt;
if ( (*myds)->encrypted == false ) { // client wants to use SSL
if (len == sizeof(mysql_hdr)+32) {
(*myds)->encrypted = true;
- use_ssl = true;
+ vars1.use_ssl = true;
ret = false;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response\n", (*myds), (*myds)->sess, user);
- goto __exit_process_pkt_handshake_response;
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response\n", (*myds), (*myds)->sess, vars1.user);
+ return false;
}
}
// see bug #810
- if (charset==0) {
+ if (vars1.charset==0) {
const MARIADB_CHARSET_INFO *ci = NULL;
ci = proxysql_find_charset_name(mysql_thread___default_variables[SQL_CHARACTER_SET]);
if (!ci) {
@@ -1764,204 +1789,641 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned
assert(0);
// LCOV_EXCL_STOP
}
- charset=ci->nr;
+ vars1.charset=ci->nr;
}
- (*myds)->tmp_charset=charset;
- pkt += 24;
+ (*myds)->tmp_charset = vars1.charset;
+ pkt += 24;
// if (len==sizeof(mysql_hdr)+32) {
// (*myds)->encrypted=true;
// use_ssl=true;
// } else {
- user = pkt;
- pkt += strlen((char *)user) + 1;
+ vars1.user = pkt;
+ pkt += strlen((char *)vars1.user) + 1;
- if (capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) {
+ if (vars1.capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) {
uint64_t passlen64;
int pass_len_enc=mysql_decode_length(pkt,&passlen64);
- pass_len = passlen64;
+ vars1.pass_len = passlen64;
pkt += pass_len_enc;
- if (pass_len > (len - (pkt - _ptr))) {
+ if (vars1.pass_len > (len - (pkt - vars1._ptr))) {
ret = false;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response\n", (*myds), (*myds)->sess, user);
- goto __exit_process_pkt_handshake_response;
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response\n", (*myds), (*myds)->sess, vars1.user);
+ return false;
}
} else {
- pass_len = (capabilities & CLIENT_SECURE_CONNECTION ? *pkt++ : strlen((char *)pkt));
- if (pass_len > (len - (pkt - _ptr))) {
+ vars1.pass_len = (vars1.capabilities & CLIENT_SECURE_CONNECTION ? *pkt++ : strlen((char *)pkt));
+ if (vars1.pass_len > (len - (pkt - vars1._ptr))) {
ret = false;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response\n", (*myds), (*myds)->sess, user);
- goto __exit_process_pkt_handshake_response;
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response\n", (*myds), (*myds)->sess, vars1.user);
+ return false;
}
}
- pass = (unsigned char *)malloc(pass_len+1);
- memcpy(pass, pkt, pass_len);
- pass[pass_len] = 0;
+ vars1.pass = (unsigned char *)malloc(vars1.pass_len+1);
+ memcpy(vars1.pass, pkt, vars1.pass_len);
+ vars1.pass[vars1.pass_len] = 0;
- pkt += pass_len;
- if (capabilities & CLIENT_CONNECT_WITH_DB) {
- unsigned int remaining = len - (pkt - _ptr);
- db_tmp = strndup((const char *)pkt, remaining);
- if (db_tmp) {
- db = db_tmp;
+ pkt += vars1.pass_len;
+ if (vars1.capabilities & CLIENT_CONNECT_WITH_DB) {
+ unsigned int remaining = len - (pkt - vars1._ptr);
+ vars1.db_tmp = strndup((const char *)pkt, remaining);
+ if (vars1.db_tmp) {
+ vars1.db = vars1.db_tmp;
}
pkt++;
- if (db) {
- pkt+=strlen(db);
+ if (vars1.db) {
+ pkt+=strlen(vars1.db);
}
} else {
- db = NULL;
+ vars1.db = NULL;
}
- if (pass_len) {
- if (pass[pass_len-1] == 0) {
- pass_len--; // remove the extra 0 if present
+ if (vars1.pass_len) {
+ if (vars1.pass[vars1.pass_len-1] == 0) {
+ vars1.pass_len--; // remove the extra 0 if present
}
}
- if (_ptr+len > pkt) {
- if (capabilities & CLIENT_PLUGIN_AUTH) {
- auth_plugin = pkt;
+ if (vars1._ptr+len > pkt) {
+ if (vars1.capabilities & CLIENT_PLUGIN_AUTH) {
+ vars1.auth_plugin = pkt;
}
}
- if (auth_plugin == NULL) {
- auth_plugin = (unsigned char *)"mysql_native_password"; // default
- auth_plugin_id = 1;
- }
+ return true;
+}
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' , auth_plugin_id=%d\n", (*myds), (*myds)->sess, user, auth_plugin_id);
- if (auth_plugin_id == 0) {
- if (strncmp((char *)auth_plugin,(char *)"mysql_native_password",strlen((char *)"mysql_native_password"))==0) {
- auth_plugin_id = 1;
+void MySQL_Protocol::PPHR_3(MyProt_tmp_auth_vars& vars1) { // detect plugin id
+ if (vars1.auth_plugin == NULL) {
+ vars1.auth_plugin = (unsigned char *)"mysql_native_password"; // default
+ auth_plugin_id = AUTH_MYSQL_NATIVE_PASSWORD;
+ }
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' , auth_plugin_id=%d\n", (*myds), (*myds)->sess, vars1.user, auth_plugin_id);
+
+ if (auth_plugin_id == AUTH_UNKNOWN_PLUGIN) {
+ if (strncmp((char *)vars1.auth_plugin,plugins[0],strlen(plugins[0]))==0) { // mysql_native_password
+ auth_plugin_id = AUTH_MYSQL_NATIVE_PASSWORD;
+ } else if (strncmp((char *)vars1.auth_plugin,plugins[1],strlen(plugins[1]))==0) { // mysql_clear_password
+ auth_plugin_id = AUTH_MYSQL_CLEAR_PASSWORD;
+ } else if (strncmp((char *)vars1.auth_plugin,plugins[2],strlen(plugins[2]))==0) { // caching_sha2_password
+ if (sent_auth_plugin_id == AUTH_MYSQL_NATIVE_PASSWORD) {
+ // if we send mysql_native_password as default authentication plugin we do not support
+ // clients using caching_sha2_password , thus we define "unknown plugin" and force the
+ // client to switch to mysql_native_password
+ auth_plugin_id = AUTH_UNKNOWN_PLUGIN;
+ } else if (sent_auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD) {
+ auth_plugin_id = AUTH_MYSQL_CACHING_SHA2_PASSWORD;
+ } else {
+ assert(0);
+ }
}
}
- if (auth_plugin_id == 0) {
- if (strncmp((char *)auth_plugin,(char *)"mysql_clear_password",strlen((char *)"mysql_clear_password"))==0) {
- auth_plugin_id = 2;
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' , auth_plugin_id=%d\n", (*myds), (*myds)->sess, vars1.user, auth_plugin_id);
+}
+
+bool MySQL_Protocol::PPHR_4auth0(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1) {
+ if ((*myds)->switching_auth_stage == 0) {
+ (*myds)->switching_auth_stage = 1;
+ (*myds)->auth_in_progress = 1;
+ // check if user exists
+ bool user_exists = true;
+ if (GloMyLdapAuth) { // we check if user exists only if GloMyLdapAuth is enabled
+#ifdef PROXYSQLCLICKHOUSE
+ enum proxysql_session_type session_type = (*myds)->sess->session_type;
+ if (session_type == PROXYSQL_SESSION_CLICKHOUSE) {
+ //user_exists = GloClickHouseAuth->exists((char *)user);
+ // for clickhouse, we currently do not support clear text or LDAP
+ user_exists = true;
+ } else {
+#endif /* PROXYSQLCLICKHOUSE */
+ user_exists = GloMyAuth->exists((char *)vars1.user);
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user_exists=%d , user='%s'\n", (*myds), (*myds)->sess, user_exists, vars1.user);
+ //password=GloMyAuth->lookup((char *)user, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked, &transaction_persistent, &fast_forward, &max_connections, &sha1_pass);
+#ifdef PROXYSQLCLICKHOUSE
+ }
+#endif /* PROXYSQLCLICKHOUSE */
}
+ if (user_exists) {
+ (*myds)->switching_auth_type = AUTH_MYSQL_NATIVE_PASSWORD; // mysql_native_password
+ } else {
+ (*myds)->switching_auth_type = AUTH_MYSQL_CLEAR_PASSWORD; // mysql_clear_password
+ }
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user_exists=%d , user='%s' , setting switching_auth_type=%d\n", (*myds), (*myds)->sess, user_exists, vars1.user, (*myds)->switching_auth_type);
+ generate_pkt_auth_switch_request(true, NULL, NULL);
+ (*myds)->myconn->userinfo->set((char *)vars1.user, NULL, vars1.db, NULL);
+ ret = false;
+ return false;
}
-//__switch_auth_plugin:
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' , auth_plugin_id=%d\n", (*myds), (*myds)->sess, user, auth_plugin_id);
- if (auth_plugin_id == 0) {
+ return true;
+}
+
+
+bool MySQL_Protocol::PPHR_4auth1(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1) {
+ if (GloMyLdapAuth) {
if ((*myds)->switching_auth_stage == 0) {
- (*myds)->switching_auth_stage = 1;
- // check if user exists
bool user_exists = true;
- if (GloMyLdapAuth) { // we check if user exists only if GloMyLdapAuth is enabled
#ifdef PROXYSQLCLICKHOUSE
- if (session_type == PROXYSQL_SESSION_CLICKHOUSE) {
- //user_exists = GloClickHouseAuth->exists((char *)user);
- // for clickhouse, we currently do not support clear text or LDAP
- user_exists = true;
- } else {
+ enum proxysql_session_type session_type = (*myds)->sess->session_type;
+ if (session_type == PROXYSQL_SESSION_CLICKHOUSE) {
+ //user_exists = GloClickHouseAuth->exists((char *)user);
+ // for clickhouse, we currently do not support clear text or LDAP
+ user_exists = true;
+ } else {
#endif /* PROXYSQLCLICKHOUSE */
- user_exists = GloMyAuth->exists((char *)user);
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user_exists=%d , user='%s'\n", (*myds), (*myds)->sess, user_exists, user);
- //password=GloMyAuth->lookup((char *)user, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked, &transaction_persistent, &fast_forward, &max_connections, &sha1_pass);
+ user_exists = GloMyAuth->exists((char *)vars1.user);
+ //password=GloMyAuth->lookup((char *)user, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked, &transaction_persistent, &fast_forward, &max_connections, &sha1_pass);
#ifdef PROXYSQLCLICKHOUSE
- }
-#endif /* PROXYSQLCLICKHOUSE */
}
- if (user_exists) {
- (*myds)->switching_auth_type = 1; // mysql_native_password
- } else {
- (*myds)->switching_auth_type = 2; // mysql_clear_password
+#endif /* PROXYSQLCLICKHOUSE */
+ if (user_exists == false) {
+ (*myds)->switching_auth_type = AUTH_MYSQL_CLEAR_PASSWORD; // mysql_clear_password
+ (*myds)->switching_auth_stage = 1;
+ (*myds)->auth_in_progress = 1;
+ generate_pkt_auth_switch_request(true, NULL, NULL);
+ (*myds)->myconn->userinfo->set((char *)vars1.user, NULL, vars1.db, NULL);
+ ret = false;
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response. User does not exist\n", (*myds), (*myds)->sess, vars1.user);
+ return false;
}
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user_exists=%d , user='%s' , setting switching_auth_type=%d\n", (*myds), (*myds)->sess, user_exists, user, (*myds)->switching_auth_type);
- generate_pkt_auth_switch_request(true, NULL, NULL);
- (*myds)->myconn->userinfo->set((char *)user, NULL, db, NULL);
- ret = false;
- goto __exit_process_pkt_handshake_response;
+ }
+ }
+ return true;
+}
+
+void MySQL_Protocol::PPHR_5passwordTrue(
+ unsigned char *pkt, unsigned int len,
+ bool& ret,
+ MyProt_tmp_auth_vars& vars1,
+ char * reply,
+ MyProt_tmp_auth_attrs& attr1) {
+ //int& default_hostgroup, char *& default_schema, char *& attributes, bool& schema_locked, bool& transaction_persistent, bool& fast_forward, int& max_connections) {
+#ifdef DEBUG
+ char *tmp_pass=strdup(vars1.password);
+ int lpass = strlen(tmp_pass);
+ for (int i=2; isess, vars1.user, tmp_pass);
+ free(tmp_pass);
+#endif // debug
+ // Could be reached several times before auth completion; allocating attributes should be reset
+ (*myds)->sess->default_hostgroup = attr1.default_hostgroup;
+ if ((*myds)->sess->default_schema) {
+ free((*myds)->sess->default_schema);
+ (*myds)->sess->default_schema = nullptr;
+ }
+ (*myds)->sess->default_schema = attr1.default_schema; // just the pointer is passed
+ if ((*myds)->sess->user_attributes) {
+ free((*myds)->sess->user_attributes);
+ (*myds)->sess->user_attributes = nullptr;
+ }
+ (*myds)->sess->user_attributes = attr1.attributes; // just the pointer is passed
+#ifdef DEBUG
+ debug_spiffe_id(vars1.user,attr1.attributes, __LINE__, __func__);
+#endif
+ (*myds)->sess->schema_locked = attr1.schema_locked;
+ (*myds)->sess->transaction_persistent = attr1.transaction_persistent;
+ (*myds)->sess->session_fast_forward=false; // default
+ if ((*myds)->sess->session_type == PROXYSQL_SESSION_MYSQL) {
+ (*myds)->sess->session_fast_forward = attr1.fast_forward;
+ }
+ (*myds)->sess->user_max_connections = attr1.max_connections;
+}
+
+
+void MySQL_Protocol::PPHR_5passwordFalse_0(
+ // FIXME: does this work only for mysql_native_password ?
+ unsigned char *pkt, unsigned int len,
+ bool& ret,
+ MyProt_tmp_auth_vars& vars1,
+ char * reply,
+ MyProt_tmp_auth_attrs& attr1) {
+ if (strcmp((const char *)vars1.user,mysql_thread___monitor_username)==0) {
+ proxy_scramble(reply, (*myds)->myconn->scramble_buff, mysql_thread___monitor_password);
+ if (memcmp(reply, vars1.pass, SHA_DIGEST_LENGTH)==0) {
+ (*myds)->sess->default_hostgroup=STATS_HOSTGROUP;
+ (*myds)->sess->default_schema=strdup((char *)"main"); // just the pointer is passed
+ (*myds)->sess->schema_locked=false;
+ (*myds)->sess->transaction_persistent=false;
+ (*myds)->sess->session_fast_forward=false;
+ (*myds)->sess->user_max_connections=0;
+ vars1.password=l_strdup(mysql_thread___monitor_password);
+ ret=true;
}
} else {
- if (auth_plugin_id == 1) {
- if (GloMyLdapAuth) {
- if ((*myds)->switching_auth_stage == 0) {
- bool user_exists = true;
-#ifdef PROXYSQLCLICKHOUSE
- if (session_type == PROXYSQL_SESSION_CLICKHOUSE) {
- //user_exists = GloClickHouseAuth->exists((char *)user);
- // for clickhouse, we currently do not support clear text or LDAP
- user_exists = true;
+ ret=false;
+ }
+}
+
+void MySQL_Protocol::PPHR_5passwordFalse_auth2(
+ unsigned char *pkt, unsigned int len,
+ bool& ret,
+ MyProt_tmp_auth_vars& vars1,
+ char * reply,
+ MyProt_tmp_auth_attrs& attr1,
+ void *& sha1_pass) {
+ if (GloMyLdapAuth) {
+#ifdef DEBUG
+ {
+ char *tmp_pass=strdup((const char *)vars1.pass);
+ int lpass = strlen(tmp_pass);
+ for (int i=2; isess, vars1.user, tmp_pass);
+ free(tmp_pass);
+ }
+#endif // debug
+ char *backend_username = NULL;
+ (*myds)->sess->use_ldap_auth = true;
+ vars1.password = GloMyLdapAuth->lookup((char *) vars1.user, (char *) vars1.pass, USERNAME_FRONTEND,
+ &attr1._ret_use_ssl, &attr1.default_hostgroup, &attr1.default_schema, &attr1.schema_locked,
+ &attr1.transaction_persistent, &attr1.fast_forward, &attr1.max_connections, &sha1_pass, &attr1.attributes, &backend_username);
+ if (vars1.password) {
+#ifdef DEBUG
+ char *tmp_pass=strdup(vars1.password);
+ int lpass = strlen(tmp_pass);
+ for (int i=2; isess, backend_username, tmp_pass);
+ free(tmp_pass);
+#endif // debug
+ (*myds)->sess->default_hostgroup=attr1.default_hostgroup;
+ (*myds)->sess->default_schema=attr1.default_schema; // just the pointer is passed
+ (*myds)->sess->user_attributes = attr1.attributes; // just the pointer is passed, LDAP returns empty string
+#ifdef DEBUG
+ debug_spiffe_id(vars1.user,attr1.attributes, __LINE__, __func__);
+#endif
+ (*myds)->sess->schema_locked=attr1.schema_locked;
+ (*myds)->sess->transaction_persistent=attr1.transaction_persistent;
+ (*myds)->sess->session_fast_forward=attr1.fast_forward;
+ (*myds)->sess->user_max_connections=attr1.max_connections;
+ if (strcmp(vars1.password, (char *) vars1.pass) == 0) {
+ if (backend_username) {
+ free(vars1.password);
+ vars1.password=NULL;
+ vars1.password=GloMyAuth->lookup(backend_username, USERNAME_BACKEND, &attr1._ret_use_ssl, &attr1.default_hostgroup, &attr1.default_schema, &attr1.schema_locked, &attr1.transaction_persistent, &attr1.fast_forward, &attr1.max_connections, &sha1_pass, &attr1.attributes);
+ if (vars1.password) {
+ (*myds)->sess->default_hostgroup=attr1.default_hostgroup;
+ // Free the previously set 'default_schema' by 'GloMyLdapAuth'
+ if ((*myds)->sess->default_schema) {
+ free((*myds)->sess->default_schema);
+ }
+ (*myds)->sess->default_schema=attr1.default_schema; // just the pointer is passed
+ // Free the previously set 'user_attributes' by 'GloMyLdapAuth'
+ if ((*myds)->sess->user_attributes) {
+ free((*myds)->sess->user_attributes);
+ }
+ (*myds)->sess->user_attributes = attr1.attributes; // just the pointer is passed
+#ifdef DEBUG
+ proxy_info("Attributes for user %s: %s\n" , vars1.user, attr1.attributes);
+#endif
+ (*myds)->sess->schema_locked=attr1.schema_locked;
+ (*myds)->sess->transaction_persistent=attr1.transaction_persistent;
+ (*myds)->sess->session_fast_forward=attr1.fast_forward;
+ (*myds)->sess->user_max_connections=attr1.max_connections;
+ char *tmp_user=strdup((const char *)vars1.user);
+ userinfo->set(backend_username, NULL, NULL, NULL);
+ // 'MySQL_Connection_userinfo::set' duplicates the supplied information, 'free' is required.
+ free(backend_username);
+ if (sha1_pass==NULL) {
+ // currently proxysql doesn't know any sha1_pass for that specific user, let's set it!
+ GloMyAuth->set_SHA1((char *)userinfo->username, USERNAME_FRONTEND,reply);
+ }
+ if (userinfo->sha1_pass) free(userinfo->sha1_pass);
+ userinfo->sha1_pass=sha1_pass_hex(reply);
+ userinfo->fe_username=strdup((const char *)tmp_user);
+ free(tmp_user);
+ ret=true;
} else {
-#endif /* PROXYSQLCLICKHOUSE */
- user_exists = GloMyAuth->exists((char *)user);
- //password=GloMyAuth->lookup((char *)user, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked, &transaction_persistent, &fast_forward, &max_connections, &sha1_pass);
-#ifdef PROXYSQLCLICKHOUSE
- }
-#endif /* PROXYSQLCLICKHOUSE */
- if (user_exists == false) {
- (*myds)->switching_auth_type = 2; // mysql_clear_password
- (*myds)->switching_auth_stage = 1;
- generate_pkt_auth_switch_request(true, NULL, NULL);
- (*myds)->myconn->userinfo->set((char *)user, NULL, db, NULL);
- ret = false;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response. User does not exist\n", (*myds), (*myds)->sess, user);
- goto __exit_process_pkt_handshake_response;
+ proxy_error("Unable to load credentials for backend user %s , associated to LDAP user %s\n", backend_username, vars1.user);
}
+ } else {
+ proxy_error("Unable to find backend user associated to LDAP user '%s'\n", vars1.user);
+ ret=false;
}
}
}
}
- if (auth_plugin_id == 0) { // unknown plugin
+}
+
+void MySQL_Protocol::PPHR_6auth2(
+ bool& ret,
+ MyProt_tmp_auth_vars& vars1
+ ) {
+ enum proxysql_session_type session_type = (*myds)->sess->session_type;
+ if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE || session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
+ unsigned char a[SHA256_DIGEST_LENGTH];
+ unsigned char b[SHA256_DIGEST_LENGTH];
+ unsigned char c[SHA256_DIGEST_LENGTH+20];
+ unsigned char d[SHA256_DIGEST_LENGTH];
+ unsigned char e[SHA256_DIGEST_LENGTH];
+ SHA256((const unsigned char *)vars1.password, strlen(vars1.password), a);
+ SHA256(a, SHA256_DIGEST_LENGTH, b);
+ memcpy(c,b,SHA256_DIGEST_LENGTH);
+ memcpy(c+SHA256_DIGEST_LENGTH, (*myds)->myconn->scramble_buff, 20);
+ SHA256(c, SHA256_DIGEST_LENGTH+20, d);
+ for (int i=0; isess->session_type;
+ if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE || session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
+ ret=proxy_scramble_sha1((char *)vars1.pass,(*myds)->myconn->scramble_buff,vars1.password+1, reply);
+ if (ret) {
+ if (sha1_pass==NULL) {
+ // currently proxysql doesn't know any sha1_pass for that specific user, let's set it!
+ GloMyAuth->set_SHA1((char *)vars1.user, USERNAME_FRONTEND,reply);
+ }
+ if (userinfo->sha1_pass)
+ free(userinfo->sha1_pass);
+ userinfo->sha1_pass=sha1_pass_hex(reply);
+ }
+ }
+}
+
+
+void MySQL_Protocol::PPHR_7auth2(
+ unsigned char *pkt, unsigned int len,
+ bool& ret,
+ MyProt_tmp_auth_vars& vars1,
+ char * reply,
+ MyProt_tmp_auth_attrs& attr1,
+ void *& sha1_pass) {
+ enum proxysql_session_type session_type = (*myds)->sess->session_type;
+ if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE || session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , username='%s' , session_type=%d\n", (*myds), (*myds)->sess, vars1.user, session_type);
+/*
+ uint8_t hash_stage1[SHA_DIGEST_LENGTH];
+ uint8_t hash_stage2[SHA_DIGEST_LENGTH];
+ SHA_CTX sha1_context;
+ SHA1_Init(&sha1_context);
+ SHA1_Update(&sha1_context, vars1.pass, vars1.pass_len);
+ SHA1_Final(hash_stage1, &sha1_context);
+ SHA1_Init(&sha1_context);
+ SHA1_Update(&sha1_context,hash_stage1,SHA_DIGEST_LENGTH);
+ SHA1_Final(hash_stage2, &sha1_context);
+ char *double_hashed_password = sha1_pass_hex((char *)hash_stage2); // note that sha1_pass_hex() returns a new buffer
+*/
+ unsigned char md1_buf[SHA_DIGEST_LENGTH];
+ unsigned char md2_buf[SHA_DIGEST_LENGTH];
+ SHA1(vars1.pass, vars1.pass_len, md1_buf);
+ SHA1(md1_buf,SHA_DIGEST_LENGTH,md2_buf);
+ char *double_hashed_password = sha1_pass_hex((char *)md2_buf); // note that sha1_pass_hex() returns a new buffer
+
+ if (strcasecmp(double_hashed_password,vars1.password)==0) {
+ ret = true;
+ if (sha1_pass==NULL) {
+ // currently proxysql doesn't know any sha1_pass for that specific user, let's set it!
+ GloMyAuth->set_SHA1((char *)vars1.user, USERNAME_FRONTEND,md1_buf);
+ }
+ if (userinfo->sha1_pass)
+ free(userinfo->sha1_pass);
+ userinfo->sha1_pass=sha1_pass_hex((char *)md1_buf);
+ } else {
+ ret = false;
+ }
+ free(double_hashed_password);
+ }
+}
+
+void MySQL_Protocol::PPHR_sha2full(
+ bool& ret,
+ MyProt_tmp_auth_vars& vars1,
+ enum proxysql_auth_plugins passformat
+ ) {
+ if ((*myds)->switching_auth_stage == 0) {
+ const unsigned char perform_full_authentication = '\4';
+ generate_one_byte_pkt(perform_full_authentication);
+ // Required to be set; later used in 'PPHR_1' for setting current 'auth_plugin_id'. E.g:
+ // - mysql-default_authentication_plugin: 'caching_sha2_password'
+ // - Requested authentication: 'caching_sha2_password'
+ // - Stored password: 'mysql_native_password'
+ // A full auth is required; and the switching auth type will be used later in 'PPHR_1'.
+ (*myds)->switching_auth_type = auth_plugin_id;
+ (*myds)->switching_auth_stage = 4;
+ (*myds)->auth_in_progress = 1;
+ } else if ((*myds)->switching_auth_stage == 5) {
+ if (passformat == AUTH_MYSQL_NATIVE_PASSWORD) {
+ unsigned char md1_buf[SHA_DIGEST_LENGTH];
+ unsigned char md2_buf[SHA_DIGEST_LENGTH];
+ SHA1(vars1.pass, vars1.pass_len, md1_buf);
+ SHA1(md1_buf,SHA_DIGEST_LENGTH,md2_buf);
+ char *double_hashed_password = sha1_pass_hex((char *)md2_buf); // note that sha1_pass_hex() returns a new buffer
+ if (strcasecmp(double_hashed_password,vars1.password)==0) {
+ ret = true;
+ }
+ free(double_hashed_password);
+ } else if (passformat == AUTH_MYSQL_CACHING_SHA2_PASSWORD) {
+ assert(strlen(vars1.password) == 70);
+ string sp = string(vars1.password);
+ long rounds = stol(sp.substr(3,3));
+ string salt = sp.substr(7,20);
+ string sha256hash = sp.substr(27,43);
+ //char * sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen);
+ char buf[100];
+ salt = "$5$rounds=" + to_string(rounds*1000) + "$" + salt;
+ sha256_crypt_r((const char*)vars1.pass, salt.c_str(), buf, sizeof(buf));
+ string sbuf = string(buf);
+ std::size_t found = sbuf.find_last_of("$");
+ assert(found != string::npos);
+ sbuf = sbuf.substr(found+1);
+ if (strcmp(sbuf.c_str(),vars1.password+27)==0) {
+ ret = true;
+ }
+ } else {
+ assert(0);
+ }
+ if (ret == true) {
+ enum proxysql_session_type session_type = (*myds)->sess->session_type;
+ if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE || session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
+ // currently proxysql doesn't know the clear text password for that specific user, let's set it!
+ GloMyAuth->set_clear_text_password((char *)vars1.user, USERNAME_FRONTEND, (const char *)vars1.pass);
+ // Update 'vars1' password with 'clear text' one, so session can be later updated with it
+ if (vars1.password) { free(vars1.password); }
+ vars1.password = strdup(reinterpret_cast(vars1.pass));
+ }
+ }
+ } else {
+ assert(0);
+ }
+}
+
+void MySQL_Protocol::PPHR_SetConnAttrs(MyProt_tmp_auth_vars& vars1, MyProt_tmp_auth_attrs& attr1) {
+ MySQL_Connection *myconn = NULL;
+ myconn=sess->client_myds->myconn;
+ assert(myconn);
+ myconn->set_charset(vars1.charset, CONNECT_START);
+
+ std::stringstream ss;
+ ss << vars1.charset;
+
+ /* We are processing handshake from client. Client sends us a character set it will use in communication.
+ * we store this character set in the client's variables to use later in multiplexing with different backends
+ */
+ mysql_variables.client_set_value(sess, SQL_CHARACTER_SET_RESULTS, ss.str().c_str());
+ mysql_variables.client_set_value(sess, SQL_CHARACTER_SET_CLIENT, ss.str().c_str());
+ mysql_variables.client_set_value(sess, SQL_CHARACTER_SET_CONNECTION, ss.str().c_str());
+ mysql_variables.client_set_value(sess, SQL_COLLATION_CONNECTION, ss.str().c_str());
+
+ // enable compression
+ if (vars1.capabilities & CLIENT_COMPRESS) {
+ if (myconn->options.server_capabilities & CLIENT_COMPRESS) {
+ myconn->options.compression_min_length=50;
+ //myconn->set_status_compression(true); // don't enable this here. It needs to be enabled after the OK is sent
+ }
+ }
+ if (attr1._ret_use_ssl==true) {
+ (*myds)->sess->use_ssl = true;
+ }
+}
+
+/**
+ * @brief Process handshake response from the client, and it needs to be called until
+ * the authentication is completed (successfully or failed)
+ *
+ * @return:
+ * true: the authentication completed
+ * false: the authentication failed, or more data is needed
+ */
+bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned int len) {
+#ifdef DEBUG
+ if (dump_pkt) { __dump_pkt(__func__,pkt,len); }
+#endif
+ bool ret = false;
+ auth_plugin_id = AUTH_UNKNOWN_PLUGIN;
+
+ char reply[SHA_DIGEST_LENGTH+1] = { 0 };
+ enum proxysql_session_type session_type = (*myds)->sess->session_type;
+
+ void *sha1_pass=NULL;
+ MyProt_tmp_auth_vars vars1;
+ MyProt_tmp_auth_attrs attr1;
+ vars1._ptr = pkt;
+ mysql_hdr hdr;
+ bool bool_rc = false;
+ memcpy(&hdr,pkt,sizeof(mysql_hdr));
+ //Copy4B(&hdr,pkt);
+ pkt += sizeof(mysql_hdr);
+
+ // NOTE: 'mysqlsh' sends a 'COM_INIT_DB' as soon as the connection is openned
+ // before ProxySQL has sent 'Server Greeting' messsage. Because this packet is
+ // unexpected, we simple return 'false' and exit.
+ if (hdr.pkt_id == 0 && *pkt == 2) {
ret = false;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response . Unknown auth plugin\n", (*myds), (*myds)->sess, user);
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . Client is disconnecting\n", (*myds), (*myds)->sess, vars1.user);
goto __exit_process_pkt_handshake_response;
}
- //char reply[SHA_DIGEST_LENGTH+1];
- //reply[SHA_DIGEST_LENGTH]='\0';
- //int default_hostgroup=-1;
- //char *default_schema=NULL;
- //bool schema_locked;
- //bool transaction_persistent = true;
- //bool fast_forward = false;
- //int max_connections;
- //enum proxysql_session_type session_type = (*myds)->sess->session_type;
-__do_auth:
+ if ((*myds)->myconn->userinfo->username) { // authentication already started.
+ int rc = PPHR_1(pkt, len, ret, vars1);
+ if (rc == 1)
+ goto __exit_process_pkt_handshake_response;
+ if (rc == 2)
+ goto __do_auth;
+ assert(0);
+ }
+
+ bool_rc = PPHR_2(pkt, len, ret, vars1);
+ if (bool_rc == false)
+ goto __exit_process_pkt_handshake_response;
+
+
+ PPHR_3(vars1); // detect plugin id
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' , auth_plugin_id=%d\n", (*myds), (*myds)->sess, vars1.user, auth_plugin_id);
+
+
+ if (sent_auth_plugin_id == AUTH_MYSQL_NATIVE_PASSWORD) {
+ switch (auth_plugin_id) {
+ case AUTH_UNKNOWN_PLUGIN:
+ bool_rc = PPHR_4auth0(pkt, len, ret, vars1);
+ if (bool_rc == false) {
+ goto __exit_process_pkt_handshake_response;
+ } else {
+ }
+ break;
+ case AUTH_MYSQL_NATIVE_PASSWORD:
+ bool_rc = PPHR_4auth1(pkt, len, ret, vars1);
+ if (bool_rc == false) {
+ goto __exit_process_pkt_handshake_response;
+ } else {
+ }
+ break;
+ case AUTH_MYSQL_CLEAR_PASSWORD:
+ break;
+ case AUTH_MYSQL_CACHING_SHA2_PASSWORD:
+ // this should never happen.
+ // in PPHR_3 we set auth_plugin_id = AUTH_UNKNOWN_PLUGIN
+ // if sent_auth_plugin_id == AUTH_MYSQL_NATIVE_PASSWORD
+ assert(0);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ } else if (sent_auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD) {
+ switch (auth_plugin_id) {
+ case AUTH_UNKNOWN_PLUGIN:
+ case AUTH_MYSQL_NATIVE_PASSWORD:
+ // for now we always switch to mysql_native_password
+ // FIXME: verify if it is correct to call this here.
+ // maybe it should only be called for AUTH_UNKNOWN_PLUGIN and not for AUTH_MYSQL_NATIVE_PASSWORD
+ bool_rc = PPHR_4auth0(pkt, len, ret, vars1);
+ if (bool_rc == false) {
+ goto __exit_process_pkt_handshake_response;
+ } else {
+ }
+ break;
+ case AUTH_MYSQL_CLEAR_PASSWORD:
+ break;
+ case AUTH_MYSQL_CACHING_SHA2_PASSWORD:
+ if ((*myds)->auth_in_progress != 0) {
+ assert(0);
+ }
+ if ((*myds)->switching_auth_stage != 0) {
+ assert(0);
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ assert(0);
+ }
+__do_auth:
{
// reject connections from unknown charsets
- const MARIADB_CHARSET_INFO * c = proxysql_find_charset_nr(charset);
+ const MARIADB_CHARSET_INFO * c = proxysql_find_charset_nr(vars1.charset);
if (!c) {
- proxy_error("Client %s:%d is trying to use unknown charset %u. Disconnecting\n", (*myds)->addr.addr, (*myds)->addr.port, charset);
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . Client %s:%d is trying to use unknown charset %u. Disconnecting\n", (*myds), (*myds)->sess, user, (*myds)->addr.addr, (*myds)->addr.port, charset);
+ proxy_error("Client %s:%d is trying to use unknown charset %u. Disconnecting\n", (*myds)->addr.addr, (*myds)->addr.port, vars1.charset);
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . Client %s:%d is trying to use unknown charset %u. Disconnecting\n", (*myds), (*myds)->sess, vars1.user, (*myds)->addr.addr, (*myds)->addr.port, vars1.charset);
ret = false;
goto __exit_do_auth;
}
// set the default session charset
- (*myds)->sess->default_charset = charset;
+ (*myds)->sess->default_charset = vars1.charset;
}
if (session_type == PROXYSQL_SESSION_CLICKHOUSE) {
#ifdef PROXYSQLCLICKHOUSE
- password=GloClickHouseAuth->lookup((char *)user, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked, &transaction_persistent, &fast_forward, &max_connections, &sha1_pass);
+ vars1.password=GloClickHouseAuth->lookup((char *)vars1.user, USERNAME_FRONTEND, &attr1._ret_use_ssl, &attr1.default_hostgroup, &attr1.default_schema, &attr1.schema_locked, &attr1.transaction_persistent, &attr1.fast_forward, &attr1.max_connections, &sha1_pass);
#endif /* PROXYSQLCLICKHOUSE */
} else {
- password=GloMyAuth->lookup((char *)user, USERNAME_FRONTEND, &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked, &transaction_persistent, &fast_forward, &max_connections, &sha1_pass, &attributes);
+ vars1.password=GloMyAuth->lookup((char *)vars1.user, USERNAME_FRONTEND, &attr1._ret_use_ssl, &attr1.default_hostgroup, &attr1.default_schema, &attr1.schema_locked, &attr1.transaction_persistent, &attr1.fast_forward, &attr1.max_connections, &sha1_pass, &attr1.attributes);
}
//assert(default_hostgroup>=0);
- if (password) {
-#ifdef DEBUG
- char *tmp_pass=strdup(password);
- int lpass = strlen(tmp_pass);
- for (int i=2; isess, user, tmp_pass);
- free(tmp_pass);
-#endif // debug
- (*myds)->sess->default_hostgroup=default_hostgroup;
- (*myds)->sess->default_schema=default_schema; // just the pointer is passed
- (*myds)->sess->user_attributes = attributes; // just the pointer is passed
-#ifdef DEBUG
- debug_spiffe_id(user,attributes, __LINE__, __func__);
-#endif
- (*myds)->sess->schema_locked=schema_locked;
- (*myds)->sess->transaction_persistent=transaction_persistent;
- (*myds)->sess->session_fast_forward=false; // default
- if ((*myds)->sess->session_type == PROXYSQL_SESSION_MYSQL) {
- (*myds)->sess->session_fast_forward=fast_forward;
- }
- (*myds)->sess->user_max_connections=max_connections;
- }
- if (password == NULL) {
+ //if (vars1.password) {
+ //}
+ if (vars1.password == NULL) {
// this is a workaround for bug #603
if (
((*myds)->sess->session_type == PROXYSQL_SESSION_ADMIN)
@@ -1972,182 +2434,89 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned
((*myds)->sess->session_type == PROXYSQL_SESSION_SQLITE)
//#endif // TEST_AURORA || TEST_GALERA
) {
- if (strcmp((const char *)user,mysql_thread___monitor_username)==0) {
- proxy_scramble(reply, (*myds)->myconn->scramble_buff, mysql_thread___monitor_password);
- if (memcmp(reply, pass, SHA_DIGEST_LENGTH)==0) {
- (*myds)->sess->default_hostgroup=STATS_HOSTGROUP;
- (*myds)->sess->default_schema=strdup((char *)"main"); // just the pointer is passed
- (*myds)->sess->schema_locked=false;
- (*myds)->sess->transaction_persistent=false;
- (*myds)->sess->session_fast_forward=false;
- (*myds)->sess->user_max_connections=0;
- password=l_strdup(mysql_thread___monitor_password);
- ret=true;
- }
- } else {
- ret=false;
- }
+ PPHR_5passwordFalse_0(pkt, len, ret, vars1, reply, attr1);
} else {
ret=false; // by default, assume this will fail
// try LDAP
- if (auth_plugin_id==2) {
- if (GloMyLdapAuth) {
-#ifdef DEBUG
- {
- char *tmp_pass=strdup((const char *)pass);
- int lpass = strlen(tmp_pass);
- for (int i=2; isess, user, tmp_pass);
- free(tmp_pass);
- }
-#endif // debug
- char *backend_username = NULL;
- (*myds)->sess->use_ldap_auth = true;
- password = GloMyLdapAuth->lookup((char *) user, (char *) pass, USERNAME_FRONTEND,
- &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked,
- &transaction_persistent, &fast_forward, &max_connections, &sha1_pass, &attributes, &backend_username);
- if (password) {
-#ifdef DEBUG
- char *tmp_pass=strdup(password);
- int lpass = strlen(tmp_pass);
- for (int i=2; isess, backend_username, tmp_pass);
- free(tmp_pass);
-#endif // debug
- (*myds)->sess->default_hostgroup=default_hostgroup;
- (*myds)->sess->default_schema=default_schema; // just the pointer is passed
- (*myds)->sess->user_attributes = attributes; // just the pointer is passed, LDAP returns empty string
-#ifdef DEBUG
- debug_spiffe_id(user,attributes, __LINE__, __func__);
-#endif
- (*myds)->sess->schema_locked=schema_locked;
- (*myds)->sess->transaction_persistent=transaction_persistent;
- (*myds)->sess->session_fast_forward=fast_forward;
- (*myds)->sess->user_max_connections=max_connections;
- if (strcmp(password, (char *) pass) == 0) {
- if (backend_username) {
- free(password);
- password=NULL;
- password=GloMyAuth->lookup(backend_username, USERNAME_BACKEND, &_ret_use_ssl, &default_hostgroup, &default_schema, &schema_locked, &transaction_persistent, &fast_forward, &max_connections, &sha1_pass, &attributes);
- if (password) {
- (*myds)->sess->default_hostgroup=default_hostgroup;
- // Free the previously set 'default_schema' by 'GloMyLdapAuth'
- if ((*myds)->sess->default_schema) {
- free((*myds)->sess->default_schema);
- }
- (*myds)->sess->default_schema=default_schema; // just the pointer is passed
- // Free the previously set 'user_attributes' by 'GloMyLdapAuth'
- if ((*myds)->sess->user_attributes) {
- free((*myds)->sess->user_attributes);
- }
- (*myds)->sess->user_attributes = attributes; // just the pointer is passed
-#ifdef DEBUG
- proxy_info("Attributes for user %s: %s\n" , user, attributes);
-#endif
- (*myds)->sess->schema_locked=schema_locked;
- (*myds)->sess->transaction_persistent=transaction_persistent;
- (*myds)->sess->session_fast_forward=fast_forward;
- (*myds)->sess->user_max_connections=max_connections;
- char *tmp_user=strdup((const char *)user);
- userinfo->set(backend_username, NULL, NULL, NULL);
- // 'MySQL_Connection_userinfo::set' duplicates the supplied information, 'free' is required.
- free(backend_username);
- if (sha1_pass==NULL) {
- // currently proxysql doesn't know any sha1_pass for that specific user, let's set it!
- GloMyAuth->set_SHA1((char *)userinfo->username, USERNAME_FRONTEND,reply);
- }
- if (userinfo->sha1_pass) free(userinfo->sha1_pass);
- userinfo->sha1_pass=sha1_pass_hex(reply);
- userinfo->fe_username=strdup((const char *)tmp_user);
- free(tmp_user);
- ret=true;
- } else {
- proxy_error("Unable to load credentials for backend user %s , associated to LDAP user %s\n", backend_username, user);
- }
- } else {
- proxy_error("Unable to find backend user associated to LDAP user '%s'\n", user);
- ret=false;
- }
- }
- }
- }
+ if (auth_plugin_id == AUTH_MYSQL_CLEAR_PASSWORD) {
+ PPHR_5passwordFalse_auth2(pkt, len, ret, vars1, reply, attr1, sha1_pass);
}
}
} else {
- if (pass_len==0 && strlen(password)==0) {
+ PPHR_5passwordTrue(pkt, len, ret, vars1, reply, attr1);
+ if (vars1.pass_len==0 && strlen(vars1.password)==0) {
ret=true;
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , username='%s' , password=''\n", (*myds), (*myds)->sess, user);
- } else {
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , username='%s' , password=''\n", (*myds), (*myds)->sess, vars1.user);
+ }
+ // For empty passwords client expects either 'OK' or 'ERR'
+ else if (vars1.pass_len == 0 && strlen(vars1.password) != 0) {
+ ret=false;
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , username='%s' , password=''\n", (*myds), (*myds)->sess, vars1.user);
+ }
+ else {
#ifdef DEBUG
- char *tmp_pass=strdup(password);
+ char *tmp_pass=strdup(vars1.password);
int lpass = strlen(tmp_pass);
for (int i=2; isess, user, tmp_pass, auth_plugin_id);
+ proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , username='%s' , password='%s' , auth_plugin_id=%d\n", (*myds), (*myds)->sess, vars1.user, tmp_pass, auth_plugin_id);
free(tmp_pass);
#endif // debug
- if (password[0]!='*') { // clear text password
- if (auth_plugin_id == 1) { // mysql_native_password
- proxy_scramble(reply, (*myds)->myconn->scramble_buff, password);
- if (pass_len != 0 && memcmp(reply, pass, SHA_DIGEST_LENGTH)==0) {
+ if (
+ auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD
+ &&
+ strlen(vars1.password) == 70
+ &&
+ strncasecmp(vars1.password,"$A$0",4)==0
+ ) {
+ // we have a hashed caching_sha2_password
+ PPHR_sha2full(ret, vars1, AUTH_MYSQL_CACHING_SHA2_PASSWORD);
+ } else if (vars1.password[0]!='*') { // clear text password
+ if (auth_plugin_id == AUTH_MYSQL_NATIVE_PASSWORD) { // mysql_native_password
+ proxy_scramble(reply, (*myds)->myconn->scramble_buff, vars1.password);
+ if (vars1.pass_len != 0 && memcmp(reply, vars1.pass, SHA_DIGEST_LENGTH)==0) {
ret=true;
}
- } else { // mysql_clear_password
- if (strcmp(password, (char *) pass) == 0) {
+ } else if (auth_plugin_id == AUTH_MYSQL_CLEAR_PASSWORD) { // mysql_clear_password
+ if (strcmp(vars1.password, (char *) vars1.pass) == 0) {
ret = true;
}
- }
- } else {
- if (auth_plugin_id == 1) {
- if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE || session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
- ret=proxy_scramble_sha1((char *)pass,(*myds)->myconn->scramble_buff,password+1, reply);
- if (ret) {
- if (sha1_pass==NULL) {
- // currently proxysql doesn't know any sha1_pass for that specific user, let's set it!
- GloMyAuth->set_SHA1((char *)user, USERNAME_FRONTEND,reply);
- }
- if (userinfo->sha1_pass)
- free(userinfo->sha1_pass);
- userinfo->sha1_pass=sha1_pass_hex(reply);
- }
- }
- } else { // mysql_clear_password
- if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE || session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) {
-/*
- char sha1_2[SHA_DIGEST_LENGTH+1];
- sha1_2[SHA_DIGEST_LENGTH]='\0';
- proxy_compute_sha1_hash((unsigned char *)reply,(char *)pass,pass_len);
- proxy_compute_sha1_hash((unsigned char *)sha1_2,reply,strlen(reply));
- uint8 hash_stage2[SHA_DIGEST_LENGTH];
- unhex_pass(hash_stage2,sha1_2);
-*/
- proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , username='%s' , session_type=%d\n", (*myds), (*myds)->sess, user, session_type);
- unsigned char md1_buf[SHA_DIGEST_LENGTH];
- unsigned char md2_buf[SHA_DIGEST_LENGTH];
- SHA1(pass,pass_len,md1_buf);
- SHA1(md1_buf,SHA_DIGEST_LENGTH,md2_buf);
-
- char *double_hashed_password = sha1_pass_hex((char *)md2_buf); // note that sha1_pass_hex() returns a new buffer
-
- if (strcasecmp(double_hashed_password,password)==0) {
+ } else if (auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD) { // caching_sha2_password
+ // Checking 'switching_auth_stage' is required case due to a potential concurrent update
+ // of pass in 'GloMyAuth'. When the pass found is clear-text it's assumed that full-auth
+ // is never required and that we are in the first auth stage, because of this, the pass
+ // received by the client is assumed to be hashed (first auth data received). Yet, during
+ // during a 'full-auth' the pass stored in 'GloMyAuth' could have been updated either by
+ // user action or by another concurrent connection that called 'set_clear_text_pass' on
+ // completion. In this case, we would have received a 'clear-text' pass form 'GloMyAuth'
+ // but we since we would be in the final auth stage, the pass sent by client should also
+ // be 'clear-text' (encrypt-pass).
+ if ((*myds)->switching_auth_stage == 5) {
+ if (strcmp(vars1.password, reinterpret_cast(vars1.pass)) == 0) {
ret = true;
- if (sha1_pass==NULL) {
- // currently proxysql doesn't know any sha1_pass for that specific user, let's set it!
- GloMyAuth->set_SHA1((char *)user, USERNAME_FRONTEND,md1_buf);
+ }
+ } else {
+ PPHR_6auth2(ret, vars1);
+ if (ret == true) {
+ if ((*myds)->switching_auth_stage == 0) {
+ const unsigned char fast_auth_success = '\3';
+ generate_one_byte_pkt(fast_auth_success);
}
- if (userinfo->sha1_pass)
- free(userinfo->sha1_pass);
- userinfo->sha1_pass=sha1_pass_hex((char *)md1_buf);
- } else {
- ret = false;
}
- free(double_hashed_password);
}
+ } else {
+ assert(0);
+ }
+ } else { // password hashed with SHA1 , mysql_native_password format
+ if (auth_plugin_id == AUTH_MYSQL_NATIVE_PASSWORD) { // mysql_native_password
+ PPHR_7auth1(pkt, len, ret, vars1, reply, attr1, sha1_pass);
+ } else if (auth_plugin_id == AUTH_MYSQL_CLEAR_PASSWORD) { // mysql_clear_password
+ PPHR_7auth2(pkt, len, ret, vars1, reply, attr1, sha1_pass);
+ } else if (auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD) { // caching_sha2_password
+ PPHR_sha2full(ret, vars1, AUTH_MYSQL_NATIVE_PASSWORD);
+ } else {
+ assert(0);
}
}
}
@@ -2155,101 +2524,80 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned
__exit_do_auth:
- if (_ret_use_ssl==true) {
- (*myds)->sess->use_ssl = true;
- }
-// if (_ret_use_ssl==true) {
-// // if we reached here, use_ssl is false , but _ret_use_ssl is true
-// // it means that a client is required to use SSL , but it is not
-// ret=false;
-// }
-// }
#ifdef DEBUG
{
char *tmp_pass= NULL;
- if (password) {
- tmp_pass = strdup(password);
+ if (vars1.password) {
+ tmp_pass = strdup(vars1.password);
int lpass = strlen(tmp_pass);
for (int i=2; i, capabilities:%u char:%u, use_ssl:%s\n",
- (capabilities & CLIENT_SECURE_CONNECTION ? "new" : "old"), user, tmp_pass, db, (*myds)->myconn->options.max_allowed_pkt, capabilities, charset, ((*myds)->encrypted ? "yes" : "no"));
+ (vars1.capabilities & CLIENT_SECURE_CONNECTION ? "new" : "old"), vars1.user, tmp_pass, vars1.db, (*myds)->myconn->options.max_allowed_pkt, vars1.capabilities, vars1.charset, ((*myds)->encrypted ? "yes" : "no"));
free(tmp_pass);
}
#endif
assert(sess);
assert(sess->client_myds);
- myconn=sess->client_myds->myconn;
- assert(myconn);
- myconn->set_charset(charset, CONNECT_START);
- {
- std::stringstream ss;
- ss << charset;
- /* We are processing handshake from client. Client sends us a character set it will use in communication.
- * we store this character set in the client's variables to use later in multiplexing with different backends
- */
- mysql_variables.client_set_value(sess, SQL_CHARACTER_SET_RESULTS, ss.str().c_str());
- mysql_variables.client_set_value(sess, SQL_CHARACTER_SET_CLIENT, ss.str().c_str());
- mysql_variables.client_set_value(sess, SQL_CHARACTER_SET_CONNECTION, ss.str().c_str());
- mysql_variables.client_set_value(sess, SQL_COLLATION_CONNECTION, ss.str().c_str());
- }
- // enable compression
- if (capabilities & CLIENT_COMPRESS) {
- if (myconn->options.server_capabilities & CLIENT_COMPRESS) {
- myconn->options.compression_min_length=50;
- //myconn->set_status_compression(true); // don't enable this here. It needs to be enabled after the OK is sent
- }
- }
+ // set connection attributes (charsets, compression, encryption)
+ PPHR_SetConnAttrs(vars1, attr1);
+
#ifdef DEBUG
- if (dump_pkt) { __dump_pkt(__func__,_ptr,len); }
+ if (dump_pkt) { __dump_pkt(__func__,vars1._ptr,len); }
#endif
- if (use_ssl) {
+ if (vars1.use_ssl) {
ret=true;
goto __exit_process_pkt_handshake_response;
}
+ // Could be reached several times before auth completion; allocating attributes should be reset
if (ret==true) {
(*myds)->DSS=STATE_CLIENT_HANDSHAKE;
if (!userinfo->username) // if set already, ignore
- userinfo->username=strdup((const char *)user);
- userinfo->password=strdup((const char *)password);
- if (db) userinfo->set_schemaname(db,strlen(db));
+ userinfo->username=strdup((const char *)vars1.user);
+ if (userinfo->password) {
+ free(userinfo->password);
+ }
+ userinfo->password=strdup((const char *)vars1.password);
+ if (vars1.db) userinfo->set_schemaname(vars1.db,strlen(vars1.db));
} else {
// we always duplicate username and password, or crashes happen
if (!userinfo->username) // if set already, ignore
- userinfo->username=strdup((const char *)user);
- if (pass_len) userinfo->password=strdup((const char *)"");
+ userinfo->username=strdup((const char *)vars1.user);
+ if (vars1.pass_len) {
+ if (userinfo->password) { free(userinfo->password); }
+ userinfo->password=strdup((const char *)"");
+ };
}
userinfo->set(NULL,NULL,NULL,NULL); // just to call compute_hash()
__exit_process_pkt_handshake_response:
- free(pass);
- if (password) {
- free(password);
- password=NULL;
+ free(vars1.pass);
+ if (vars1.password) {
+ free(vars1.password);
+ vars1.password=NULL;
}
if (sha1_pass) {
free(sha1_pass);
sha1_pass=NULL;
}
- if (db_tmp) {
- free(db_tmp);
- db_tmp=NULL;
+ if (vars1.db_tmp) {
+ free(vars1.db_tmp);
+ vars1.db_tmp=NULL;
}
if (ret == true) {
- ret = verify_user_attributes(__LINE__, __func__, user);
+ ret = verify_user_attributes(__LINE__, __func__, vars1.user);
}
return ret;
}
-
bool MySQL_Protocol::verify_user_attributes(int calling_line, const char *calling_func, const unsigned char *user) {
bool ret = true;
if ((*myds)->sess->user_attributes) {
diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp
index c6097ebca4..ac9d00fc7a 100644
--- a/lib/MySQL_Session.cpp
+++ b/lib/MySQL_Session.cpp
@@ -1166,7 +1166,23 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) {
}
}
j["client"]["DSS"] = client_myds->DSS;
+ j["client"]["switching_auth_sent"] = client_myds->switching_auth_sent;
j["client"]["switching_auth_type"] = client_myds->switching_auth_type;
+ j["client"]["prot"]["sent_auth_plugin_id"] = client_myds->myprot.sent_auth_plugin_id;
+ j["client"]["prot"]["auth_plugin_id"] = client_myds->myprot.auth_plugin_id;
+ switch (client_myds->myprot.auth_plugin_id) {
+ case AUTH_MYSQL_NATIVE_PASSWORD:
+ j["client"]["prot"]["auth_plugin"] = "mysql_native_password";
+ break;
+ case AUTH_MYSQL_CLEAR_PASSWORD:
+ j["client"]["prot"]["auth_plugin"] = "mysql_clear_password";
+ break;
+ case AUTH_MYSQL_CACHING_SHA2_PASSWORD:
+ j["client"]["prot"]["auth_plugin"] = "caching_sha2_password";
+ break;
+ default:
+ break;
+ }
if (client_myds->myconn != NULL) { // only if myconn is defined
if (client_myds->myconn->userinfo != NULL) { // only if userinfo is defined
j["client"]["userinfo"]["username"] = ( client_myds->myconn->userinfo->username ? client_myds->myconn->userinfo->username : "" );
@@ -5504,31 +5520,29 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
bool handshake_err = true;
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION,8,"Session=%p , DS=%p , handshake_response=%d , switching_auth_stage=%d , is_encrypted=%d , client_encrypted=%d\n", this, client_myds, handshake_response_return, client_myds->switching_auth_stage, is_encrypted, client_myds->encrypted);
- if (
- (handshake_response_return == false) && (client_myds->switching_auth_stage == 1)
- ) {
- l_free(pkt->size,pkt->ptr);
- proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION,8,"Session=%p , DS=%p . Returning\n", this, client_myds);
- return;
- }
-
- if (
- (is_encrypted == false) && // the connection was encrypted
- (handshake_response_return == false) && // the authentication didn't complete
- (client_myds->encrypted == true) // client is asking for encryption
- ) {
- // use SSL
- proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION,8,"Session=%p , DS=%p . SSL_INIT\n", this, client_myds);
- client_myds->DSS=STATE_SSL_INIT;
- client_myds->rbio_ssl = BIO_new(BIO_s_mem());
- client_myds->wbio_ssl = BIO_new(BIO_s_mem());
- client_myds->ssl = GloVars.get_SSL_new();
- SSL_set_fd(client_myds->ssl, client_myds->fd);
- SSL_set_accept_state(client_myds->ssl);
- SSL_set_bio(client_myds->ssl, client_myds->rbio_ssl, client_myds->wbio_ssl);
- l_free(pkt->size,pkt->ptr);
- proxysql_keylog_attach_callback(GloVars.get_SSL_ctx());
- return;
+ if (handshake_response_return == false) {
+ if (client_myds->auth_in_progress != 0) {
+ l_free(pkt->size,pkt->ptr);
+ proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION,8,"Session=%p , DS=%p . Returning\n", this, client_myds);
+ return;
+ }
+ if (
+ (is_encrypted == false) && // the connection was encrypted
+ (client_myds->encrypted == true) // client is asking for encryption
+ ) {
+ // use SSL
+ proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION,8,"Session=%p , DS=%p . SSL_INIT\n", this, client_myds);
+ client_myds->DSS=STATE_SSL_INIT;
+ client_myds->rbio_ssl = BIO_new(BIO_s_mem());
+ client_myds->wbio_ssl = BIO_new(BIO_s_mem());
+ client_myds->ssl = GloVars.get_SSL_new();
+ SSL_set_fd(client_myds->ssl, client_myds->fd);
+ SSL_set_accept_state(client_myds->ssl);
+ SSL_set_bio(client_myds->ssl, client_myds->rbio_ssl, client_myds->wbio_ssl);
+ l_free(pkt->size,pkt->ptr);
+ proxysql_keylog_attach_callback(GloVars.get_SSL_ctx());
+ return;
+ }
}
if (
@@ -5616,8 +5630,9 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
client_authenticated=false;
*wrong_pass=true;
client_myds->setDSS_STATE_QUERY_SENT_NET();
- uint8_t _pid = 2;
- if (client_myds->switching_auth_stage) _pid+=2;
+ //uint8_t _pid = 2;
+ //if (client_myds->switching_auth_stage) _pid+=2;
+ uint8_t _pid = client_myds->pkt_sid; _pid++;
if (max_connections_reached==true) {
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p , Too many connections\n", this, client_myds);
client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,_pid,1040,(char *)"08004", (char *)"Too many connections", true);
@@ -5677,9 +5692,10 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
} else {
client_addr = strdup((char *)"");
}
- uint8_t _pid = 2;
- if (client_myds->switching_auth_stage) _pid+=2;
- if (is_encrypted) _pid++;
+ //uint8_t _pid = 2;
+ //if (client_myds->switching_auth_stage) _pid+=2;
+ //if (is_encrypted) _pid++;
+ uint8_t _pid = client_myds->pkt_sid; _pid++;
if (
(strcmp(client_addr,(char *)"127.0.0.1")==0)
||
@@ -5705,8 +5721,9 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
free(client_addr);
} else {
uint8_t _pid = 2;
- if (client_myds->switching_auth_stage) _pid+=2;
- if (is_encrypted) _pid++;
+ //if (client_myds->switching_auth_stage) _pid+=2;
+ //if (is_encrypted) _pid++;
+ _pid = client_myds->pkt_sid; _pid++;
// If this condition is met, it means that the
// 'STATE_SERVER_HANDSHAKE' being performed isn't from the start of a
// connection, but as a consequence of a 'COM_USER_CHANGE' which
@@ -5776,9 +5793,10 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(
}
if (client_myds->myconn->userinfo->username) {
char *_s=(char *)malloc(strlen(client_myds->myconn->userinfo->username)+100+strlen(client_addr));
- uint8_t _pid = 2;
- if (client_myds->switching_auth_stage) _pid+=2;
- if (is_encrypted) _pid++;
+ //uint8_t _pid = 2;
+ //if (client_myds->switching_auth_stage) _pid+=2;
+ //if (is_encrypted) _pid++;
+ uint8_t _pid = client_myds->pkt_sid; _pid++;
#ifdef DEBUG
if (client_myds->myconn->userinfo->password) {
char *tmp_pass=strdup(client_myds->myconn->userinfo->password);
diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp
index 2ae7d6cc94..84919d66d9 100644
--- a/lib/MySQL_Thread.cpp
+++ b/lib/MySQL_Thread.cpp
@@ -390,6 +390,7 @@ static char * mysql_thread_variables_names[]= {
(char *)"server_capabilities",
(char *)"server_version",
(char *)"keep_multiplexing_variables",
+ (char *)"default_authentication_plugin",
(char *)"kill_backend_connection_when_disconnect",
(char *)"client_session_track_gtid",
(char *)"sessions_sort",
@@ -1046,6 +1047,8 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() {
variables.ssl_p2s_crl=NULL;
variables.ssl_p2s_crlpath=NULL;
variables.keep_multiplexing_variables=strdup((char *)"tx_isolation,transaction_isolation,version");
+ variables.default_authentication_plugin=strdup((char *)"mysql_native_password");
+ variables.default_authentication_plugin_int = 0; // mysql_native_password
#ifdef DEBUG
variables.session_debug=true;
#endif /*debug */
@@ -1261,6 +1264,7 @@ char * MySQL_Threads_Handler::get_variable_string(char *name) {
if (!strcmp(name,"auditlog_filename")) return strdup(variables.auditlog_filename);
if (!strcmp(name,"interfaces")) return strdup(variables.interfaces);
if (!strcmp(name,"keep_multiplexing_variables")) return strdup(variables.keep_multiplexing_variables);
+ if (!strcmp(name,"default_authentication_plugin")) return strdup(variables.default_authentication_plugin);
// LCOV_EXCL_START
proxy_error("Not existing variable: %s\n", name); assert(0);
return NULL;
@@ -1389,6 +1393,7 @@ char * MySQL_Threads_Handler::get_variable(char *name) { // this is the public f
if (!strcasecmp(name,"eventslog_filename")) return strdup(variables.eventslog_filename);
if (!strcasecmp(name,"default_schema")) return strdup(variables.default_schema);
if (!strcasecmp(name,"keep_multiplexing_variables")) return strdup(variables.keep_multiplexing_variables);
+ if (!strcasecmp(name,"default_authentication_plugin")) return strdup(variables.default_authentication_plugin);
if (!strcasecmp(name,"interfaces")) return strdup(variables.interfaces);
if (!strcasecmp(name,"server_capabilities")) {
// FIXME : make it human readable
@@ -1728,6 +1733,25 @@ bool MySQL_Threads_Handler::set_variable(char *name, const char *value) { // thi
return true;
}
}
+ if (!strcasecmp(name,"default_authentication_plugin")) {
+ if (vallen) {
+ const char * valids[2] = { "mysql_native_password", "caching_sha2_password" };
+ for (long unsigned int i=0; i < sizeof(valids)/sizeof(char *) ; i++) {
+ if (strcmp(valids[i],value)==0) {
+ free(variables.default_authentication_plugin);
+ variables.default_authentication_plugin=strdup(value);
+ if (i==0) variables.default_authentication_plugin_int = 0;
+ if (i==1) variables.default_authentication_plugin_int = 2;
+ return true;
+ }
+ }
+ // not found
+ proxy_error("%s is an invalid value for default_authentication_plugin\n", value);
+ return false;
+ } else {
+ return false;
+ }
+ }
}
@@ -2509,6 +2533,7 @@ MySQL_Threads_Handler::~MySQL_Threads_Handler() {
if (variables.interfaces) free(variables.interfaces);
if (variables.server_version) free(variables.server_version);
if (variables.keep_multiplexing_variables) free(variables.keep_multiplexing_variables);
+ if (variables.default_authentication_plugin) free(variables.default_authentication_plugin);
if (variables.firewall_whitelist_errormsg) free(variables.firewall_whitelist_errormsg);
if (variables.init_connect) free(variables.init_connect);
if (variables.ldap_user_variable) free(variables.ldap_user_variable);
@@ -2639,6 +2664,7 @@ MySQL_Thread::~MySQL_Thread() {
if (mysql_thread___default_schema) { free(mysql_thread___default_schema); mysql_thread___default_schema=NULL; }
if (mysql_thread___server_version) { free(mysql_thread___server_version); mysql_thread___server_version=NULL; }
if (mysql_thread___keep_multiplexing_variables) { free(mysql_thread___keep_multiplexing_variables); mysql_thread___keep_multiplexing_variables=NULL; }
+ if (mysql_thread___default_authentication_plugin) { free(mysql_thread___default_authentication_plugin); mysql_thread___default_authentication_plugin=NULL; }
if (mysql_thread___firewall_whitelist_errormsg) { free(mysql_thread___firewall_whitelist_errormsg); mysql_thread___firewall_whitelist_errormsg=NULL; }
if (mysql_thread___init_connect) { free(mysql_thread___init_connect); mysql_thread___init_connect=NULL; }
if (mysql_thread___ldap_user_variable) { free(mysql_thread___ldap_user_variable); mysql_thread___ldap_user_variable=NULL; }
@@ -3968,6 +3994,9 @@ void MySQL_Thread::refresh_variables() {
mysql_thread___default_schema=GloMTH->get_variable_string((char *)"default_schema");
if (mysql_thread___keep_multiplexing_variables) free(mysql_thread___keep_multiplexing_variables);
mysql_thread___keep_multiplexing_variables=GloMTH->get_variable_string((char *)"keep_multiplexing_variables");
+ if (mysql_thread___default_authentication_plugin) free(mysql_thread___default_authentication_plugin);
+ mysql_thread___default_authentication_plugin=GloMTH->get_variable_string((char *)"default_authentication_plugin");
+ mysql_thread___default_authentication_plugin_int = GloMTH->variables.default_authentication_plugin_int;
mysql_thread___server_capabilities=GloMTH->get_variable_uint16((char *)"server_capabilities");
mysql_thread___handle_unknown_charset=GloMTH->get_variable_int((char *)"handle_unknown_charset");
mysql_thread___poll_timeout=GloMTH->get_variable_int((char *)"poll_timeout");
diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp
index d818d02c28..c008c35157 100644
--- a/lib/ProxySQL_Admin.cpp
+++ b/lib/ProxySQL_Admin.cpp
@@ -648,7 +648,7 @@ static char * admin_variables_names[]= {
(char *)"telnet_stats_ifaces",
(char *)"refresh_interval",
(char *)"read_only",
- (char *)"hash_passwords",
+// (char *)"hash_passwords",
(char *)"vacuum_stats",
(char *)"version",
(char *)"cluster_username",
@@ -2007,6 +2007,9 @@ bool admin_handler_command_set(char *query_no_space, unsigned int query_no_space
// Trim spaces from var name to allow writing like 'var = value'
char *var_name = trim_spaces_in_place(untrimmed_var_name);
+ if (strstr(var_name,(char *)"password") || strcmp(var_name,(char *)"mysql-default_authentication_plugin")==0) {
+ proxy_info("Received SET command for %s\n", var_name);
+ }
bool run_query = false;
// Check if the command tries to set a non-existing variable.
@@ -5974,7 +5977,7 @@ ProxySQL_Admin::ProxySQL_Admin() :
variables.telnet_stats_ifaces=NULL;
variables.refresh_interval=2000;
variables.mysql_show_processlist_extended = false;
- variables.hash_passwords=true; // issue #676
+// variables.hash_passwords=true; // issue #676
variables.vacuum_stats=true; // issue #1011
variables.admin_read_only=false; // by default, the admin interface accepts writes
variables.admin_version=(char *)PROXYSQL_VERSION;
@@ -6909,10 +6912,12 @@ void ProxySQL_Admin::init_sqliteserver_variables() {
}
void ProxySQL_Admin::init_ldap_variables() {
+/*
if (variables.hash_passwords==true) {
proxy_info("Impossible to set admin-hash_passwords=true when LDAP is enabled. Reverting to false\n");
variables.hash_passwords=false;
}
+*/
flush_ldap_variables___runtime_to_database(configdb, false, false, false);
flush_ldap_variables___runtime_to_database(admindb, false, true, false);
flush_ldap_variables___database_to_runtime(admindb,true);
@@ -8590,9 +8595,11 @@ char * ProxySQL_Admin::get_variable(char *name) {
if (!strcasecmp(name,"read_only")) {
return strdup((variables.admin_read_only ? "true" : "false"));
}
+/*
if (!strcasecmp(name,"hash_passwords")) {
return strdup((variables.hash_passwords ? "true" : "false"));
}
+*/
if (!strcasecmp(name,"vacuum_stats")) {
return strdup((variables.vacuum_stats ? "true" : "false"));
}
@@ -9061,6 +9068,8 @@ bool ProxySQL_Admin::set_variable(char *name, char *value, bool lock) { // this
}
}
if (!strcasecmp(name,"hash_passwords")) {
+ proxy_warning("Variable admin-hash_passwords is now deprecated and removed. See github issue #4218\n");
+/*
if (strcasecmp(value,"true")==0 || strcasecmp(value,"1")==0) {
variables.hash_passwords=true;
if (GloMyLdapAuth) {
@@ -9074,6 +9083,7 @@ bool ProxySQL_Admin::set_variable(char *name, char *value, bool lock) { // this
return true;
}
return false;
+*/
}
if (!strcasecmp(name,"vacuum_stats")) {
if (strcasecmp(value,"true")==0 || strcasecmp(value,"1")==0) {
@@ -11904,6 +11914,7 @@ SQLite3_result* ProxySQL_Admin::__add_active_users(
for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) {
SQLite3_row *r=*it;
char *password=NULL;
+/*
if (variables.hash_passwords) { // We must use hashed password. See issue #676
// Admin needs to hash the password
if (r->fields[1] && strlen(r->fields[1])) {
@@ -11921,12 +11932,13 @@ SQLite3_result* ProxySQL_Admin::__add_active_users(
password=strdup((char *)""); // we also generate a new string if hash_passwords is set
}
} else {
- if (r->fields[1]) {
- password=r->fields[1];
- } else {
- password=(char *)"";
- }
+*/
+ if (r->fields[1]) {
+ password=r->fields[1];
+ } else {
+ password=(char *)"";
}
+// }
std::vector usertypes {};
char* max_connections = nullptr;
@@ -11985,9 +11997,11 @@ SQLite3_result* ProxySQL_Admin::__add_active_users(
sqlite_result->add_row(&pta[0]);
}
+/*
if (variables.hash_passwords) {
free(password); // because we always generate a new string
}
+*/
}
if (__user == nullptr) {
diff --git a/lib/SHA-crypt.txt b/lib/SHA-crypt.txt
new file mode 100644
index 0000000000..f375e8000e
--- /dev/null
+++ b/lib/SHA-crypt.txt
@@ -0,0 +1,1870 @@
+Unix crypt using SHA-256 and SHA-512
+------------------------------------
+
+Author: Ulrich Drepper
+Version: 0.6 2016-8-31
+
+Only editorial changes since 0.4
+
+
+Discussion Group:
+
+Josh Bressers, Red Hat
+Mark Brown, IBM
+David Clissold, IBM
+Don Cragun, Sun
+Casper Dik, Sun
+Ulrich Drepper, Red Hat
+Larry Dwyer, HP
+Steve Grubb, Red Hat
+Ravi A Shankar, IBM
+Borislav Simov, HP
+
+
+Various Unix crypt implementations have been MD5 as an alternative
+method to the traditional DES encryption for the one-way conversion
+needed. Both DES and MD5 are deemed insecure for their primary
+purpose and by association their use in password encryption is put
+into question. In addition, the produced output for both DES and MD5
+has a short length which makes it possible to construct rainbow tables.
+
+Requests for a better solution to the problem have been heard for some
+time. Security departments in companies are trying to phase out all
+uses of MD5. They demand a method which is officially sanctioned.
+For US-based users this means tested by the NIST.
+
+This rules out the use of another already implemented method with
+limited spread: the use of the Blowfish encryption method. The choice
+comes down to tested encryption (3DES, AES) or hash sums (the SHA
+family).
+
+Encryption-based solution do not seem to provide better security (no
+proof to the contrary) and the higher CPU requirements can be
+compensated for by adding more complexity to a hash sum-based
+solution. This is why the decision has been made by a group of Unix
+and Linux vendors to persue this route.
+
+The SHA hash sum functions are well tested. By choosing the SHA-256
+and SHA-512 algorithms the produced output is 32 or 64 bytes
+respectively in size. This fulfills the requirement for a large
+output set which makes rainbow tables less useful to impossible, at
+least for the next years.
+
+The algorithm used by the MD5-based password hashing is generally
+deemed safe as well so there is no big problem with using a similar
+algorithm for the SHA-based password hashing solutions. Parts of the
+algorithm have been changed and in one instance what is thought to be
+a mistake in the MD5-based implementation has been fixed.
+
+The integration into existing systems is easy if those systems already
+support the MD5-based solution. Ever since the introduction of the
+MD5-based method an extended password format is in used:
+
+ $$$
+
+If the password is not of this form it is an old-style DES-encrypted
+password. If the password has this form the ID identifies the method
+used and this then determines how the rest of the password string is
+interpreted. So far the following ID values are in use:
+
+
+ ID | Method
+ -------------------------------
+ 1 | MD5 (Linux, BSD)
+ 2a | Blowfish (OpenBSD)
+ md5 | Sun MD5
+
+
+For the new SHA-256 and SHA-512 methods the following values are
+selected:
+
+
+ ID | Method
+ -------------------------------
+ 5 | SHA-256
+ 6 | SHA-512
+
+
+For the SHA-based methods the SALT string can be a simple string of
+which up to 16 characters are used. The MD5-based implementation used
+up to eight characters.. It was decided to allow one extension which
+follows an invention Sun implemented in their pluggable crypt
+implementation. If the SALT strings starts with
+
+ rounds=$
+
+where N is an unsigned decimal number the numeric value of N is used
+to modify the algorithm used. As will be explained later, the
+SHA-based algorithm contains a loop which can be run an arbitrary
+number of times. The more rounds are performed the higher the CPU
+requirements are. This is a safety mechanism which might help
+countering brute-force attacks in the face of increasing computing
+power.
+
+The default number of rounds for both algorithms is 5,000. To ensure
+minimal security and stability on the other hand minimum and maximum
+values for N are enforced:
+
+ minimum for N = 1,000
+ maximum for N = 999,999,999
+
+Any selection of N below the minimum will cause the use of 1,000
+rounds and a value of 1 billion and higher will cause 999,999,999
+rounds to be used. In these cases the output string produced by the
+crypt function will not have the same salt as the input salt string
+since the correct, limited rounds value is used in the output.
+
+The PWD part of the password string is the actual computed password.
+The size of this string is fixed:
+
+ SHA-256 43 characters
+ SHA-512 86 characters
+
+The output consists of the base64-encoded digest. The maximum length
+of a password string is therefore (excluding final NUL byte in the C
+representation):
+
+ SHA-256 80 characters
+ SHA-512 123 characters
+
+The input string used for the salt parameter of the crypt function can
+potentially be much longer. But since the salt string is truncated to
+at most 16 characters the size of the output string is limited.
+
+The algorithm used for the password hashing follows the one used in
+the Linux/BSD MD5 implementation. The following is a description of
+the algorithm where the differences are explicitly pointed out. Both,
+the SHA-256 and the SHA-512 method, use the same algorithm. The only
+difference, which is also a difference to the MD5 version, are all the
+cases where an existing digest is used as input for another digest
+computation. In this case the input size (i.e., the digest size)
+varies. For MD5 the digest is 16 bytes, for SHA-256 it is 32 bytes,
+and for SHA-512 it is 64 bytes. The following description will not
+mention this difference further.
+
+The algorithm using three primitives for creating a hash digest:
+
+- start a digest. This sets up the data structures and initial state
+ as required for the hash function
+
+- add bytes to a digest. This can happen multiple times. Only when
+ the required number of bytes for a round of the hash function is
+ added will anything happen. If the required number of bytes is not
+ yet reached the bytes will simply be queued up. For SHA-256 and
+ SHA-512 the respective sizes are 64 and 128 bytes.
+
+- finish the context. This operation causes the currently queued
+ bytes to be padded according to the hash function specification and
+ the result is processed. The final digest is computed and made
+ available to the use.
+
+When the algorithm talks about adding the salt string this really
+means adding the salt string truncated to 16 characters.
+
+When the algorithm talks about adding a string the terminating NUL
+byte of the C presentation of the string in NOT added.
+
+
+Algorithm for crypt using SHA-256/SHA-512:
+
+1. start digest A
+
+2. the password string is added to digest A
+
+3. the salt string is added to digest A. This is just the salt string
+ itself without the enclosing '$', without the magic prefix $5$ and
+ $6$ respectively and without the rounds= specification.
+
+ NB: the MD5 algorithm did add the $1$ prefix. This is not deemed
+ necessary since it is a constant string and does not add security
+ and /possibly/ allows a plain text attack. Since the rounds=
+ specification should never be added this would also create an
+ inconsistency.
+
+4. start digest B
+
+5. add the password to digest B
+
+6. add the salt string to digest B
+
+7. add the password again to digest B
+
+8. finish digest B
+
+9. For each block of 32 or 64 bytes in the password string (excluding
+ the terminating NUL in the C representation), add digest B to digest A
+
+10. For the remaining N bytes of the password string add the first
+ N bytes of digest B to digest A
+
+11. For each bit of the binary representation of the length of the
+ password string up to and including the highest 1-digit, starting
+ from to lowest bit position (numeric value 1):
+
+ a) for a 1-digit add digest B to digest A
+
+ b) for a 0-digit add the password string
+
+ NB: this step differs significantly from the MD5 algorithm. It
+ adds more randomness.
+
+12. finish digest A
+
+13. start digest DP
+
+14. for every byte in the password (excluding the terminating NUL byte
+ in the C representation of the string)
+
+ add the password to digest DP
+
+15. finish digest DP
+
+16. produce byte sequence P of the same length as the password where
+
+ a) for each block of 32 or 64 bytes of length of the password string
+ the entire digest DP is used
+
+ b) for the remaining N (up to 31 or 63) bytes use the first N
+ bytes of digest DP
+
+17. start digest DS
+
+18. repeat the following 16+A[0] times, where A[0] represents the first
+ byte in digest A interpreted as an 8-bit unsigned value
+
+ add the salt to digest DS
+
+19. finish digest DS
+
+20. produce byte sequence S of the same length as the salt string where
+
+ a) for each block of 32 or 64 bytes of length of the salt string
+ the entire digest DS is used
+
+ b) for the remaining N (up to 31 or 63) bytes use the first N
+ bytes of digest DS
+
+21. repeat a loop according to the number specified in the rounds=
+ specification in the salt (or the default value if none is
+ present). Each round is numbered, starting with 0 and up to N-1.
+
+ The loop uses a digest as input. In the first round it is the
+ digest produced in step 12. In the latter steps it is the digest
+ produced in step 21.h of the previous round. The following text
+ uses the notation "digest A/C" to describe this behavior.
+
+
+ a) start digest C
+
+ b) for odd round numbers add the byte sequense P to digest C
+
+ c) for even round numbers add digest A/C
+
+ d) for all round numbers not divisible by 3 add the byte sequence S
+
+ e) for all round numbers not divisible by 7 add the byte sequence P
+
+ f) for odd round numbers add digest A/C
+
+ g) for even round numbers add the byte sequence P
+
+ h) finish digest C.
+
+22. Produce the output string. This is an ASCII string of the maximum
+ size specified above, consisting of multiple pieces:
+
+ a) the salt prefix, $5$ or $6$ respectively
+
+ b) the rounds= specification, if one was present in the input
+ salt string. A trailing '$' is added in this case to separate
+ the rounds specification from the following text.
+
+ c) the salt string truncated to 16 characters
+
+ d) a '$' character
+
+ e) the base-64 encoded final C digest. The encoding used is as
+ follows:
+
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ ----------------------------------------------------------------
+ ./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
+
+ Each group of three bytes from the digest produces four
+ characters as output:
+
+ 1. character: the six low bits of the first byte
+ 2. character: the two high bits of the first byte and the
+ four low bytes from the second byte
+ 3. character: the four high bits from the second byte and
+ the two low bits from the third byte
+ 4. character: the six high bits from the third byte
+
+ The groups of three bytes are as follows (in this sequence).
+ These are the indices into the byte array containing the
+ digest, starting with index 0. For the last group there are
+ not enough bytes left in the digest and the value zero is used
+ in its place. This group also produces only three or two
+ characters as output for SHA-256 and SHA-512 respectively.
+
+ For SHA-256:
+
+ #3 #2 #1 <-- byte number in group
+
+ 0 - 10 - 20
+ 21 - 1 - 11
+ 12 - 22 - 2
+ 3 - 13 - 23
+ 24 - 4 - 14
+ 15 - 25 - 5
+ 6 - 16 - 26
+ 27 - 7 - 17
+ 18 - 28 - 8
+ 9 - 19 - 29
+ * - 31 - 30
+
+
+ For SHA-512:
+
+ #3 #2 #1 <-- byte number in group
+
+ 0 - 21 - 42
+ 22 - 43 - 1
+ 44 - 2 - 23
+ 3 - 24 - 45
+ 25 - 46 - 4
+ 47 - 5 - 26
+ 6 - 27 - 48
+ 28 - 49 - 7
+ 50 - 8 - 29
+ 9 - 30 - 51
+ 31 - 52 - 10
+ 53 - 11 - 32
+ 12 - 33 - 54
+ 34 - 55 - 13
+ 56 - 14 - 35
+ 15 - 36 - 57
+ 37 - 58 - 16
+ 59 - 17 - 38
+ 18 - 39 - 60
+ 40 - 61 - 19
+ 62 - 20 - 41
+ * - * - 63
+
+
+
+The following are complete implementation of the crypt variants using
+SHA-256 and SHA-512 respectively. The sources include a self test
+which can be enabled by defining the macro TEST.
+
+-------- sha256crypt.c ------------------------------------------------------
+/* SHA256-based Unix crypt implementation.
+ Released into the Public Domain by Ulrich Drepper . */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/* Structure to save state of computation between the single steps. */
+struct sha256_ctx
+{
+ uint32_t H[8];
+
+ uint32_t total[2];
+ uint32_t buflen;
+ char buffer[128]; /* NB: always correctly aligned for uint32_t. */
+};
+
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# define SWAP(n) \
+ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+ 64-byte boundary. (FIPS 180-2:5.1.1) */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
+
+
+/* Constants for SHA256 from FIPS 180-2:4.2.2. */
+static const uint32_t K[64] =
+ {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+ };
+
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+ It is assumed that LEN % 64 == 0. */
+static void
+sha256_process_block (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+ const uint32_t *words = buffer;
+ size_t nwords = len / sizeof (uint32_t);
+ uint32_t a = ctx->H[0];
+ uint32_t b = ctx->H[1];
+ uint32_t c = ctx->H[2];
+ uint32_t d = ctx->H[3];
+ uint32_t e = ctx->H[4];
+ uint32_t f = ctx->H[5];
+ uint32_t g = ctx->H[6];
+ uint32_t h = ctx->H[7];
+
+ /* First increment the byte count. FIPS 180-2 specifies the possible
+ length of the file up to 2^64 bits. Here we only compute the
+ number of bytes. Do a double word increment. */
+ ctx->total[0] += len;
+ if (ctx->total[0] < len)
+ ++ctx->total[1];
+
+ /* Process all bytes in the buffer with 64 bytes in each round of
+ the loop. */
+ while (nwords > 0)
+ {
+ uint32_t W[64];
+ uint32_t a_save = a;
+ uint32_t b_save = b;
+ uint32_t c_save = c;
+ uint32_t d_save = d;
+ uint32_t e_save = e;
+ uint32_t f_save = f;
+ uint32_t g_save = g;
+ uint32_t h_save = h;
+
+ /* Operators defined in FIPS 180-2:4.1.2. */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22))
+#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25))
+#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3))
+#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10))
+
+ /* It is unfortunate that C does not provide an operator for
+ cyclic rotation. Hope the C compiler is smart enough. */
+#define CYCLIC(w, s) ((w >> s) | (w << (32 - s)))
+
+ /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */
+ for (unsigned int t = 0; t < 16; ++t)
+ {
+ W[t] = SWAP (*words);
+ ++words;
+ }
+ for (unsigned int t = 16; t < 64; ++t)
+ W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
+
+ /* The actual computation according to FIPS 180-2:6.2.2 step 3. */
+ for (unsigned int t = 0; t < 64; ++t)
+ {
+ uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
+ uint32_t T2 = S0 (a) + Maj (a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + T1;
+ d = c;
+ c = b;
+ b = a;
+ a = T1 + T2;
+ }
+
+ /* Add the starting values of the context according to FIPS 180-2:6.2.2
+ step 4. */
+ a += a_save;
+ b += b_save;
+ c += c_save;
+ d += d_save;
+ e += e_save;
+ f += f_save;
+ g += g_save;
+ h += h_save;
+
+ /* Prepare for the next round. */
+ nwords -= 16;
+ }
+
+ /* Put checksum in context given as argument. */
+ ctx->H[0] = a;
+ ctx->H[1] = b;
+ ctx->H[2] = c;
+ ctx->H[3] = d;
+ ctx->H[4] = e;
+ ctx->H[5] = f;
+ ctx->H[6] = g;
+ ctx->H[7] = h;
+}
+
+
+/* Initialize structure containing state of computation.
+ (FIPS 180-2:5.3.2) */
+static void
+sha256_init_ctx (struct sha256_ctx *ctx)
+{
+ ctx->H[0] = 0x6a09e667;
+ ctx->H[1] = 0xbb67ae85;
+ ctx->H[2] = 0x3c6ef372;
+ ctx->H[3] = 0xa54ff53a;
+ ctx->H[4] = 0x510e527f;
+ ctx->H[5] = 0x9b05688c;
+ ctx->H[6] = 0x1f83d9ab;
+ ctx->H[7] = 0x5be0cd19;
+
+ ctx->total[0] = ctx->total[1] = 0;
+ ctx->buflen = 0;
+}
+
+
+/* Process the remaining bytes in the internal buffer and the usual
+ prolog according to the standard and write the result to RESBUF.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+static void *
+sha256_finish_ctx (struct sha256_ctx *ctx, void *resbuf)
+{
+ /* Take yet unprocessed bytes into account. */
+ uint32_t bytes = ctx->buflen;
+ size_t pad;
+
+ /* Now count remaining bytes. */
+ ctx->total[0] += bytes;
+ if (ctx->total[0] < bytes)
+ ++ctx->total[1];
+
+ pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+ memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+ /* Put the 64-bit file length in *bits* at the end of the buffer. */
+ *(uint32_t *) &ctx->buffer[bytes + pad + 4] = SWAP (ctx->total[0] << 3);
+ *(uint32_t *) &ctx->buffer[bytes + pad] = SWAP ((ctx->total[1] << 3) |
+ (ctx->total[0] >> 29));
+
+ /* Process last bytes. */
+ sha256_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+ /* Put result from CTX in first 32 bytes following RESBUF. */
+ for (unsigned int i = 0; i < 8; ++i)
+ ((uint32_t *) resbuf)[i] = SWAP (ctx->H[i]);
+
+ return resbuf;
+}
+
+
+static void
+sha256_process_bytes (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+ /* When we already have some bits in our internal buffer concatenate
+ both inputs first. */
+ if (ctx->buflen != 0)
+ {
+ size_t left_over = ctx->buflen;
+ size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+ memcpy (&ctx->buffer[left_over], buffer, add);
+ ctx->buflen += add;
+
+ if (ctx->buflen > 64)
+ {
+ sha256_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
+
+ ctx->buflen &= 63;
+ /* The regions in the following copy operation cannot overlap. */
+ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+ ctx->buflen);
+ }
+
+ buffer = (const char *) buffer + add;
+ len -= add;
+ }
+
+ /* Process available complete blocks. */
+ if (len >= 64)
+ {
+/* To check alignment gcc has an appropriate operator. Other
+ compilers don't. */
+#if __GNUC__ >= 2
+# define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__ (uint32_t) != 0)
+#else
+# define UNALIGNED_P(p) (((uintptr_t) p) % sizeof (uint32_t) != 0)
+#endif
+ if (UNALIGNED_P (buffer))
+ while (len > 64)
+ {
+ sha256_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx);
+ buffer = (const char *) buffer + 64;
+ len -= 64;
+ }
+ else
+ {
+ sha256_process_block (buffer, len & ~63, ctx);
+ buffer = (const char *) buffer + (len & ~63);
+ len &= 63;
+ }
+ }
+
+ /* Move remaining bytes into internal buffer. */
+ if (len > 0)
+ {
+ size_t left_over = ctx->buflen;
+
+ memcpy (&ctx->buffer[left_over], buffer, len);
+ left_over += len;
+ if (left_over >= 64)
+ {
+ sha256_process_block (ctx->buffer, 64, ctx);
+ left_over -= 64;
+ memcpy (ctx->buffer, &ctx->buffer[64], left_over);
+ }
+ ctx->buflen = left_over;
+ }
+}
+
+
+/* Define our magic string to mark salt for SHA256 "encryption"
+ replacement. */
+static const char sha256_salt_prefix[] = "$5$";
+
+/* Prefix for optional rounds specification. */
+static const char sha256_rounds_prefix[] = "rounds=";
+
+/* Maximum salt string length. */
+#define SALT_LEN_MAX 16
+/* Default number of rounds if not explicitly specified. */
+#define ROUNDS_DEFAULT 5000
+/* Minimum number of rounds. */
+#define ROUNDS_MIN 1000
+/* Maximum number of rounds. */
+#define ROUNDS_MAX 999999999
+
+/* Table with characters for base64 transformation. */
+static const char b64t[64] =
+"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+static char *
+sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen)
+{
+ unsigned char alt_result[32]
+ __attribute__ ((__aligned__ (__alignof__ (uint32_t))));
+ unsigned char temp_result[32]
+ __attribute__ ((__aligned__ (__alignof__ (uint32_t))));
+ struct sha256_ctx ctx;
+ struct sha256_ctx alt_ctx;
+ size_t salt_len;
+ size_t key_len;
+ size_t cnt;
+ char *cp;
+ char *copied_key = NULL;
+ char *copied_salt = NULL;
+ char *p_bytes;
+ char *s_bytes;
+ /* Default number of rounds. */
+ size_t rounds = ROUNDS_DEFAULT;
+ bool rounds_custom = false;
+
+ /* Find beginning of salt string. The prefix should normally always
+ be present. Just in case it is not. */
+ if (strncmp (sha256_salt_prefix, salt, sizeof (sha256_salt_prefix) - 1) == 0)
+ /* Skip salt prefix. */
+ salt += sizeof (sha256_salt_prefix) - 1;
+
+ if (strncmp (salt, sha256_rounds_prefix, sizeof (sha256_rounds_prefix) - 1)
+ == 0)
+ {
+ const char *num = salt + sizeof (sha256_rounds_prefix) - 1;
+ char *endp;
+ unsigned long int srounds = strtoul (num, &endp, 10);
+ if (*endp == '$')
+ {
+ salt = endp + 1;
+ rounds = MAX (ROUNDS_MIN, MIN (srounds, ROUNDS_MAX));
+ rounds_custom = true;
+ }
+ }
+
+ salt_len = MIN (strcspn (salt, "$"), SALT_LEN_MAX);
+ key_len = strlen (key);
+
+ if ((key - (char *) 0) % __alignof__ (uint32_t) != 0)
+ {
+ char *tmp = (char *) alloca (key_len + __alignof__ (uint32_t));
+ key = copied_key =
+ memcpy (tmp + __alignof__ (uint32_t)
+ - (tmp - (char *) 0) % __alignof__ (uint32_t),
+ key, key_len);
+ }
+
+ if ((salt - (char *) 0) % __alignof__ (uint32_t) != 0)
+ {
+ char *tmp = (char *) alloca (salt_len + __alignof__ (uint32_t));
+ salt = copied_salt =
+ memcpy (tmp + __alignof__ (uint32_t)
+ - (tmp - (char *) 0) % __alignof__ (uint32_t),
+ salt, salt_len);
+ }
+
+ /* Prepare for the real work. */
+ sha256_init_ctx (&ctx);
+
+ /* Add the key string. */
+ sha256_process_bytes (key, key_len, &ctx);
+
+ /* The last part is the salt string. This must be at most 16
+ characters and it ends at the first `$' character (for
+ compatibility with existing implementations). */
+ sha256_process_bytes (salt, salt_len, &ctx);
+
+
+ /* Compute alternate SHA256 sum with input KEY, SALT, and KEY. The
+ final result will be added to the first context. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* Add key. */
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Add salt. */
+ sha256_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Add key again. */
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Now get result of this (32 bytes) and add it to the other
+ context. */
+ sha256_finish_ctx (&alt_ctx, alt_result);
+
+ /* Add for any character in the key one byte of the alternate sum. */
+ for (cnt = key_len; cnt > 32; cnt -= 32)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ sha256_process_bytes (alt_result, cnt, &ctx);
+
+ /* Take the binary representation of the length of the key and for every
+ 1 add the alternate sum, for every 0 the key. */
+ for (cnt = key_len; cnt > 0; cnt >>= 1)
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ else
+ sha256_process_bytes (key, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha256_finish_ctx (&ctx, alt_result);
+
+ /* Start computation of P byte sequence. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < key_len; ++cnt)
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha256_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence P. */
+ cp = p_bytes = alloca (key_len);
+ for (cnt = key_len; cnt >= 32; cnt -= 32)
+ cp = mempcpy (cp, temp_result, 32);
+ memcpy (cp, temp_result, cnt);
+
+ /* Start computation of S byte sequence. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
+ sha256_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha256_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence S. */
+ cp = s_bytes = alloca (salt_len);
+ for (cnt = salt_len; cnt >= 32; cnt -= 32)
+ cp = mempcpy (cp, temp_result, 32);
+ memcpy (cp, temp_result, cnt);
+
+ /* Repeatedly run the collected hash value through SHA256 to burn
+ CPU cycles. */
+ for (cnt = 0; cnt < rounds; ++cnt)
+ {
+ /* New context. */
+ sha256_init_ctx (&ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+ else
+ sha256_process_bytes (alt_result, 32, &ctx);
+
+ /* Add salt for numbers not divisible by 3. */
+ if (cnt % 3 != 0)
+ sha256_process_bytes (s_bytes, salt_len, &ctx);
+
+ /* Add key for numbers not divisible by 7. */
+ if (cnt % 7 != 0)
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ else
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha256_finish_ctx (&ctx, alt_result);
+ }
+
+ /* Now we can construct the result string. It consists of three
+ parts. */
+ cp = stpncpy (buffer, sha256_salt_prefix, MAX (0, buflen));
+ buflen -= sizeof (sha256_salt_prefix) - 1;
+
+ if (rounds_custom)
+ {
+ int n = snprintf (cp, MAX (0, buflen), "%s%zu$",
+ sha256_rounds_prefix, rounds);
+ cp += n;
+ buflen -= n;
+ }
+
+ cp = stpncpy (cp, salt, MIN ((size_t) MAX (0, buflen), salt_len));
+ buflen -= MIN ((size_t) MAX (0, buflen), salt_len);
+
+ if (buflen > 0)
+ {
+ *cp++ = '$';
+ --buflen;
+ }
+
+#define b64_from_24bit(B2, B1, B0, N) \
+ do { \
+ unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+ int n = (N); \
+ while (n-- > 0 && buflen > 0) \
+ { \
+ *cp++ = b64t[w & 0x3f]; \
+ --buflen; \
+ w >>= 6; \
+ } \
+ } while (0)
+
+ b64_from_24bit (alt_result[0], alt_result[10], alt_result[20], 4);
+ b64_from_24bit (alt_result[21], alt_result[1], alt_result[11], 4);
+ b64_from_24bit (alt_result[12], alt_result[22], alt_result[2], 4);
+ b64_from_24bit (alt_result[3], alt_result[13], alt_result[23], 4);
+ b64_from_24bit (alt_result[24], alt_result[4], alt_result[14], 4);
+ b64_from_24bit (alt_result[15], alt_result[25], alt_result[5], 4);
+ b64_from_24bit (alt_result[6], alt_result[16], alt_result[26], 4);
+ b64_from_24bit (alt_result[27], alt_result[7], alt_result[17], 4);
+ b64_from_24bit (alt_result[18], alt_result[28], alt_result[8], 4);
+ b64_from_24bit (alt_result[9], alt_result[19], alt_result[29], 4);
+ b64_from_24bit (0, alt_result[31], alt_result[30], 3);
+ if (buflen <= 0)
+ {
+ errno = ERANGE;
+ buffer = NULL;
+ }
+ else
+ *cp = '\0'; /* Terminate the string. */
+
+ /* Clear the buffer for the intermediate result so that people
+ attaching to processes or reading core dumps cannot get any
+ information. We do it in this way to clear correct_words[]
+ inside the SHA256 implementation as well. */
+ sha256_init_ctx (&ctx);
+ sha256_finish_ctx (&ctx, alt_result);
+ memset (temp_result, '\0', sizeof (temp_result));
+ memset (p_bytes, '\0', key_len);
+ memset (s_bytes, '\0', salt_len);
+ memset (&ctx, '\0', sizeof (ctx));
+ memset (&alt_ctx, '\0', sizeof (alt_ctx));
+ if (copied_key != NULL)
+ memset (copied_key, '\0', key_len);
+ if (copied_salt != NULL)
+ memset (copied_salt, '\0', salt_len);
+
+ return buffer;
+}
+
+
+/* This entry point is equivalent to the `crypt' function in Unix
+ libcs. */
+char *
+sha256_crypt (const char *key, const char *salt)
+{
+ /* We don't want to have an arbitrary limit in the size of the
+ password. We can compute an upper bound for the size of the
+ result in advance and so we can prepare the buffer we pass to
+ `sha256_crypt_r'. */
+ static char *buffer;
+ static int buflen;
+ int needed = (sizeof (sha256_salt_prefix) - 1
+ + sizeof (sha256_rounds_prefix) + 9 + 1
+ + strlen (salt) + 1 + 43 + 1);
+
+ if (buflen < needed)
+ {
+ char *new_buffer = (char *) realloc (buffer, needed);
+ if (new_buffer == NULL)
+ return NULL;
+
+ buffer = new_buffer;
+ buflen = needed;
+ }
+
+ return sha256_crypt_r (key, salt, buffer, buflen);
+}
+
+
+#ifdef TEST
+static const struct
+{
+ const char *input;
+ const char result[32];
+} tests[] =
+ {
+ /* Test vectors from FIPS 180-2: appendix B.1. */
+ { "abc",
+ "\xba\x78\x16\xbf\x8f\x01\xcf\xea\x41\x41\x40\xde\x5d\xae\x22\x23"
+ "\xb0\x03\x61\xa3\x96\x17\x7a\x9c\xb4\x10\xff\x61\xf2\x00\x15\xad" },
+ /* Test vectors from FIPS 180-2: appendix B.2. */
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "\x24\x8d\x6a\x61\xd2\x06\x38\xb8\xe5\xc0\x26\x93\x0c\x3e\x60\x39"
+ "\xa3\x3c\xe4\x59\x64\xff\x21\x67\xf6\xec\xed\xd4\x19\xdb\x06\xc1" },
+ /* Test vectors from the NESSIE project. */
+ { "",
+ "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24"
+ "\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55" },
+ { "a",
+ "\xca\x97\x81\x12\xca\x1b\xbd\xca\xfa\xc2\x31\xb3\x9a\x23\xdc\x4d"
+ "\xa7\x86\xef\xf8\x14\x7c\x4e\x72\xb9\x80\x77\x85\xaf\xee\x48\xbb" },
+ { "message digest",
+ "\xf7\x84\x6f\x55\xcf\x23\xe1\x4e\xeb\xea\xb5\xb4\xe1\x55\x0c\xad"
+ "\x5b\x50\x9e\x33\x48\xfb\xc4\xef\xa3\xa1\x41\x3d\x39\x3c\xb6\x50" },
+ { "abcdefghijklmnopqrstuvwxyz",
+ "\x71\xc4\x80\xdf\x93\xd6\xae\x2f\x1e\xfa\xd1\x44\x7c\x66\xc9\x52"
+ "\x5e\x31\x62\x18\xcf\x51\xfc\x8d\x9e\xd8\x32\xf2\xda\xf1\x8b\x73" },
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "\x24\x8d\x6a\x61\xd2\x06\x38\xb8\xe5\xc0\x26\x93\x0c\x3e\x60\x39"
+ "\xa3\x3c\xe4\x59\x64\xff\x21\x67\xf6\xec\xed\xd4\x19\xdb\x06\xc1" },
+ { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "\xdb\x4b\xfc\xbd\x4d\xa0\xcd\x85\xa6\x0c\x3c\x37\xd3\xfb\xd8\x80"
+ "\x5c\x77\xf1\x5f\xc6\xb1\xfd\xfe\x61\x4e\xe0\xa7\xc8\xfd\xb4\xc0" },
+ { "123456789012345678901234567890123456789012345678901234567890"
+ "12345678901234567890",
+ "\xf3\x71\xbc\x4a\x31\x1f\x2b\x00\x9e\xef\x95\x2d\xd8\x3c\xa8\x0e"
+ "\x2b\x60\x02\x6c\x8e\x93\x55\x92\xd0\xf9\xc3\x08\x45\x3c\x81\x3e" }
+ };
+#define ntests (sizeof (tests) / sizeof (tests[0]))
+
+
+static const struct
+{
+ const char *salt;
+ const char *input;
+ const char *expected;
+} tests2[] =
+{
+ { "$5$saltstring", "Hello world!",
+ "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" },
+ { "$5$rounds=10000$saltstringsaltstring", "Hello world!",
+ "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2."
+ "opqey6IcA" },
+ { "$5$rounds=5000$toolongsaltstring", "This is just a test",
+ "$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8"
+ "mGRcvxa5" },
+ { "$5$rounds=1400$anotherlongsaltstring",
+ "a very much longer text to encrypt. This one even stretches over more"
+ "than one line.",
+ "$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12"
+ "oP84Bnq1" },
+ { "$5$rounds=77777$short",
+ "we have a short salt string but not a short password",
+ "$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" },
+ { "$5$rounds=123456$asaltof16chars..", "a short string",
+ "$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/"
+ "cZKmF/wJvD" },
+ { "$5$rounds=10$roundstoolow", "the minimum number is still observed",
+ "$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL97"
+ "2bIC" },
+};
+#define ntests2 (sizeof (tests2) / sizeof (tests2[0]))
+
+
+int
+main (void)
+{
+ struct sha256_ctx ctx;
+ char sum[32];
+ int result = 0;
+ int cnt;
+
+ for (cnt = 0; cnt < (int) ntests; ++cnt)
+ {
+ sha256_init_ctx (&ctx);
+ sha256_process_bytes (tests[cnt].input, strlen (tests[cnt].input), &ctx);
+ sha256_finish_ctx (&ctx, sum);
+ if (memcmp (tests[cnt].result, sum, 32) != 0)
+ {
+ printf ("test %d run %d failed\n", cnt, 1);
+ result = 1;
+ }
+
+ sha256_init_ctx (&ctx);
+ for (int i = 0; tests[cnt].input[i] != '\0'; ++i)
+ sha256_process_bytes (&tests[cnt].input[i], 1, &ctx);
+ sha256_finish_ctx (&ctx, sum);
+ if (memcmp (tests[cnt].result, sum, 32) != 0)
+ {
+ printf ("test %d run %d failed\n", cnt, 2);
+ result = 1;
+ }
+ }
+
+ /* Test vector from FIPS 180-2: appendix B.3. */
+ char buf[1000];
+ memset (buf, 'a', sizeof (buf));
+ sha256_init_ctx (&ctx);
+ for (int i = 0; i < 1000; ++i)
+ sha256_process_bytes (buf, sizeof (buf), &ctx);
+ sha256_finish_ctx (&ctx, sum);
+ static const char expected[32] =
+ "\xcd\xc7\x6e\x5c\x99\x14\xfb\x92\x81\xa1\xc7\xe2\x84\xd7\x3e\x67"
+ "\xf1\x80\x9a\x48\xa4\x97\x20\x0e\x04\x6d\x39\xcc\xc7\x11\x2c\xd0";
+ if (memcmp (expected, sum, 32) != 0)
+ {
+ printf ("test %d failed\n", cnt);
+ result = 1;
+ }
+
+ for (cnt = 0; cnt < ntests2; ++cnt)
+ {
+ char *cp = sha256_crypt (tests2[cnt].input, tests2[cnt].salt);
+
+ if (strcmp (cp, tests2[cnt].expected) != 0)
+ {
+ printf ("test %d: expected \"%s\", got \"%s\"\n",
+ cnt, tests2[cnt].expected, cp);
+ result = 1;
+ }
+ }
+
+ if (result == 0)
+ puts ("all tests OK");
+
+ return result;
+}
+#endif
+-----------------------------------------------------------------------------
+
+-------- sha512crypt.c ------------------------------------------------------
+/* SHA512-based Unix crypt implementation.
+ Released into the Public Domain by Ulrich Drepper . */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/* Structure to save state of computation between the single steps. */
+struct sha512_ctx
+{
+ uint64_t H[8];
+
+ uint64_t total[2];
+ uint64_t buflen;
+ char buffer[256]; /* NB: always correctly aligned for uint64_t. */
+};
+
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# define SWAP(n) \
+ (((n) << 56) \
+ | (((n) & 0xff00) << 40) \
+ | (((n) & 0xff0000) << 24) \
+ | (((n) & 0xff000000) << 8) \
+ | (((n) >> 8) & 0xff000000) \
+ | (((n) >> 24) & 0xff0000) \
+ | (((n) >> 40) & 0xff00) \
+ | ((n) >> 56))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+ 64-byte boundary. (FIPS 180-2:5.1.2) */
+static const unsigned char fillbuf[128] = { 0x80, 0 /* , 0, 0, ... */ };
+
+
+/* Constants for SHA512 from FIPS 180-2:4.2.3. */
+static const uint64_t K[80] =
+ {
+ UINT64_C (0x428a2f98d728ae22), UINT64_C (0x7137449123ef65cd),
+ UINT64_C (0xb5c0fbcfec4d3b2f), UINT64_C (0xe9b5dba58189dbbc),
+ UINT64_C (0x3956c25bf348b538), UINT64_C (0x59f111f1b605d019),
+ UINT64_C (0x923f82a4af194f9b), UINT64_C (0xab1c5ed5da6d8118),
+ UINT64_C (0xd807aa98a3030242), UINT64_C (0x12835b0145706fbe),
+ UINT64_C (0x243185be4ee4b28c), UINT64_C (0x550c7dc3d5ffb4e2),
+ UINT64_C (0x72be5d74f27b896f), UINT64_C (0x80deb1fe3b1696b1),
+ UINT64_C (0x9bdc06a725c71235), UINT64_C (0xc19bf174cf692694),
+ UINT64_C (0xe49b69c19ef14ad2), UINT64_C (0xefbe4786384f25e3),
+ UINT64_C (0x0fc19dc68b8cd5b5), UINT64_C (0x240ca1cc77ac9c65),
+ UINT64_C (0x2de92c6f592b0275), UINT64_C (0x4a7484aa6ea6e483),
+ UINT64_C (0x5cb0a9dcbd41fbd4), UINT64_C (0x76f988da831153b5),
+ UINT64_C (0x983e5152ee66dfab), UINT64_C (0xa831c66d2db43210),
+ UINT64_C (0xb00327c898fb213f), UINT64_C (0xbf597fc7beef0ee4),
+ UINT64_C (0xc6e00bf33da88fc2), UINT64_C (0xd5a79147930aa725),
+ UINT64_C (0x06ca6351e003826f), UINT64_C (0x142929670a0e6e70),
+ UINT64_C (0x27b70a8546d22ffc), UINT64_C (0x2e1b21385c26c926),
+ UINT64_C (0x4d2c6dfc5ac42aed), UINT64_C (0x53380d139d95b3df),
+ UINT64_C (0x650a73548baf63de), UINT64_C (0x766a0abb3c77b2a8),
+ UINT64_C (0x81c2c92e47edaee6), UINT64_C (0x92722c851482353b),
+ UINT64_C (0xa2bfe8a14cf10364), UINT64_C (0xa81a664bbc423001),
+ UINT64_C (0xc24b8b70d0f89791), UINT64_C (0xc76c51a30654be30),
+ UINT64_C (0xd192e819d6ef5218), UINT64_C (0xd69906245565a910),
+ UINT64_C (0xf40e35855771202a), UINT64_C (0x106aa07032bbd1b8),
+ UINT64_C (0x19a4c116b8d2d0c8), UINT64_C (0x1e376c085141ab53),
+ UINT64_C (0x2748774cdf8eeb99), UINT64_C (0x34b0bcb5e19b48a8),
+ UINT64_C (0x391c0cb3c5c95a63), UINT64_C (0x4ed8aa4ae3418acb),
+ UINT64_C (0x5b9cca4f7763e373), UINT64_C (0x682e6ff3d6b2b8a3),
+ UINT64_C (0x748f82ee5defb2fc), UINT64_C (0x78a5636f43172f60),
+ UINT64_C (0x84c87814a1f0ab72), UINT64_C (0x8cc702081a6439ec),
+ UINT64_C (0x90befffa23631e28), UINT64_C (0xa4506cebde82bde9),
+ UINT64_C (0xbef9a3f7b2c67915), UINT64_C (0xc67178f2e372532b),
+ UINT64_C (0xca273eceea26619c), UINT64_C (0xd186b8c721c0c207),
+ UINT64_C (0xeada7dd6cde0eb1e), UINT64_C (0xf57d4f7fee6ed178),
+ UINT64_C (0x06f067aa72176fba), UINT64_C (0x0a637dc5a2c898a6),
+ UINT64_C (0x113f9804bef90dae), UINT64_C (0x1b710b35131c471b),
+ UINT64_C (0x28db77f523047d84), UINT64_C (0x32caab7b40c72493),
+ UINT64_C (0x3c9ebe0a15c9bebc), UINT64_C (0x431d67c49c100d4c),
+ UINT64_C (0x4cc5d4becb3e42b6), UINT64_C (0x597f299cfc657e2a),
+ UINT64_C (0x5fcb6fab3ad6faec), UINT64_C (0x6c44198c4a475817)
+ };
+
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+ It is assumed that LEN % 128 == 0. */
+static void
+sha512_process_block (const void *buffer, size_t len, struct sha512_ctx *ctx)
+{
+ const uint64_t *words = buffer;
+ size_t nwords = len / sizeof (uint64_t);
+ uint64_t a = ctx->H[0];
+ uint64_t b = ctx->H[1];
+ uint64_t c = ctx->H[2];
+ uint64_t d = ctx->H[3];
+ uint64_t e = ctx->H[4];
+ uint64_t f = ctx->H[5];
+ uint64_t g = ctx->H[6];
+ uint64_t h = ctx->H[7];
+
+ /* First increment the byte count. FIPS 180-2 specifies the possible
+ length of the file up to 2^128 bits. Here we only compute the
+ number of bytes. Do a double word increment. */
+ ctx->total[0] += len;
+ if (ctx->total[0] < len)
+ ++ctx->total[1];
+
+ /* Process all bytes in the buffer with 128 bytes in each round of
+ the loop. */
+ while (nwords > 0)
+ {
+ uint64_t W[80];
+ uint64_t a_save = a;
+ uint64_t b_save = b;
+ uint64_t c_save = c;
+ uint64_t d_save = d;
+ uint64_t e_save = e;
+ uint64_t f_save = f;
+ uint64_t g_save = g;
+ uint64_t h_save = h;
+
+ /* Operators defined in FIPS 180-2:4.1.2. */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (CYCLIC (x, 28) ^ CYCLIC (x, 34) ^ CYCLIC (x, 39))
+#define S1(x) (CYCLIC (x, 14) ^ CYCLIC (x, 18) ^ CYCLIC (x, 41))
+#define R0(x) (CYCLIC (x, 1) ^ CYCLIC (x, 8) ^ (x >> 7))
+#define R1(x) (CYCLIC (x, 19) ^ CYCLIC (x, 61) ^ (x >> 6))
+
+ /* It is unfortunate that C does not provide an operator for
+ cyclic rotation. Hope the C compiler is smart enough. */
+#define CYCLIC(w, s) ((w >> s) | (w << (64 - s)))
+
+ /* Compute the message schedule according to FIPS 180-2:6.3.2 step 2. */
+ for (unsigned int t = 0; t < 16; ++t)
+ {
+ W[t] = SWAP (*words);
+ ++words;
+ }
+ for (unsigned int t = 16; t < 80; ++t)
+ W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
+
+ /* The actual computation according to FIPS 180-2:6.3.2 step 3. */
+ for (unsigned int t = 0; t < 80; ++t)
+ {
+ uint64_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
+ uint64_t T2 = S0 (a) + Maj (a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + T1;
+ d = c;
+ c = b;
+ b = a;
+ a = T1 + T2;
+ }
+
+ /* Add the starting values of the context according to FIPS 180-2:6.3.2
+ step 4. */
+ a += a_save;
+ b += b_save;
+ c += c_save;
+ d += d_save;
+ e += e_save;
+ f += f_save;
+ g += g_save;
+ h += h_save;
+
+ /* Prepare for the next round. */
+ nwords -= 16;
+ }
+
+ /* Put checksum in context given as argument. */
+ ctx->H[0] = a;
+ ctx->H[1] = b;
+ ctx->H[2] = c;
+ ctx->H[3] = d;
+ ctx->H[4] = e;
+ ctx->H[5] = f;
+ ctx->H[6] = g;
+ ctx->H[7] = h;
+}
+
+
+/* Initialize structure containing state of computation.
+ (FIPS 180-2:5.3.3) */
+static void
+sha512_init_ctx (struct sha512_ctx *ctx)
+{
+ ctx->H[0] = UINT64_C (0x6a09e667f3bcc908);
+ ctx->H[1] = UINT64_C (0xbb67ae8584caa73b);
+ ctx->H[2] = UINT64_C (0x3c6ef372fe94f82b);
+ ctx->H[3] = UINT64_C (0xa54ff53a5f1d36f1);
+ ctx->H[4] = UINT64_C (0x510e527fade682d1);
+ ctx->H[5] = UINT64_C (0x9b05688c2b3e6c1f);
+ ctx->H[6] = UINT64_C (0x1f83d9abfb41bd6b);
+ ctx->H[7] = UINT64_C (0x5be0cd19137e2179);
+
+ ctx->total[0] = ctx->total[1] = 0;
+ ctx->buflen = 0;
+}
+
+
+/* Process the remaining bytes in the internal buffer and the usual
+ prolog according to the standard and write the result to RESBUF.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+static void *
+sha512_finish_ctx (struct sha512_ctx *ctx, void *resbuf)
+{
+ /* Take yet unprocessed bytes into account. */
+ uint64_t bytes = ctx->buflen;
+ size_t pad;
+
+ /* Now count remaining bytes. */
+ ctx->total[0] += bytes;
+ if (ctx->total[0] < bytes)
+ ++ctx->total[1];
+
+ pad = bytes >= 112 ? 128 + 112 - bytes : 112 - bytes;
+ memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+ /* Put the 128-bit file length in *bits* at the end of the buffer. */
+ *(uint64_t *) &ctx->buffer[bytes + pad + 8] = SWAP (ctx->total[0] << 3);
+ *(uint64_t *) &ctx->buffer[bytes + pad] = SWAP ((ctx->total[1] << 3) |
+ (ctx->total[0] >> 61));
+
+ /* Process last bytes. */
+ sha512_process_block (ctx->buffer, bytes + pad + 16, ctx);
+
+ /* Put result from CTX in first 64 bytes following RESBUF. */
+ for (unsigned int i = 0; i < 8; ++i)
+ ((uint64_t *) resbuf)[i] = SWAP (ctx->H[i]);
+
+ return resbuf;
+}
+
+
+static void
+sha512_process_bytes (const void *buffer, size_t len, struct sha512_ctx *ctx)
+{
+ /* When we already have some bits in our internal buffer concatenate
+ both inputs first. */
+ if (ctx->buflen != 0)
+ {
+ size_t left_over = ctx->buflen;
+ size_t add = 256 - left_over > len ? len : 256 - left_over;
+
+ memcpy (&ctx->buffer[left_over], buffer, add);
+ ctx->buflen += add;
+
+ if (ctx->buflen > 128)
+ {
+ sha512_process_block (ctx->buffer, ctx->buflen & ~127, ctx);
+
+ ctx->buflen &= 127;
+ /* The regions in the following copy operation cannot overlap. */
+ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~127],
+ ctx->buflen);
+ }
+
+ buffer = (const char *) buffer + add;
+ len -= add;
+ }
+
+ /* Process available complete blocks. */
+ if (len >= 128)
+ {
+#if !_STRING_ARCH_unaligned
+/* To check alignment gcc has an appropriate operator. Other
+ compilers don't. */
+# if __GNUC__ >= 2
+# define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__ (uint64_t) != 0)
+# else
+# define UNALIGNED_P(p) (((uintptr_t) p) % sizeof (uint64_t) != 0)
+# endif
+ if (UNALIGNED_P (buffer))
+ while (len > 128)
+ {
+ sha512_process_block (memcpy (ctx->buffer, buffer, 128), 128,
+ ctx);
+ buffer = (const char *) buffer + 128;
+ len -= 128;
+ }
+ else
+#endif
+ {
+ sha512_process_block (buffer, len & ~127, ctx);
+ buffer = (const char *) buffer + (len & ~127);
+ len &= 127;
+ }
+ }
+
+ /* Move remaining bytes into internal buffer. */
+ if (len > 0)
+ {
+ size_t left_over = ctx->buflen;
+
+ memcpy (&ctx->buffer[left_over], buffer, len);
+ left_over += len;
+ if (left_over >= 128)
+ {
+ sha512_process_block (ctx->buffer, 128, ctx);
+ left_over -= 128;
+ memcpy (ctx->buffer, &ctx->buffer[128], left_over);
+ }
+ ctx->buflen = left_over;
+ }
+}
+
+
+/* Define our magic string to mark salt for SHA512 "encryption"
+ replacement. */
+static const char sha512_salt_prefix[] = "$6$";
+
+/* Prefix for optional rounds specification. */
+static const char sha512_rounds_prefix[] = "rounds=";
+
+/* Maximum salt string length. */
+#define SALT_LEN_MAX 16
+/* Default number of rounds if not explicitly specified. */
+#define ROUNDS_DEFAULT 5000
+/* Minimum number of rounds. */
+#define ROUNDS_MIN 1000
+/* Maximum number of rounds. */
+#define ROUNDS_MAX 999999999
+
+/* Table with characters for base64 transformation. */
+static const char b64t[64] =
+"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+static char *
+sha512_crypt_r (const char *key, const char *salt, char *buffer, int buflen)
+{
+ unsigned char alt_result[64]
+ __attribute__ ((__aligned__ (__alignof__ (uint64_t))));
+ unsigned char temp_result[64]
+ __attribute__ ((__aligned__ (__alignof__ (uint64_t))));
+ struct sha512_ctx ctx;
+ struct sha512_ctx alt_ctx;
+ size_t salt_len;
+ size_t key_len;
+ size_t cnt;
+ char *cp;
+ char *copied_key = NULL;
+ char *copied_salt = NULL;
+ char *p_bytes;
+ char *s_bytes;
+ /* Default number of rounds. */
+ size_t rounds = ROUNDS_DEFAULT;
+ bool rounds_custom = false;
+
+ /* Find beginning of salt string. The prefix should normally always
+ be present. Just in case it is not. */
+ if (strncmp (sha512_salt_prefix, salt, sizeof (sha512_salt_prefix) - 1) == 0)
+ /* Skip salt prefix. */
+ salt += sizeof (sha512_salt_prefix) - 1;
+
+ if (strncmp (salt, sha512_rounds_prefix, sizeof (sha512_rounds_prefix) - 1)
+ == 0)
+ {
+ const char *num = salt + sizeof (sha512_rounds_prefix) - 1;
+ char *endp;
+ unsigned long int srounds = strtoul (num, &endp, 10);
+ if (*endp == '$')
+ {
+ salt = endp + 1;
+ rounds = MAX (ROUNDS_MIN, MIN (srounds, ROUNDS_MAX));
+ rounds_custom = true;
+ }
+ }
+
+ salt_len = MIN (strcspn (salt, "$"), SALT_LEN_MAX);
+ key_len = strlen (key);
+
+ if ((key - (char *) 0) % __alignof__ (uint64_t) != 0)
+ {
+ char *tmp = (char *) alloca (key_len + __alignof__ (uint64_t));
+ key = copied_key =
+ memcpy (tmp + __alignof__ (uint64_t)
+ - (tmp - (char *) 0) % __alignof__ (uint64_t),
+ key, key_len);
+ }
+
+ if ((salt - (char *) 0) % __alignof__ (uint64_t) != 0)
+ {
+ char *tmp = (char *) alloca (salt_len + __alignof__ (uint64_t));
+ salt = copied_salt =
+ memcpy (tmp + __alignof__ (uint64_t)
+ - (tmp - (char *) 0) % __alignof__ (uint64_t),
+ salt, salt_len);
+ }
+
+ /* Prepare for the real work. */
+ sha512_init_ctx (&ctx);
+
+ /* Add the key string. */
+ sha512_process_bytes (key, key_len, &ctx);
+
+ /* The last part is the salt string. This must be at most 16
+ characters and it ends at the first `$' character (for
+ compatibility with existing implementations). */
+ sha512_process_bytes (salt, salt_len, &ctx);
+
+
+ /* Compute alternate SHA512 sum with input KEY, SALT, and KEY. The
+ final result will be added to the first context. */
+ sha512_init_ctx (&alt_ctx);
+
+ /* Add key. */
+ sha512_process_bytes (key, key_len, &alt_ctx);
+
+ /* Add salt. */
+ sha512_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Add key again. */
+ sha512_process_bytes (key, key_len, &alt_ctx);
+
+ /* Now get result of this (64 bytes) and add it to the other
+ context. */
+ sha512_finish_ctx (&alt_ctx, alt_result);
+
+ /* Add for any character in the key one byte of the alternate sum. */
+ for (cnt = key_len; cnt > 64; cnt -= 64)
+ sha512_process_bytes (alt_result, 64, &ctx);
+ sha512_process_bytes (alt_result, cnt, &ctx);
+
+ /* Take the binary representation of the length of the key and for every
+ 1 add the alternate sum, for every 0 the key. */
+ for (cnt = key_len; cnt > 0; cnt >>= 1)
+ if ((cnt & 1) != 0)
+ sha512_process_bytes (alt_result, 64, &ctx);
+ else
+ sha512_process_bytes (key, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha512_finish_ctx (&ctx, alt_result);
+
+ /* Start computation of P byte sequence. */
+ sha512_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < key_len; ++cnt)
+ sha512_process_bytes (key, key_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha512_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence P. */
+ cp = p_bytes = alloca (key_len);
+ for (cnt = key_len; cnt >= 64; cnt -= 64)
+ cp = mempcpy (cp, temp_result, 64);
+ memcpy (cp, temp_result, cnt);
+
+ /* Start computation of S byte sequence. */
+ sha512_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
+ sha512_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha512_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence S. */
+ cp = s_bytes = alloca (salt_len);
+ for (cnt = salt_len; cnt >= 64; cnt -= 64)
+ cp = mempcpy (cp, temp_result, 64);
+ memcpy (cp, temp_result, cnt);
+
+ /* Repeatedly run the collected hash value through SHA512 to burn
+ CPU cycles. */
+ for (cnt = 0; cnt < rounds; ++cnt)
+ {
+ /* New context. */
+ sha512_init_ctx (&ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha512_process_bytes (p_bytes, key_len, &ctx);
+ else
+ sha512_process_bytes (alt_result, 64, &ctx);
+
+ /* Add salt for numbers not divisible by 3. */
+ if (cnt % 3 != 0)
+ sha512_process_bytes (s_bytes, salt_len, &ctx);
+
+ /* Add key for numbers not divisible by 7. */
+ if (cnt % 7 != 0)
+ sha512_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha512_process_bytes (alt_result, 64, &ctx);
+ else
+ sha512_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha512_finish_ctx (&ctx, alt_result);
+ }
+
+ /* Now we can construct the result string. It consists of three
+ parts. */
+ cp = __stpncpy (buffer, sha512_salt_prefix, MAX (0, buflen));
+ buflen -= sizeof (sha512_salt_prefix) - 1;
+
+ if (rounds_custom)
+ {
+ int n = snprintf (cp, MAX (0, buflen), "%s%zu$",
+ sha512_rounds_prefix, rounds);
+ cp += n;
+ buflen -= n;
+ }
+
+ cp = __stpncpy (cp, salt, MIN ((size_t) MAX (0, buflen), salt_len));
+ buflen -= MIN ((size_t) MAX (0, buflen), salt_len);
+
+ if (buflen > 0)
+ {
+ *cp++ = '$';
+ --buflen;
+ }
+
+#define b64_from_24bit(B2, B1, B0, N) \
+ do { \
+ unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+ int n = (N); \
+ while (n-- > 0 && buflen > 0) \
+ { \
+ *cp++ = b64t[w & 0x3f]; \
+ --buflen; \
+ w >>= 6; \
+ } \
+ } while (0)
+
+ b64_from_24bit (alt_result[0], alt_result[21], alt_result[42], 4);
+ b64_from_24bit (alt_result[22], alt_result[43], alt_result[1], 4);
+ b64_from_24bit (alt_result[44], alt_result[2], alt_result[23], 4);
+ b64_from_24bit (alt_result[3], alt_result[24], alt_result[45], 4);
+ b64_from_24bit (alt_result[25], alt_result[46], alt_result[4], 4);
+ b64_from_24bit (alt_result[47], alt_result[5], alt_result[26], 4);
+ b64_from_24bit (alt_result[6], alt_result[27], alt_result[48], 4);
+ b64_from_24bit (alt_result[28], alt_result[49], alt_result[7], 4);
+ b64_from_24bit (alt_result[50], alt_result[8], alt_result[29], 4);
+ b64_from_24bit (alt_result[9], alt_result[30], alt_result[51], 4);
+ b64_from_24bit (alt_result[31], alt_result[52], alt_result[10], 4);
+ b64_from_24bit (alt_result[53], alt_result[11], alt_result[32], 4);
+ b64_from_24bit (alt_result[12], alt_result[33], alt_result[54], 4);
+ b64_from_24bit (alt_result[34], alt_result[55], alt_result[13], 4);
+ b64_from_24bit (alt_result[56], alt_result[14], alt_result[35], 4);
+ b64_from_24bit (alt_result[15], alt_result[36], alt_result[57], 4);
+ b64_from_24bit (alt_result[37], alt_result[58], alt_result[16], 4);
+ b64_from_24bit (alt_result[59], alt_result[17], alt_result[38], 4);
+ b64_from_24bit (alt_result[18], alt_result[39], alt_result[60], 4);
+ b64_from_24bit (alt_result[40], alt_result[61], alt_result[19], 4);
+ b64_from_24bit (alt_result[62], alt_result[20], alt_result[41], 4);
+ b64_from_24bit (0, 0, alt_result[63], 2);
+
+ if (buflen <= 0)
+ {
+ errno = ERANGE;
+ buffer = NULL;
+ }
+ else
+ *cp = '\0'; /* Terminate the string. */
+
+ /* Clear the buffer for the intermediate result so that people
+ attaching to processes or reading core dumps cannot get any
+ information. We do it in this way to clear correct_words[]
+ inside the SHA512 implementation as well. */
+ sha512_init_ctx (&ctx);
+ sha512_finish_ctx (&ctx, alt_result);
+ memset (temp_result, '\0', sizeof (temp_result));
+ memset (p_bytes, '\0', key_len);
+ memset (s_bytes, '\0', salt_len);
+ memset (&ctx, '\0', sizeof (ctx));
+ memset (&alt_ctx, '\0', sizeof (alt_ctx));
+ if (copied_key != NULL)
+ memset (copied_key, '\0', key_len);
+ if (copied_salt != NULL)
+ memset (copied_salt, '\0', salt_len);
+
+ return buffer;
+}
+
+
+/* This entry point is equivalent to the `crypt' function in Unix
+ libcs. */
+char *
+sha512_crypt (const char *key, const char *salt)
+{
+ /* We don't want to have an arbitrary limit in the size of the
+ password. We can compute an upper bound for the size of the
+ result in advance and so we can prepare the buffer we pass to
+ `sha512_crypt_r'. */
+ static char *buffer;
+ static int buflen;
+ int needed = (sizeof (sha512_salt_prefix) - 1
+ + sizeof (sha512_rounds_prefix) + 9 + 1
+ + strlen (salt) + 1 + 86 + 1);
+
+ if (buflen < needed)
+ {
+ char *new_buffer = (char *) realloc (buffer, needed);
+ if (new_buffer == NULL)
+ return NULL;
+
+ buffer = new_buffer;
+ buflen = needed;
+ }
+
+ return sha512_crypt_r (key, salt, buffer, buflen);
+}
+
+
+#ifdef TEST
+static const struct
+{
+ const char *input;
+ const char result[64];
+} tests[] =
+ {
+ /* Test vectors from FIPS 180-2: appendix C.1. */
+ { "abc",
+ "\xdd\xaf\x35\xa1\x93\x61\x7a\xba\xcc\x41\x73\x49\xae\x20\x41\x31"
+ "\x12\xe6\xfa\x4e\x89\xa9\x7e\xa2\x0a\x9e\xee\xe6\x4b\x55\xd3\x9a"
+ "\x21\x92\x99\x2a\x27\x4f\xc1\xa8\x36\xba\x3c\x23\xa3\xfe\xeb\xbd"
+ "\x45\x4d\x44\x23\x64\x3c\xe8\x0e\x2a\x9a\xc9\x4f\xa5\x4c\xa4\x9f" },
+ /* Test vectors from FIPS 180-2: appendix C.2. */
+ { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+ "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "\x8e\x95\x9b\x75\xda\xe3\x13\xda\x8c\xf4\xf7\x28\x14\xfc\x14\x3f"
+ "\x8f\x77\x79\xc6\xeb\x9f\x7f\xa1\x72\x99\xae\xad\xb6\x88\x90\x18"
+ "\x50\x1d\x28\x9e\x49\x00\xf7\xe4\x33\x1b\x99\xde\xc4\xb5\x43\x3a"
+ "\xc7\xd3\x29\xee\xb6\xdd\x26\x54\x5e\x96\xe5\x5b\x87\x4b\xe9\x09" },
+ /* Test vectors from the NESSIE project. */
+ { "",
+ "\xcf\x83\xe1\x35\x7e\xef\xb8\xbd\xf1\x54\x28\x50\xd6\x6d\x80\x07"
+ "\xd6\x20\xe4\x05\x0b\x57\x15\xdc\x83\xf4\xa9\x21\xd3\x6c\xe9\xce"
+ "\x47\xd0\xd1\x3c\x5d\x85\xf2\xb0\xff\x83\x18\xd2\x87\x7e\xec\x2f"
+ "\x63\xb9\x31\xbd\x47\x41\x7a\x81\xa5\x38\x32\x7a\xf9\x27\xda\x3e" },
+ { "a",
+ "\x1f\x40\xfc\x92\xda\x24\x16\x94\x75\x09\x79\xee\x6c\xf5\x82\xf2"
+ "\xd5\xd7\xd2\x8e\x18\x33\x5d\xe0\x5a\xbc\x54\xd0\x56\x0e\x0f\x53"
+ "\x02\x86\x0c\x65\x2b\xf0\x8d\x56\x02\x52\xaa\x5e\x74\x21\x05\x46"
+ "\xf3\x69\xfb\xbb\xce\x8c\x12\xcf\xc7\x95\x7b\x26\x52\xfe\x9a\x75" },
+ { "message digest",
+ "\x10\x7d\xbf\x38\x9d\x9e\x9f\x71\xa3\xa9\x5f\x6c\x05\x5b\x92\x51"
+ "\xbc\x52\x68\xc2\xbe\x16\xd6\xc1\x34\x92\xea\x45\xb0\x19\x9f\x33"
+ "\x09\xe1\x64\x55\xab\x1e\x96\x11\x8e\x8a\x90\x5d\x55\x97\xb7\x20"
+ "\x38\xdd\xb3\x72\xa8\x98\x26\x04\x6d\xe6\x66\x87\xbb\x42\x0e\x7c" },
+ { "abcdefghijklmnopqrstuvwxyz",
+ "\x4d\xbf\xf8\x6c\xc2\xca\x1b\xae\x1e\x16\x46\x8a\x05\xcb\x98\x81"
+ "\xc9\x7f\x17\x53\xbc\xe3\x61\x90\x34\x89\x8f\xaa\x1a\xab\xe4\x29"
+ "\x95\x5a\x1b\xf8\xec\x48\x3d\x74\x21\xfe\x3c\x16\x46\x61\x3a\x59"
+ "\xed\x54\x41\xfb\x0f\x32\x13\x89\xf7\x7f\x48\xa8\x79\xc7\xb1\xf1" },
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "\x20\x4a\x8f\xc6\xdd\xa8\x2f\x0a\x0c\xed\x7b\xeb\x8e\x08\xa4\x16"
+ "\x57\xc1\x6e\xf4\x68\xb2\x28\xa8\x27\x9b\xe3\x31\xa7\x03\xc3\x35"
+ "\x96\xfd\x15\xc1\x3b\x1b\x07\xf9\xaa\x1d\x3b\xea\x57\x78\x9c\xa0"
+ "\x31\xad\x85\xc7\xa7\x1d\xd7\x03\x54\xec\x63\x12\x38\xca\x34\x45" },
+ { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "\x1e\x07\xbe\x23\xc2\x6a\x86\xea\x37\xea\x81\x0c\x8e\xc7\x80\x93"
+ "\x52\x51\x5a\x97\x0e\x92\x53\xc2\x6f\x53\x6c\xfc\x7a\x99\x96\xc4"
+ "\x5c\x83\x70\x58\x3e\x0a\x78\xfa\x4a\x90\x04\x1d\x71\xa4\xce\xab"
+ "\x74\x23\xf1\x9c\x71\xb9\xd5\xa3\xe0\x12\x49\xf0\xbe\xbd\x58\x94" },
+ { "123456789012345678901234567890123456789012345678901234567890"
+ "12345678901234567890",
+ "\x72\xec\x1e\xf1\x12\x4a\x45\xb0\x47\xe8\xb7\xc7\x5a\x93\x21\x95"
+ "\x13\x5b\xb6\x1d\xe2\x4e\xc0\xd1\x91\x40\x42\x24\x6e\x0a\xec\x3a"
+ "\x23\x54\xe0\x93\xd7\x6f\x30\x48\xb4\x56\x76\x43\x46\x90\x0c\xb1"
+ "\x30\xd2\xa4\xfd\x5d\xd1\x6a\xbb\x5e\x30\xbc\xb8\x50\xde\xe8\x43" }
+ };
+#define ntests (sizeof (tests) / sizeof (tests[0]))
+
+
+static const struct
+{
+ const char *salt;
+ const char *input;
+ const char *expected;
+} tests2[] =
+{
+ { "$6$saltstring", "Hello world!",
+ "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu"
+ "esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" },
+ { "$6$rounds=10000$saltstringsaltstring", "Hello world!",
+ "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb"
+ "HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." },
+ { "$6$rounds=5000$toolongsaltstring", "This is just a test",
+ "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQ"
+ "zQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0" },
+ { "$6$rounds=1400$anotherlongsaltstring",
+ "a very much longer text to encrypt. This one even stretches over more"
+ "than one line.",
+ "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wP"
+ "vMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1" },
+ { "$6$rounds=77777$short",
+ "we have a short salt string but not a short password",
+ "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g"
+ "ge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0" },
+ { "$6$rounds=123456$asaltof16chars..", "a short string",
+ "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc"
+ "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1" },
+ { "$6$rounds=10$roundstoolow", "the minimum number is still observed",
+ "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x"
+ "hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." },
+};
+#define ntests2 (sizeof (tests2) / sizeof (tests2[0]))
+
+
+int
+main (void)
+{
+ struct sha512_ctx ctx;
+ char sum[64];
+ int result = 0;
+ int cnt;
+
+ for (cnt = 0; cnt < (int) ntests; ++cnt)
+ {
+ sha512_init_ctx (&ctx);
+ sha512_process_bytes (tests[cnt].input, strlen (tests[cnt].input), &ctx);
+ sha512_finish_ctx (&ctx, sum);
+ if (memcmp (tests[cnt].result, sum, 64) != 0)
+ {
+ printf ("test %d run %d failed\n", cnt, 1);
+ result = 1;
+ }
+
+ sha512_init_ctx (&ctx);
+ for (int i = 0; tests[cnt].input[i] != '\0'; ++i)
+ sha512_process_bytes (&tests[cnt].input[i], 1, &ctx);
+ sha512_finish_ctx (&ctx, sum);
+ if (memcmp (tests[cnt].result, sum, 64) != 0)
+ {
+ printf ("test %d run %d failed\n", cnt, 2);
+ result = 1;
+ }
+ }
+
+ /* Test vector from FIPS 180-2: appendix C.3. */
+ char buf[1000];
+ memset (buf, 'a', sizeof (buf));
+ sha512_init_ctx (&ctx);
+ for (int i = 0; i < 1000; ++i)
+ sha512_process_bytes (buf, sizeof (buf), &ctx);
+ sha512_finish_ctx (&ctx, sum);
+ static const char expected[64] =
+ "\xe7\x18\x48\x3d\x0c\xe7\x69\x64\x4e\x2e\x42\xc7\xbc\x15\xb4\x63"
+ "\x8e\x1f\x98\xb1\x3b\x20\x44\x28\x56\x32\xa8\x03\xaf\xa9\x73\xeb"
+ "\xde\x0f\xf2\x44\x87\x7e\xa6\x0a\x4c\xb0\x43\x2c\xe5\x77\xc3\x1b"
+ "\xeb\x00\x9c\x5c\x2c\x49\xaa\x2e\x4e\xad\xb2\x17\xad\x8c\xc0\x9b";
+ if (memcmp (expected, sum, 64) != 0)
+ {
+ printf ("test %d failed\n", cnt);
+ result = 1;
+ }
+
+ for (cnt = 0; cnt < ntests2; ++cnt)
+ {
+ char *cp = sha512_crypt (tests2[cnt].input, tests2[cnt].salt);
+
+ if (strcmp (cp, tests2[cnt].expected) != 0)
+ {
+ printf ("test %d: expected \"%s\", got \"%s\"\n",
+ cnt, tests2[cnt].expected, cp);
+ result = 1;
+ }
+ }
+
+ if (result == 0)
+ puts ("all tests OK");
+
+ return result;
+}
+#endif
+-----------------------------------------------------------------------------
diff --git a/lib/mysql_data_stream.cpp b/lib/mysql_data_stream.cpp
index 4d1fa9ae0d..0f66276773 100644
--- a/lib/mysql_data_stream.cpp
+++ b/lib/mysql_data_stream.cpp
@@ -337,7 +337,9 @@ MySQL_Data_Stream::MySQL_Data_Stream() {
DSS=STATE_NOT_CONNECTED;
encrypted=false;
switching_auth_stage = 0;
- switching_auth_type = 0;
+ switching_auth_type = AUTH_UNKNOWN_PLUGIN;
+ switching_auth_sent = AUTH_UNKNOWN_PLUGIN;
+ auth_in_progress = 0;
x509_subject_alt_name=NULL;
ssl=NULL;
rbio_ssl = NULL;
diff --git a/lib/sha256crypt.cpp b/lib/sha256crypt.cpp
new file mode 100644
index 0000000000..51c996b125
--- /dev/null
+++ b/lib/sha256crypt.cpp
@@ -0,0 +1,576 @@
+/* SHA256-based Unix crypt implementation.
+ Released into the Public Domain by Ulrich Drepper . */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/* Structure to save state of computation between the single steps. */
+struct sha256_ctx
+{
+ uint32_t H[8];
+
+ uint32_t total[2];
+ uint32_t buflen;
+ char buffer[128]; /* NB: always correctly aligned for uint32_t. */
+};
+
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# define SWAP(n) \
+ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+ 64-byte boundary. (FIPS 180-2:5.1.1) */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
+
+
+/* Constants for SHA256 from FIPS 180-2:4.2.2. */
+static const uint32_t K[64] =
+ {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+ };
+
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+ It is assumed that LEN % 64 == 0. */
+static void
+sha256_process_block (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+ const uint32_t *words = (const uint32_t*) buffer;
+ size_t nwords = len / sizeof (uint32_t);
+ uint32_t a = ctx->H[0];
+ uint32_t b = ctx->H[1];
+ uint32_t c = ctx->H[2];
+ uint32_t d = ctx->H[3];
+ uint32_t e = ctx->H[4];
+ uint32_t f = ctx->H[5];
+ uint32_t g = ctx->H[6];
+ uint32_t h = ctx->H[7];
+
+ /* First increment the byte count. FIPS 180-2 specifies the possible
+ length of the file up to 2^64 bits. Here we only compute the
+ number of bytes. Do a double word increment. */
+ ctx->total[0] += len;
+ if (ctx->total[0] < len)
+ ++ctx->total[1];
+
+ /* Process all bytes in the buffer with 64 bytes in each round of
+ the loop. */
+ while (nwords > 0)
+ {
+ uint32_t W[64];
+ uint32_t a_save = a;
+ uint32_t b_save = b;
+ uint32_t c_save = c;
+ uint32_t d_save = d;
+ uint32_t e_save = e;
+ uint32_t f_save = f;
+ uint32_t g_save = g;
+ uint32_t h_save = h;
+
+ /* Operators defined in FIPS 180-2:4.1.2. */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22))
+#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25))
+#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3))
+#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10))
+
+ /* It is unfortunate that C does not provide an operator for
+ cyclic rotation. Hope the C compiler is smart enough. */
+#define CYCLIC(w, s) ((w >> s) | (w << (32 - s)))
+
+ /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */
+ for (unsigned int t = 0; t < 16; ++t)
+ {
+ W[t] = SWAP (*words);
+ ++words;
+ }
+ for (unsigned int t = 16; t < 64; ++t)
+ W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
+
+ /* The actual computation according to FIPS 180-2:6.2.2 step 3. */
+ for (unsigned int t = 0; t < 64; ++t)
+ {
+ uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
+ uint32_t T2 = S0 (a) + Maj (a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + T1;
+ d = c;
+ c = b;
+ b = a;
+ a = T1 + T2;
+ }
+
+ /* Add the starting values of the context according to FIPS 180-2:6.2.2
+ step 4. */
+ a += a_save;
+ b += b_save;
+ c += c_save;
+ d += d_save;
+ e += e_save;
+ f += f_save;
+ g += g_save;
+ h += h_save;
+
+ /* Prepare for the next round. */
+ nwords -= 16;
+ }
+
+ /* Put checksum in context given as argument. */
+ ctx->H[0] = a;
+ ctx->H[1] = b;
+ ctx->H[2] = c;
+ ctx->H[3] = d;
+ ctx->H[4] = e;
+ ctx->H[5] = f;
+ ctx->H[6] = g;
+ ctx->H[7] = h;
+}
+
+
+/* Initialize structure containing state of computation.
+ (FIPS 180-2:5.3.2) */
+static void
+sha256_init_ctx (struct sha256_ctx *ctx)
+{
+ ctx->H[0] = 0x6a09e667;
+ ctx->H[1] = 0xbb67ae85;
+ ctx->H[2] = 0x3c6ef372;
+ ctx->H[3] = 0xa54ff53a;
+ ctx->H[4] = 0x510e527f;
+ ctx->H[5] = 0x9b05688c;
+ ctx->H[6] = 0x1f83d9ab;
+ ctx->H[7] = 0x5be0cd19;
+
+ ctx->total[0] = ctx->total[1] = 0;
+ ctx->buflen = 0;
+}
+
+
+/* Process the remaining bytes in the internal buffer and the usual
+ prolog according to the standard and write the result to RESBUF.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+static void *
+sha256_finish_ctx (struct sha256_ctx *ctx, void *resbuf)
+{
+ /* Take yet unprocessed bytes into account. */
+ uint32_t bytes = ctx->buflen;
+ size_t pad;
+
+ /* Now count remaining bytes. */
+ ctx->total[0] += bytes;
+ if (ctx->total[0] < bytes)
+ ++ctx->total[1];
+
+ pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+ memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+ /* Put the 64-bit file length in *bits* at the end of the buffer. */
+ *(uint32_t *) &ctx->buffer[bytes + pad + 4] = SWAP (ctx->total[0] << 3);
+ *(uint32_t *) &ctx->buffer[bytes + pad] = SWAP ((ctx->total[1] << 3) |
+ (ctx->total[0] >> 29));
+
+ /* Process last bytes. */
+ sha256_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+ /* Put result from CTX in first 32 bytes following RESBUF. */
+ for (unsigned int i = 0; i < 8; ++i)
+ ((uint32_t *) resbuf)[i] = SWAP (ctx->H[i]);
+
+ return resbuf;
+}
+
+
+static void
+sha256_process_bytes (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+ /* When we already have some bits in our internal buffer concatenate
+ both inputs first. */
+ if (ctx->buflen != 0)
+ {
+ size_t left_over = ctx->buflen;
+ size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+ memcpy (&ctx->buffer[left_over], buffer, add);
+ ctx->buflen += add;
+
+ if (ctx->buflen > 64)
+ {
+ sha256_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
+
+ ctx->buflen &= 63;
+ /* The regions in the following copy operation cannot overlap. */
+ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+ ctx->buflen);
+ }
+
+ buffer = (const char *) buffer + add;
+ len -= add;
+ }
+
+ /* Process available complete blocks. */
+ if (len >= 64)
+ {
+/* To check alignment gcc has an appropriate operator. Other
+ compilers don't. */
+#if __GNUC__ >= 2
+# define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__ (uint32_t) != 0)
+#else
+# define UNALIGNED_P(p) (((uintptr_t) p) % sizeof (uint32_t) != 0)
+#endif
+ if (UNALIGNED_P (buffer))
+ while (len > 64)
+ {
+ sha256_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx);
+ buffer = (const char *) buffer + 64;
+ len -= 64;
+ }
+ else
+ {
+ sha256_process_block (buffer, len & ~63, ctx);
+ buffer = (const char *) buffer + (len & ~63);
+ len &= 63;
+ }
+ }
+
+ /* Move remaining bytes into internal buffer. */
+ if (len > 0)
+ {
+ size_t left_over = ctx->buflen;
+
+ memcpy (&ctx->buffer[left_over], buffer, len);
+ left_over += len;
+ if (left_over >= 64)
+ {
+ sha256_process_block (ctx->buffer, 64, ctx);
+ left_over -= 64;
+ memcpy (ctx->buffer, &ctx->buffer[64], left_over);
+ }
+ ctx->buflen = left_over;
+ }
+}
+
+
+/* Define our magic string to mark salt for SHA256 "encryption"
+ replacement. */
+static const char sha256_salt_prefix[] = "$5$";
+
+/* Prefix for optional rounds specification. */
+static const char sha256_rounds_prefix[] = "rounds=";
+
+/* Maximum salt string length. */
+#define SALT_LEN_MAX 20
+/* Default number of rounds if not explicitly specified. */
+#define ROUNDS_DEFAULT 5000
+/* Minimum number of rounds. */
+#define ROUNDS_MIN 1000
+/* Maximum number of rounds. */
+#define ROUNDS_MAX 999999999
+
+/* Table with characters for base64 transformation. */
+static const char b64t[65] =
+"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+char * sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen)
+{
+ unsigned char alt_result[32]
+ __attribute__ ((__aligned__ (__alignof__ (uint32_t))));
+ unsigned char temp_result[32]
+ __attribute__ ((__aligned__ (__alignof__ (uint32_t))));
+ struct sha256_ctx ctx;
+ struct sha256_ctx alt_ctx;
+ size_t salt_len;
+ size_t key_len;
+ size_t cnt;
+ char *cp;
+ char *copied_key = NULL;
+ char *copied_salt = NULL;
+ char *p_bytes;
+ char *s_bytes;
+ /* Default number of rounds. */
+ size_t rounds = ROUNDS_DEFAULT;
+ bool rounds_custom = false;
+
+ /* Find beginning of salt string. The prefix should normally always
+ be present. Just in case it is not. */
+ if (strncmp (sha256_salt_prefix, salt, sizeof (sha256_salt_prefix) - 1) == 0)
+ /* Skip salt prefix. */
+ salt += sizeof (sha256_salt_prefix) - 1;
+
+ if (strncmp (salt, sha256_rounds_prefix, sizeof (sha256_rounds_prefix) - 1)
+ == 0)
+ {
+ const char *num = salt + sizeof (sha256_rounds_prefix) - 1;
+ char *endp;
+ unsigned long int srounds = strtoul (num, &endp, 10);
+ if (*endp == '$')
+ {
+ salt = endp + 1;
+ rounds = MAX (ROUNDS_MIN, MIN (srounds, ROUNDS_MAX));
+ rounds_custom = true;
+ }
+ }
+
+ salt_len = MIN (strcspn (salt, "$"), SALT_LEN_MAX);
+ key_len = strlen (key);
+
+ if ((key - (char *) 0) % __alignof__ (uint32_t) != 0)
+ {
+ char *tmp = (char *) alloca (key_len + __alignof__ (uint32_t));
+ key = copied_key = (char *)
+ memcpy (tmp + __alignof__ (uint32_t)
+ - (tmp - (char *) 0) % __alignof__ (uint32_t),
+ key, key_len);
+ }
+
+ if ((salt - (char *) 0) % __alignof__ (uint32_t) != 0)
+ {
+ char *tmp = (char *) alloca (salt_len + __alignof__ (uint32_t));
+ salt = copied_salt = (char *)
+ memcpy (tmp + __alignof__ (uint32_t)
+ - (tmp - (char *) 0) % __alignof__ (uint32_t),
+ salt, salt_len);
+ }
+
+ /* Prepare for the real work. */
+ sha256_init_ctx (&ctx);
+
+ /* Add the key string. */
+ sha256_process_bytes (key, key_len, &ctx);
+
+ /* The last part is the salt string. This must be at most 16
+ characters and it ends at the first `$' character (for
+ compatibility with existing implementations). */
+ sha256_process_bytes (salt, salt_len, &ctx);
+
+
+ /* Compute alternate SHA256 sum with input KEY, SALT, and KEY. The
+ final result will be added to the first context. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* Add key. */
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Add salt. */
+ sha256_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Add key again. */
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Now get result of this (32 bytes) and add it to the other
+ context. */
+ sha256_finish_ctx (&alt_ctx, alt_result);
+
+ /* Add for any character in the key one byte of the alternate sum. */
+ for (cnt = key_len; cnt > 32; cnt -= 32)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ sha256_process_bytes (alt_result, cnt, &ctx);
+
+ /* Take the binary representation of the length of the key and for every
+ 1 add the alternate sum, for every 0 the key. */
+ for (cnt = key_len; cnt > 0; cnt >>= 1)
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ else
+ sha256_process_bytes (key, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha256_finish_ctx (&ctx, alt_result);
+
+ /* Start computation of P byte sequence. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < key_len; ++cnt)
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha256_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence P. */
+ cp = p_bytes = (char *)alloca (key_len);
+ for (cnt = key_len; cnt >= 32; cnt -= 32)
+ cp = (char *)mempcpy (cp, temp_result, 32);
+ memcpy (cp, temp_result, cnt);
+
+ /* Start computation of S byte sequence. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < static_cast(16) + alt_result[0]; ++cnt)
+ sha256_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha256_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence S. */
+ cp = s_bytes = (char *)alloca (salt_len);
+ for (cnt = salt_len; cnt >= 32; cnt -= 32)
+ cp = (char *) mempcpy (cp, temp_result, 32);
+ memcpy (cp, temp_result, cnt);
+
+ /* Repeatedly run the collected hash value through SHA256 to burn
+ CPU cycles. */
+ for (cnt = 0; cnt < rounds; ++cnt)
+ {
+ /* New context. */
+ sha256_init_ctx (&ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+ else
+ sha256_process_bytes (alt_result, 32, &ctx);
+
+ /* Add salt for numbers not divisible by 3. */
+ if (cnt % 3 != 0)
+ sha256_process_bytes (s_bytes, salt_len, &ctx);
+
+ /* Add key for numbers not divisible by 7. */
+ if (cnt % 7 != 0)
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ else
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha256_finish_ctx (&ctx, alt_result);
+ }
+
+ /* Now we can construct the result string. It consists of three
+ parts. */
+ cp = stpncpy (buffer, sha256_salt_prefix, MAX (0, buflen));
+ buflen -= sizeof (sha256_salt_prefix) - 1;
+
+ if (rounds_custom)
+ {
+ int n = snprintf (cp, MAX (0, buflen), "%s%zu$",
+ sha256_rounds_prefix, rounds);
+ cp += n;
+ buflen -= n;
+ }
+
+ cp = stpncpy (cp, salt, MIN ((size_t) MAX (0, buflen), salt_len));
+ buflen -= MIN ((size_t) MAX (0, buflen), salt_len);
+
+ if (buflen > 0)
+ {
+ *cp++ = '$';
+ --buflen;
+ }
+
+#define b64_from_24bit(B2, B1, B0, N) \
+ do { \
+ unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+ int n = (N); \
+ while (n-- > 0 && buflen > 0) \
+ { \
+ *cp++ = b64t[w & 0x3f]; \
+ --buflen; \
+ w >>= 6; \
+ } \
+ } while (0)
+
+ b64_from_24bit (alt_result[0], alt_result[10], alt_result[20], 4);
+ b64_from_24bit (alt_result[21], alt_result[1], alt_result[11], 4);
+ b64_from_24bit (alt_result[12], alt_result[22], alt_result[2], 4);
+ b64_from_24bit (alt_result[3], alt_result[13], alt_result[23], 4);
+ b64_from_24bit (alt_result[24], alt_result[4], alt_result[14], 4);
+ b64_from_24bit (alt_result[15], alt_result[25], alt_result[5], 4);
+ b64_from_24bit (alt_result[6], alt_result[16], alt_result[26], 4);
+ b64_from_24bit (alt_result[27], alt_result[7], alt_result[17], 4);
+ b64_from_24bit (alt_result[18], alt_result[28], alt_result[8], 4);
+ b64_from_24bit (alt_result[9], alt_result[19], alt_result[29], 4);
+ b64_from_24bit (0, alt_result[31], alt_result[30], 3);
+ if (buflen <= 0)
+ {
+ errno = ERANGE;
+ buffer = NULL;
+ }
+ else
+ *cp = '\0'; /* Terminate the string. */
+
+ /* Clear the buffer for the intermediate result so that people
+ attaching to processes or reading core dumps cannot get any
+ information. We do it in this way to clear correct_words[]
+ inside the SHA256 implementation as well. */
+ sha256_init_ctx (&ctx);
+ sha256_finish_ctx (&ctx, alt_result);
+ memset (temp_result, '\0', sizeof (temp_result));
+ memset (p_bytes, '\0', key_len);
+ memset (s_bytes, '\0', salt_len);
+ memset (&ctx, '\0', sizeof (ctx));
+ memset (&alt_ctx, '\0', sizeof (alt_ctx));
+ if (copied_key != NULL)
+ memset (copied_key, '\0', key_len);
+ if (copied_salt != NULL)
+ memset (copied_salt, '\0', salt_len);
+
+ return buffer;
+}
+
+
+/* This entry point is equivalent to the `crypt' function in Unix
+ libcs. */
+char *
+sha256_crypt (const char *key, const char *salt)
+{
+ /* We don't want to have an arbitrary limit in the size of the
+ password. We can compute an upper bound for the size of the
+ result in advance and so we can prepare the buffer we pass to
+ `sha256_crypt_r'. */
+ static char *buffer;
+ static int buflen;
+ int needed = (sizeof (sha256_salt_prefix) - 1
+ + sizeof (sha256_rounds_prefix) + 9 + 1
+ + strlen (salt) + 1 + 43 + 1);
+
+ if (buflen < needed)
+ {
+ char *new_buffer = (char *) realloc (buffer, needed);
+ if (new_buffer == NULL)
+ return NULL;
+
+ buffer = new_buffer;
+ buflen = needed;
+ }
+
+ return sha256_crypt_r (key, salt, buffer, buflen);
+}
diff --git a/lib/sha256crypt.orig.c b/lib/sha256crypt.orig.c
new file mode 100644
index 0000000000..ae644ea208
--- /dev/null
+++ b/lib/sha256crypt.orig.c
@@ -0,0 +1,718 @@
+/* SHA256-based Unix crypt implementation.
+ Released into the Public Domain by Ulrich Drepper . */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/* Structure to save state of computation between the single steps. */
+struct sha256_ctx
+{
+ uint32_t H[8];
+
+ uint32_t total[2];
+ uint32_t buflen;
+ char buffer[128]; /* NB: always correctly aligned for uint32_t. */
+};
+
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# define SWAP(n) \
+ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+ 64-byte boundary. (FIPS 180-2:5.1.1) */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
+
+
+/* Constants for SHA256 from FIPS 180-2:4.2.2. */
+static const uint32_t K[64] =
+ {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+ };
+
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+ It is assumed that LEN % 64 == 0. */
+static void
+sha256_process_block (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+ const uint32_t *words = buffer;
+ size_t nwords = len / sizeof (uint32_t);
+ uint32_t a = ctx->H[0];
+ uint32_t b = ctx->H[1];
+ uint32_t c = ctx->H[2];
+ uint32_t d = ctx->H[3];
+ uint32_t e = ctx->H[4];
+ uint32_t f = ctx->H[5];
+ uint32_t g = ctx->H[6];
+ uint32_t h = ctx->H[7];
+
+ /* First increment the byte count. FIPS 180-2 specifies the possible
+ length of the file up to 2^64 bits. Here we only compute the
+ number of bytes. Do a double word increment. */
+ ctx->total[0] += len;
+ if (ctx->total[0] < len)
+ ++ctx->total[1];
+
+ /* Process all bytes in the buffer with 64 bytes in each round of
+ the loop. */
+ while (nwords > 0)
+ {
+ uint32_t W[64];
+ uint32_t a_save = a;
+ uint32_t b_save = b;
+ uint32_t c_save = c;
+ uint32_t d_save = d;
+ uint32_t e_save = e;
+ uint32_t f_save = f;
+ uint32_t g_save = g;
+ uint32_t h_save = h;
+
+ /* Operators defined in FIPS 180-2:4.1.2. */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22))
+#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25))
+#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3))
+#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10))
+
+ /* It is unfortunate that C does not provide an operator for
+ cyclic rotation. Hope the C compiler is smart enough. */
+#define CYCLIC(w, s) ((w >> s) | (w << (32 - s)))
+
+ /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */
+ for (unsigned int t = 0; t < 16; ++t)
+ {
+ W[t] = SWAP (*words);
+ ++words;
+ }
+ for (unsigned int t = 16; t < 64; ++t)
+ W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
+
+ /* The actual computation according to FIPS 180-2:6.2.2 step 3. */
+ for (unsigned int t = 0; t < 64; ++t)
+ {
+ uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
+ uint32_t T2 = S0 (a) + Maj (a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + T1;
+ d = c;
+ c = b;
+ b = a;
+ a = T1 + T2;
+ }
+
+ /* Add the starting values of the context according to FIPS 180-2:6.2.2
+ step 4. */
+ a += a_save;
+ b += b_save;
+ c += c_save;
+ d += d_save;
+ e += e_save;
+ f += f_save;
+ g += g_save;
+ h += h_save;
+
+ /* Prepare for the next round. */
+ nwords -= 16;
+ }
+
+ /* Put checksum in context given as argument. */
+ ctx->H[0] = a;
+ ctx->H[1] = b;
+ ctx->H[2] = c;
+ ctx->H[3] = d;
+ ctx->H[4] = e;
+ ctx->H[5] = f;
+ ctx->H[6] = g;
+ ctx->H[7] = h;
+}
+
+
+/* Initialize structure containing state of computation.
+ (FIPS 180-2:5.3.2) */
+static void
+sha256_init_ctx (struct sha256_ctx *ctx)
+{
+ ctx->H[0] = 0x6a09e667;
+ ctx->H[1] = 0xbb67ae85;
+ ctx->H[2] = 0x3c6ef372;
+ ctx->H[3] = 0xa54ff53a;
+ ctx->H[4] = 0x510e527f;
+ ctx->H[5] = 0x9b05688c;
+ ctx->H[6] = 0x1f83d9ab;
+ ctx->H[7] = 0x5be0cd19;
+
+ ctx->total[0] = ctx->total[1] = 0;
+ ctx->buflen = 0;
+}
+
+
+/* Process the remaining bytes in the internal buffer and the usual
+ prolog according to the standard and write the result to RESBUF.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+static void *
+sha256_finish_ctx (struct sha256_ctx *ctx, void *resbuf)
+{
+ /* Take yet unprocessed bytes into account. */
+ uint32_t bytes = ctx->buflen;
+ size_t pad;
+
+ /* Now count remaining bytes. */
+ ctx->total[0] += bytes;
+ if (ctx->total[0] < bytes)
+ ++ctx->total[1];
+
+ pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+ memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+ /* Put the 64-bit file length in *bits* at the end of the buffer. */
+ *(uint32_t *) &ctx->buffer[bytes + pad + 4] = SWAP (ctx->total[0] << 3);
+ *(uint32_t *) &ctx->buffer[bytes + pad] = SWAP ((ctx->total[1] << 3) |
+ (ctx->total[0] >> 29));
+
+ /* Process last bytes. */
+ sha256_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+ /* Put result from CTX in first 32 bytes following RESBUF. */
+ for (unsigned int i = 0; i < 8; ++i)
+ ((uint32_t *) resbuf)[i] = SWAP (ctx->H[i]);
+
+ return resbuf;
+}
+
+
+static void
+sha256_process_bytes (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+ /* When we already have some bits in our internal buffer concatenate
+ both inputs first. */
+ if (ctx->buflen != 0)
+ {
+ size_t left_over = ctx->buflen;
+ size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+ memcpy (&ctx->buffer[left_over], buffer, add);
+ ctx->buflen += add;
+
+ if (ctx->buflen > 64)
+ {
+ sha256_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
+
+ ctx->buflen &= 63;
+ /* The regions in the following copy operation cannot overlap. */
+ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+ ctx->buflen);
+ }
+
+ buffer = (const char *) buffer + add;
+ len -= add;
+ }
+
+ /* Process available complete blocks. */
+ if (len >= 64)
+ {
+/* To check alignment gcc has an appropriate operator. Other
+ compilers don't. */
+#if __GNUC__ >= 2
+# define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__ (uint32_t) != 0)
+#else
+# define UNALIGNED_P(p) (((uintptr_t) p) % sizeof (uint32_t) != 0)
+#endif
+ if (UNALIGNED_P (buffer))
+ while (len > 64)
+ {
+ sha256_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx);
+ buffer = (const char *) buffer + 64;
+ len -= 64;
+ }
+ else
+ {
+ sha256_process_block (buffer, len & ~63, ctx);
+ buffer = (const char *) buffer + (len & ~63);
+ len &= 63;
+ }
+ }
+
+ /* Move remaining bytes into internal buffer. */
+ if (len > 0)
+ {
+ size_t left_over = ctx->buflen;
+
+ memcpy (&ctx->buffer[left_over], buffer, len);
+ left_over += len;
+ if (left_over >= 64)
+ {
+ sha256_process_block (ctx->buffer, 64, ctx);
+ left_over -= 64;
+ memcpy (ctx->buffer, &ctx->buffer[64], left_over);
+ }
+ ctx->buflen = left_over;
+ }
+}
+
+
+/* Define our magic string to mark salt for SHA256 "encryption"
+ replacement. */
+static const char sha256_salt_prefix[] = "$5$";
+
+/* Prefix for optional rounds specification. */
+static const char sha256_rounds_prefix[] = "rounds=";
+
+/* Maximum salt string length. */
+#define SALT_LEN_MAX 16
+/* Default number of rounds if not explicitly specified. */
+#define ROUNDS_DEFAULT 5000
+/* Minimum number of rounds. */
+#define ROUNDS_MIN 1000
+/* Maximum number of rounds. */
+#define ROUNDS_MAX 999999999
+
+/* Table with characters for base64 transformation. */
+static const char b64t[64] =
+"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+static char *
+sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen)
+{
+ unsigned char alt_result[32]
+ __attribute__ ((__aligned__ (__alignof__ (uint32_t))));
+ unsigned char temp_result[32]
+ __attribute__ ((__aligned__ (__alignof__ (uint32_t))));
+ struct sha256_ctx ctx;
+ struct sha256_ctx alt_ctx;
+ size_t salt_len;
+ size_t key_len;
+ size_t cnt;
+ char *cp;
+ char *copied_key = NULL;
+ char *copied_salt = NULL;
+ char *p_bytes;
+ char *s_bytes;
+ /* Default number of rounds. */
+ size_t rounds = ROUNDS_DEFAULT;
+ bool rounds_custom = false;
+
+ /* Find beginning of salt string. The prefix should normally always
+ be present. Just in case it is not. */
+ if (strncmp (sha256_salt_prefix, salt, sizeof (sha256_salt_prefix) - 1) == 0)
+ /* Skip salt prefix. */
+ salt += sizeof (sha256_salt_prefix) - 1;
+
+ if (strncmp (salt, sha256_rounds_prefix, sizeof (sha256_rounds_prefix) - 1)
+ == 0)
+ {
+ const char *num = salt + sizeof (sha256_rounds_prefix) - 1;
+ char *endp;
+ unsigned long int srounds = strtoul (num, &endp, 10);
+ if (*endp == '$')
+ {
+ salt = endp + 1;
+ rounds = MAX (ROUNDS_MIN, MIN (srounds, ROUNDS_MAX));
+ rounds_custom = true;
+ }
+ }
+
+ salt_len = MIN (strcspn (salt, "$"), SALT_LEN_MAX);
+ key_len = strlen (key);
+
+ if ((key - (char *) 0) % __alignof__ (uint32_t) != 0)
+ {
+ char *tmp = (char *) alloca (key_len + __alignof__ (uint32_t));
+ key = copied_key =
+ memcpy (tmp + __alignof__ (uint32_t)
+ - (tmp - (char *) 0) % __alignof__ (uint32_t),
+ key, key_len);
+ }
+
+ if ((salt - (char *) 0) % __alignof__ (uint32_t) != 0)
+ {
+ char *tmp = (char *) alloca (salt_len + __alignof__ (uint32_t));
+ salt = copied_salt =
+ memcpy (tmp + __alignof__ (uint32_t)
+ - (tmp - (char *) 0) % __alignof__ (uint32_t),
+ salt, salt_len);
+ }
+
+ /* Prepare for the real work. */
+ sha256_init_ctx (&ctx);
+
+ /* Add the key string. */
+ sha256_process_bytes (key, key_len, &ctx);
+
+ /* The last part is the salt string. This must be at most 16
+ characters and it ends at the first `$' character (for
+ compatibility with existing implementations). */
+ sha256_process_bytes (salt, salt_len, &ctx);
+
+
+ /* Compute alternate SHA256 sum with input KEY, SALT, and KEY. The
+ final result will be added to the first context. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* Add key. */
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Add salt. */
+ sha256_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Add key again. */
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Now get result of this (32 bytes) and add it to the other
+ context. */
+ sha256_finish_ctx (&alt_ctx, alt_result);
+
+ /* Add for any character in the key one byte of the alternate sum. */
+ for (cnt = key_len; cnt > 32; cnt -= 32)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ sha256_process_bytes (alt_result, cnt, &ctx);
+
+ /* Take the binary representation of the length of the key and for every
+ 1 add the alternate sum, for every 0 the key. */
+ for (cnt = key_len; cnt > 0; cnt >>= 1)
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ else
+ sha256_process_bytes (key, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha256_finish_ctx (&ctx, alt_result);
+
+ /* Start computation of P byte sequence. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < key_len; ++cnt)
+ sha256_process_bytes (key, key_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha256_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence P. */
+ cp = p_bytes = alloca (key_len);
+ for (cnt = key_len; cnt >= 32; cnt -= 32)
+ cp = mempcpy (cp, temp_result, 32);
+ memcpy (cp, temp_result, cnt);
+
+ /* Start computation of S byte sequence. */
+ sha256_init_ctx (&alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
+ sha256_process_bytes (salt, salt_len, &alt_ctx);
+
+ /* Finish the digest. */
+ sha256_finish_ctx (&alt_ctx, temp_result);
+
+ /* Create byte sequence S. */
+ cp = s_bytes = alloca (salt_len);
+ for (cnt = salt_len; cnt >= 32; cnt -= 32)
+ cp = mempcpy (cp, temp_result, 32);
+ memcpy (cp, temp_result, cnt);
+
+ /* Repeatedly run the collected hash value through SHA256 to burn
+ CPU cycles. */
+ for (cnt = 0; cnt < rounds; ++cnt)
+ {
+ /* New context. */
+ sha256_init_ctx (&ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+ else
+ sha256_process_bytes (alt_result, 32, &ctx);
+
+ /* Add salt for numbers not divisible by 3. */
+ if (cnt % 3 != 0)
+ sha256_process_bytes (s_bytes, salt_len, &ctx);
+
+ /* Add key for numbers not divisible by 7. */
+ if (cnt % 7 != 0)
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0)
+ sha256_process_bytes (alt_result, 32, &ctx);
+ else
+ sha256_process_bytes (p_bytes, key_len, &ctx);
+
+ /* Create intermediate result. */
+ sha256_finish_ctx (&ctx, alt_result);
+ }
+
+ /* Now we can construct the result string. It consists of three
+ parts. */
+ cp = stpncpy (buffer, sha256_salt_prefix, MAX (0, buflen));
+ buflen -= sizeof (sha256_salt_prefix) - 1;
+
+ if (rounds_custom)
+ {
+ int n = snprintf (cp, MAX (0, buflen), "%s%zu$",
+ sha256_rounds_prefix, rounds);
+ cp += n;
+ buflen -= n;
+ }
+
+ cp = stpncpy (cp, salt, MIN ((size_t) MAX (0, buflen), salt_len));
+ buflen -= MIN ((size_t) MAX (0, buflen), salt_len);
+
+ if (buflen > 0)
+ {
+ *cp++ = '$';
+ --buflen;
+ }
+
+#define b64_from_24bit(B2, B1, B0, N) \
+ do { \
+ unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+ int n = (N); \
+ while (n-- > 0 && buflen > 0) \
+ { \
+ *cp++ = b64t[w & 0x3f]; \
+ --buflen; \
+ w >>= 6; \
+ } \
+ } while (0)
+
+ b64_from_24bit (alt_result[0], alt_result[10], alt_result[20], 4);
+ b64_from_24bit (alt_result[21], alt_result[1], alt_result[11], 4);
+ b64_from_24bit (alt_result[12], alt_result[22], alt_result[2], 4);
+ b64_from_24bit (alt_result[3], alt_result[13], alt_result[23], 4);
+ b64_from_24bit (alt_result[24], alt_result[4], alt_result[14], 4);
+ b64_from_24bit (alt_result[15], alt_result[25], alt_result[5], 4);
+ b64_from_24bit (alt_result[6], alt_result[16], alt_result[26], 4);
+ b64_from_24bit (alt_result[27], alt_result[7], alt_result[17], 4);
+ b64_from_24bit (alt_result[18], alt_result[28], alt_result[8], 4);
+ b64_from_24bit (alt_result[9], alt_result[19], alt_result[29], 4);
+ b64_from_24bit (0, alt_result[31], alt_result[30], 3);
+ if (buflen <= 0)
+ {
+ errno = ERANGE;
+ buffer = NULL;
+ }
+ else
+ *cp = '\0'; /* Terminate the string. */
+
+ /* Clear the buffer for the intermediate result so that people
+ attaching to processes or reading core dumps cannot get any
+ information. We do it in this way to clear correct_words[]
+ inside the SHA256 implementation as well. */
+ sha256_init_ctx (&ctx);
+ sha256_finish_ctx (&ctx, alt_result);
+ memset (temp_result, '\0', sizeof (temp_result));
+ memset (p_bytes, '\0', key_len);
+ memset (s_bytes, '\0', salt_len);
+ memset (&ctx, '\0', sizeof (ctx));
+ memset (&alt_ctx, '\0', sizeof (alt_ctx));
+ if (copied_key != NULL)
+ memset (copied_key, '\0', key_len);
+ if (copied_salt != NULL)
+ memset (copied_salt, '\0', salt_len);
+
+ return buffer;
+}
+
+
+/* This entry point is equivalent to the `crypt' function in Unix
+ libcs. */
+char *
+sha256_crypt (const char *key, const char *salt)
+{
+ /* We don't want to have an arbitrary limit in the size of the
+ password. We can compute an upper bound for the size of the
+ result in advance and so we can prepare the buffer we pass to
+ `sha256_crypt_r'. */
+ static char *buffer;
+ static int buflen;
+ int needed = (sizeof (sha256_salt_prefix) - 1
+ + sizeof (sha256_rounds_prefix) + 9 + 1
+ + strlen (salt) + 1 + 43 + 1);
+
+ if (buflen < needed)
+ {
+ char *new_buffer = (char *) realloc (buffer, needed);
+ if (new_buffer == NULL)
+ return NULL;
+
+ buffer = new_buffer;
+ buflen = needed;
+ }
+
+ return sha256_crypt_r (key, salt, buffer, buflen);
+}
+
+
+#ifdef TEST
+static const struct
+{
+ const char *input;
+ const char result[32];
+} tests[] =
+ {
+ /* Test vectors from FIPS 180-2: appendix B.1. */
+ { "abc",
+ "\xba\x78\x16\xbf\x8f\x01\xcf\xea\x41\x41\x40\xde\x5d\xae\x22\x23"
+ "\xb0\x03\x61\xa3\x96\x17\x7a\x9c\xb4\x10\xff\x61\xf2\x00\x15\xad" },
+ /* Test vectors from FIPS 180-2: appendix B.2. */
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "\x24\x8d\x6a\x61\xd2\x06\x38\xb8\xe5\xc0\x26\x93\x0c\x3e\x60\x39"
+ "\xa3\x3c\xe4\x59\x64\xff\x21\x67\xf6\xec\xed\xd4\x19\xdb\x06\xc1" },
+ /* Test vectors from the NESSIE project. */
+ { "",
+ "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24"
+ "\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55" },
+ { "a",
+ "\xca\x97\x81\x12\xca\x1b\xbd\xca\xfa\xc2\x31\xb3\x9a\x23\xdc\x4d"
+ "\xa7\x86\xef\xf8\x14\x7c\x4e\x72\xb9\x80\x77\x85\xaf\xee\x48\xbb" },
+ { "message digest",
+ "\xf7\x84\x6f\x55\xcf\x23\xe1\x4e\xeb\xea\xb5\xb4\xe1\x55\x0c\xad"
+ "\x5b\x50\x9e\x33\x48\xfb\xc4\xef\xa3\xa1\x41\x3d\x39\x3c\xb6\x50" },
+ { "abcdefghijklmnopqrstuvwxyz",
+ "\x71\xc4\x80\xdf\x93\xd6\xae\x2f\x1e\xfa\xd1\x44\x7c\x66\xc9\x52"
+ "\x5e\x31\x62\x18\xcf\x51\xfc\x8d\x9e\xd8\x32\xf2\xda\xf1\x8b\x73" },
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "\x24\x8d\x6a\x61\xd2\x06\x38\xb8\xe5\xc0\x26\x93\x0c\x3e\x60\x39"
+ "\xa3\x3c\xe4\x59\x64\xff\x21\x67\xf6\xec\xed\xd4\x19\xdb\x06\xc1" },
+ { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "\xdb\x4b\xfc\xbd\x4d\xa0\xcd\x85\xa6\x0c\x3c\x37\xd3\xfb\xd8\x80"
+ "\x5c\x77\xf1\x5f\xc6\xb1\xfd\xfe\x61\x4e\xe0\xa7\xc8\xfd\xb4\xc0" },
+ { "123456789012345678901234567890123456789012345678901234567890"
+ "12345678901234567890",
+ "\xf3\x71\xbc\x4a\x31\x1f\x2b\x00\x9e\xef\x95\x2d\xd8\x3c\xa8\x0e"
+ "\x2b\x60\x02\x6c\x8e\x93\x55\x92\xd0\xf9\xc3\x08\x45\x3c\x81\x3e" }
+ };
+#define ntests (sizeof (tests) / sizeof (tests[0]))
+
+
+static const struct
+{
+ const char *salt;
+ const char *input;
+ const char *expected;
+} tests2[] =
+{
+ { "$5$saltstring", "Hello world!",
+ "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" },
+ { "$5$rounds=10000$saltstringsaltstring", "Hello world!",
+ "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2."
+ "opqey6IcA" },
+ { "$5$rounds=5000$toolongsaltstring", "This is just a test",
+ "$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8"
+ "mGRcvxa5" },
+ { "$5$rounds=1400$anotherlongsaltstring",
+ "a very much longer text to encrypt. This one even stretches over more"
+ "than one line.",
+ "$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12"
+ "oP84Bnq1" },
+ { "$5$rounds=77777$short",
+ "we have a short salt string but not a short password",
+ "$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" },
+ { "$5$rounds=123456$asaltof16chars..", "a short string",
+ "$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/"
+ "cZKmF/wJvD" },
+ { "$5$rounds=10$roundstoolow", "the minimum number is still observed",
+ "$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL97"
+ "2bIC" },
+};
+#define ntests2 (sizeof (tests2) / sizeof (tests2[0]))
+
+
+int
+main (void)
+{
+ struct sha256_ctx ctx;
+ char sum[32];
+ int result = 0;
+ int cnt;
+
+ for (cnt = 0; cnt < (int) ntests; ++cnt)
+ {
+ sha256_init_ctx (&ctx);
+ sha256_process_bytes (tests[cnt].input, strlen (tests[cnt].input), &ctx);
+ sha256_finish_ctx (&ctx, sum);
+ if (memcmp (tests[cnt].result, sum, 32) != 0)
+ {
+ printf ("test %d run %d failed\n", cnt, 1);
+ result = 1;
+ }
+
+ sha256_init_ctx (&ctx);
+ for (int i = 0; tests[cnt].input[i] != '\0'; ++i)
+ sha256_process_bytes (&tests[cnt].input[i], 1, &ctx);
+ sha256_finish_ctx (&ctx, sum);
+ if (memcmp (tests[cnt].result, sum, 32) != 0)
+ {
+ printf ("test %d run %d failed\n", cnt, 2);
+ result = 1;
+ }
+ }
+
+ /* Test vector from FIPS 180-2: appendix B.3. */
+ char buf[1000];
+ memset (buf, 'a', sizeof (buf));
+ sha256_init_ctx (&ctx);
+ for (int i = 0; i < 1000; ++i)
+ sha256_process_bytes (buf, sizeof (buf), &ctx);
+ sha256_finish_ctx (&ctx, sum);
+ static const char expected[32] =
+ "\xcd\xc7\x6e\x5c\x99\x14\xfb\x92\x81\xa1\xc7\xe2\x84\xd7\x3e\x67"
+ "\xf1\x80\x9a\x48\xa4\x97\x20\x0e\x04\x6d\x39\xcc\xc7\x11\x2c\xd0";
+ if (memcmp (expected, sum, 32) != 0)
+ {
+ printf ("test %d failed\n", cnt);
+ result = 1;
+ }
+
+ for (cnt = 0; cnt < ntests2; ++cnt)
+ {
+ char *cp = sha256_crypt (tests2[cnt].input, tests2[cnt].salt);
+
+ if (strcmp (cp, tests2[cnt].expected) != 0)
+ {
+ printf ("test %d: expected \"%s\", got \"%s\"\n",
+ cnt, tests2[cnt].expected, cp);
+ result = 1;
+ }
+ }
+
+ if (result == 0)
+ puts ("all tests OK");
+
+ return result;
+}
+#endif
diff --git a/src/main.cpp b/src/main.cpp
index 985b139589..bd21a42d5c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -294,6 +294,7 @@ void ProxySQL_Main_init_SSL_module() {
}
//SSL_CTX_set_options(GloVars.global.ssl_ctx, SSL_OP_NO_SSLv3); // no necessary, because of previous SSL_CTX_set_min_proto_version
#ifdef DEBUG
+#if 0
{
STACK_OF(SSL_CIPHER) *ciphers;
ciphers = SSL_CTX_get_ciphers(GloVars.global.ssl_ctx);
@@ -308,7 +309,8 @@ void ProxySQL_Main_init_SSL_module() {
}
fprintf(stderr,"\n");
}
-#endif
+#endif // 0
+#endif // DEBUG
std::string msg = "";
ProxySQL_create_or_load_TLS(true, msg);
}
diff --git a/test/tap/tap/tap.cpp b/test/tap/tap/tap.cpp
index 4369bddf60..8afb7939cd 100644
--- a/test/tap/tap/tap.cpp
+++ b/test/tap/tap/tap.cpp
@@ -87,7 +87,7 @@ vemit_tap(int pass, char const *fmt, va_list ap)
{
fprintf(tapout, "%sok %d%s",
pass ? "" : "not ",
- ++g_test.last,
+ __sync_add_and_fetch(&g_test.last, 1),
(fmt && *fmt) ? " - " : "");
if (fmt && *fmt)
vfprintf(tapout, fmt, ap);
@@ -248,7 +248,7 @@ ok(int pass, char const *fmt, ...)
va_start(ap, fmt);
if (!pass && *g_test.todo == '\0')
- ++g_test.failed;
+ __sync_add_and_fetch(&g_test.failed, 1);
vemit_tap(pass, fmt, ap);
va_end(ap);
@@ -265,7 +265,7 @@ ok1(int const pass)
memset(&ap, 0, sizeof(ap));
if (!pass && *g_test.todo == '\0')
- ++g_test.failed;
+ __sync_add_and_fetch(&g_test.failed, 1);
vemit_tap(pass, NULL, ap);
diff --git a/test/tap/tests/reg_test_3504-change_user-t.cpp b/test/tap/tests/reg_test_3504-change_user-t.cpp
index 3e839ee804..29b0bcb1fb 100644
--- a/test/tap/tests/reg_test_3504-change_user-t.cpp
+++ b/test/tap/tests/reg_test_3504-change_user-t.cpp
@@ -106,11 +106,11 @@ void perform_helper_test(
act_SSL_val = output_res.at("ssl_enabled");
if (auth == "mysql_clear_password") {
- exp_switching_auth_type = 0;
+ exp_switching_auth_type = -1;
} else if (auth == "mysql_native_password") {
- exp_switching_auth_type = 0;
+ exp_switching_auth_type = -1;
} else if (auth == "caching_sha2_password") {
- exp_switching_auth_type = 1;
+ exp_switching_auth_type = 0;
}
act_ch_usernames.push_back(output_res.at("client_com_change_user_1"));
diff --git a/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp b/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp
index 05a2e08e47..70d6106819 100644
--- a/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp
+++ b/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp
@@ -118,7 +118,7 @@ int main(int argc, char** argv) {
ok(
failed_to_connect,
- "An invalid user should fail to connect to SQLite3 server, error was: %s",
+ "A valid user with incorrect password should fail to connect to SQLite3 server, error was: %s",
inv_pass_err.c_str()
);
diff --git a/test/tap/tests/set_testing-240-t.cpp b/test/tap/tests/set_testing-240-t.cpp
index f69b59cfda..cacf8c0052 100644
--- a/test/tap/tests/set_testing-240-t.cpp
+++ b/test/tap/tests/set_testing-240-t.cpp
@@ -499,10 +499,12 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
+/* admin-hash_passwords has been deprecated
diag("Disabling admin-hash_passwords to be able to run test on MySQL 8");
MYSQL_QUERY(proxysql_admin, "SET admin-hash_passwords='false'");
MYSQL_QUERY(proxysql_admin, "LOAD ADMIN VARIABLES TO RUNTIME");
MYSQL_QUERY(proxysql_admin, "LOAD MYSQL USERS TO RUNTIME");
+*/
// find all reader host groups
MYSQL_QUERY(proxysql_admin, "DELETE FROM mysql_servers WHERE hostgroup_id = 101");
diff --git a/test/tap/tests/test_auth_methods-t.cpp b/test/tap/tests/test_auth_methods-t.cpp
new file mode 100644
index 0000000000..d5b0c86023
--- /dev/null
+++ b/test/tap/tests/test_auth_methods-t.cpp
@@ -0,0 +1,1886 @@
+/**
+ * @file test_auth_methods-t.cpp
+ * @brief Tests the different authentications methods supported by ProxySQL.
+ * @details The exhaust all possible combinations of the following supported auths:
+ *
+ * - 'clear_text_pass'
+ * - 'mysql_native_password'
+ * - 'caching_sha2_password'
+ *
+ * Checks take into account the following potential scenarios:
+ *
+ * - Check for invalid passwords/users auth attempts.
+ * - Check of correcteness for:
+ * - Supported combinations of 'default_auth', 'requested_auth' and 'stored_pass'.
+ * - Number of auth switchs requested by ProxySQL.
+ * - Number of 'caching_sha2_password' full auths requested by ProxySQL.
+ * - Check for expected failures in concurrent conns to non-warmup ProxySQL (no cached clear_text_pass).
+ * - Check for expected successes in concurrent conns to non-warmup ProxySQL (no cached clear_text_pass).
+ * - Check for correct concurrent clear_text_pass caching ('caching_sha2_password').
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "proxysql_utils.h"
+#include "openssl/ssl.h"
+#include "mysql.h"
+
+#include "tap.h"
+#include "command_line.h"
+#include "utils.h"
+
+// Additional env variables
+uint32_t TAP_MYSQL8_BACKEND_HG = 30;
+uint32_t TAP_NUM_CLIENT_THREADS = 4;
+
+using std::pair;
+using std::string;
+using std::vector;
+using std::function;
+using std::unique_ptr;
+
+#define MYSQL_QUERY_T__(mysql, query) \
+ do { \
+ const std::string time { get_formatted_time() }; \
+ fprintf(stderr, "# %s: Issuing query '%s' to ('%s':%d)\n", time.c_str(), query, mysql->host, mysql->port); \
+ if (mysql_query(mysql, query)) { \
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); \
+ return { EXIT_FAILURE, vector {} }; \
+ } \
+ } while(0)
+
+#define MYSQL_QUERY_T_(mysql, query) \
+ do { \
+ const std::string time { get_formatted_time() }; \
+ fprintf(stderr, "# %s: Issuing query '%s' to ('%s':%d)\n", time.c_str(), query, mysql->host, mysql->port); \
+ if (mysql_query(mysql, query)) { \
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); \
+ return { EXIT_FAILURE, user_def_t {} }; \
+ } \
+ } while(0)
+
+/**
+ * @brief TODO: Currently unused; move to utilities.
+ */
+string my_escape_string(MYSQL* mysql, const string& q) {
+ size_t size = q.size();
+
+ if (size <= 0) {
+ return {};
+ } else {
+ std::unique_ptr buf(new char[size * 2 + 1]);
+ int res = mysql_real_escape_string(mysql, buf.get(), q.c_str(), q.size());
+ return string(buf.get(), buf.get() + res);
+ }
+}
+
+bool is_empty_pass(const char* pass) {
+ return pass == nullptr || (pass && strlen(pass) == 0);
+}
+
+bool match_pass(const char* p1, const char* p2) {
+ if (is_empty_pass(p1) && is_empty_pass(p2)) {
+ return true;
+ } else if (!is_empty_pass(p1) && !is_empty_pass(p2)) {
+ return strcmp(p1, p2) == 0;
+ } else {
+ return false;
+ }
+}
+
+// TODO: Refactor
+///////////////////////////////////////////////////////////////////////////////
+
+int get_query_result(MYSQL* mysql, const string& query, uint64_t& out_val) {
+ int rc = mysql_query(mysql, query.c_str());
+ if (rc != EXIT_SUCCESS) {
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
+ return EXIT_FAILURE;
+ }
+
+ MYSQL_RES* myres = mysql_store_result(mysql);
+ if (myres == nullptr) {
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
+ return EXIT_FAILURE;
+ }
+
+ MYSQL_ROW row = mysql_fetch_row(myres);
+ if (row == nullptr || row[0] == nullptr) {
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Received empty row");
+ return EXIT_FAILURE;
+ }
+
+ out_val = std::stol(row[0]);
+
+ mysql_free_result(myres);
+
+ return EXIT_SUCCESS;
+}
+
+int wait_target_backend_conns(MYSQL* admin, uint32_t tg_conns, uint32_t timeout, int32_t hg=-1) {
+ const string query_select { "SELECT SUM(ConnFree + ConnUsed) FROM stats_mysql_connection_pool" };
+ const string query_where { hg == -1 ? "" : " WHERE hostgroup=" + std::to_string(hg) };
+ const string query { query_select + query_where };
+
+ uint32_t waited = 0;
+
+ while (waited < timeout) {
+ uint64_t conns_count = 0;
+ int q_res = get_query_result(admin, query.c_str(), conns_count);
+
+ if (q_res != EXIT_SUCCESS) {
+ diag("Failed getting conn stats query:`%s`,error:`%s`", query.c_str(), mysql_error(admin));
+ return -1;
+ }
+
+ if (conns_count == tg_conns) {
+ diag("Reached target conn count tg_conns:'%d',conns_count:'%ld'", tg_conns, conns_count);
+ break;
+ } else {
+ waited += 1;
+ diag(
+ "Conn count yet unmatched tg_conns:'%d',conns_count:'%ld',checks:'%u'",
+ tg_conns, conns_count, waited
+ );
+ sleep(1);
+ }
+ }
+
+ return waited < timeout ? 0 : -2;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+std::string unhex(const std::string& hex) {
+ if (hex.size() % 2) { return {}; };
+
+ string result {};
+
+ for (size_t i = 0; i < hex.size() - 1; i += 2) {
+ string hex_char { string { hex[i] } + hex[i+1] };
+ uint64_t char_val { 0 };
+
+ std::istringstream stream { hex_char };
+ stream >> std::hex >> char_val;
+
+ result += string { static_cast(char_val) };
+ }
+
+ return result;
+}
+
+std::string hex(const std::string& str) {
+ std::ostringstream hex_stream;
+
+ for (unsigned char c : str) {
+ hex_stream << std::hex << std::setfill('0') << std::setw(2) <<
+ std::uppercase << static_cast(c);
+ }
+
+ return hex_stream.str();
+}
+
+struct free_deleter {
+ void operator()(void* x) { free(x); }
+ void operator()(const void* x) { free(const_cast(x)); }
+};
+
+template
+using mf_unique_ptr = std::unique_ptr;
+
+mf_unique_ptr MF_CHAR_(const char* s) {
+ return mf_unique_ptr { s ? strdup(s) : nullptr };
+}
+
+struct user_def_t {
+ string name {};
+ mf_unique_ptr prim_pass { nullptr };
+ mf_unique_ptr addl_pass { nullptr };
+ string auth {};
+ bool rand_pass {};
+
+ user_def_t() {}
+ user_def_t(const string& name_) : name(name_) {}
+
+ user_def_t(
+ const string& name_,
+ mf_unique_ptr prim_pass_,
+ mf_unique_ptr addl_pass_,
+ const string& auth_,
+ bool rand_pass_ = false
+ ) :
+ name(name_),
+ prim_pass(std::move(prim_pass_)),
+ addl_pass(std::move(addl_pass_)),
+ auth(auth_),
+ rand_pass(rand_pass_)
+ {}
+
+ user_def_t(const user_def_t& other) :
+ name(other.name), auth(other.auth), rand_pass(other.rand_pass)
+ {
+ if (other.prim_pass) {
+ this->prim_pass = mf_unique_ptr(strdup(other.prim_pass.get()));
+ } else {
+ this->prim_pass = nullptr;
+ }
+ if (other.addl_pass) {
+ this->addl_pass = mf_unique_ptr(strdup(other.addl_pass.get()));
+ } else {
+ this->addl_pass = nullptr;
+ }
+ }
+
+ void operator=(const user_def_t& other) {
+ this->name = other.name;
+ this->auth = other.auth;
+ this->rand_pass = other.rand_pass;
+
+ if (other.prim_pass) {
+ this->prim_pass = mf_unique_ptr(strdup(other.prim_pass.get()));
+ } else {
+ this->prim_pass = nullptr;
+ }
+ if (other.addl_pass) {
+ this->addl_pass = mf_unique_ptr(strdup(other.addl_pass.get()));
+ } else {
+ this->addl_pass = nullptr;
+ }
+ }
+
+ user_def_t(user_def_t&& other) :
+ name(other.name),
+ auth(other.auth),
+ rand_pass(other.rand_pass),
+ prim_pass(std::move(other.prim_pass)),
+ addl_pass(std::move(other.addl_pass))
+ {}
+
+ void operator=(user_def_t&& other) {
+ this->name = other.name;
+ this->auth = other.auth;
+ this->rand_pass = other.rand_pass;
+ this->prim_pass = std::move(other.prim_pass);
+ this->addl_pass = std::move(other.addl_pass);
+ }
+};
+
+struct user_auth_stats_t {
+ user_def_t user_def;
+ uint64_t prim_pass_auths;
+ uint64_t addl_pass_auths;
+ uint64_t full_sha2_auths;
+};
+
+using auth_reg_t = std::unordered_map;
+
+struct user_creds_t {
+ user_def_t user_def;
+ mf_unique_ptr hashed_prim_pass_bin;
+ mf_unique_ptr hashed_addl_pass_bin;
+
+ user_creds_t(const user_creds_t&) = delete;
+ user_creds_t(user_creds_t&&) noexcept(false) = default;
+
+ void operator=(user_creds_t&& other) {
+ this->user_def = std::move(other.user_def);
+ this->hashed_prim_pass_bin = std::move(other.hashed_prim_pass_bin);
+ this->hashed_addl_pass_bin = std::move(other.hashed_addl_pass_bin);
+ }
+};
+
+/**
+ * @brief Extracts a nested JSON element from the supplied path.
+ * @param j The JSON from which to extract the element.
+ * @param p The path in which the element should be found.
+ * @return Pointer to the JSON element if found, 'nullptr' otherwise.
+ */
+const nlohmann::json* get_nested_elem(const nlohmann::json& j, const vector& p) {
+ if (j.is_discarded()) { return nullptr; }
+ const nlohmann::json* next_step = &j;
+
+ for (const auto& e : p) {
+ if (next_step->contains(e)) {
+ next_step = &next_step->at(e);
+ } else {
+ next_step = nullptr;
+ break;
+ }
+ }
+
+ return next_step;
+}
+
+template
+T get_nested_elem_val(const nlohmann::json& j, const std::vector& p, const T def_val) {
+ const nlohmann::json* next_step = get_nested_elem(j, p);
+
+ try {
+ if (next_step != nullptr && !next_step->is_null()) {
+ return next_step->get();
+ } else {
+ return def_val;
+ }
+ } catch (std::exception&) {
+ return def_val;
+ }
+}
+
+struct test_conf_t {
+ /* @brief Default auth method announced by ProxySQL */
+ string def_auth;
+ /* @brief Auth method requested by client */
+ string req_auth;
+ /* @brief Wether to use hashed or 'clear_text' passwords. Implies a reload of 'mysql_users'. */
+ bool hashed_pass;
+ /* @brief Wether to attempt auth under SSL conn or not. */
+ bool use_ssl;
+};
+
+struct sess_info_t {
+ int sent_pkts;
+ int recv_pkts;
+ int switching_auth_sent;
+ int full_sha2_auth;
+};
+
+struct PASS_TYPE {
+ enum E {
+ UNKNOWN = 0,
+ PRIMARY,
+ ADDITIONAL,
+ };
+};
+
+/**
+ * @brief Info from user defs (user_def_t), extracted for building 'test_creds_t'.
+ */
+struct creds_info_t {
+ PASS_TYPE::E type;
+ string auth;
+};
+
+/**
+ * @brief Info about user creds used in a particular test case.
+ * @details Multiple 'test_creds_t' are used while testing a single 'test_conf_t'.
+ */
+struct test_creds_t {
+ string name {};
+ mf_unique_ptr pass { nullptr };
+ creds_info_t info {};
+
+ test_creds_t(const string& name_, mf_unique_ptr pass_) : name(name_), pass(std::move(pass_)) {}
+ test_creds_t(const test_creds_t& other) : name(other.name), info(other.info) {
+ this->pass = other.pass ? MF_CHAR_(other.pass.get()) : nullptr;
+ }
+};
+
+sess_info_t ext_sess_info(MYSQL* proxy) {
+ sess_info_t sess_info { -3, -3, -3 };
+ nlohmann::json session { fetch_internal_session(proxy) };
+
+ if (session.is_array() && !session.empty()) {
+ session = session[0];
+
+ sess_info.switching_auth_sent = get_nested_elem_val(session, {"client", "switching_auth_sent"}, -3);
+ sess_info.recv_pkts = get_nested_elem_val(session, {"client", "stream", "pkts_recv"}, -3);
+ sess_info.sent_pkts = get_nested_elem_val(session, {"client", "stream", "pkts_sent"}, -3);
+ }
+
+ return sess_info;
+}
+
+auth_reg_t create_auth_reg(const vector& users_creds) {
+ auth_reg_t auth_reg {};
+
+ for (const user_creds_t& creds : users_creds) {
+ auth_reg.insert({ creds.user_def.name, { creds.user_def, 0, 0, 0 } });
+ }
+
+ return auth_reg;
+}
+
+using chk_exp_scs_t = function;
+using chk_exp_seq_scs_t = function;
+
+/**
+ * @brief Extract the auth strings (prim/addl) pass from an existing user.
+ * @param mysql Opened MySQL conn in which to perform the queries.
+ * @param user_def User definition; used to match by username.
+ * @return A pair of kind `{err_code, user_creds}`.
+ */
+pair ext_user_auth_strs(MYSQL* mysql, const user_def_t& user_def) {
+ const char* addl_pass { user_def.addl_pass.get() };
+ const char* prim_pass { user_def.prim_pass.get() };
+
+ pair p_creds_res { EXIT_SUCCESS, user_creds_t {} };
+
+ const string ext_auths_query {
+ "SELECT HEX(authentication_string),json_value(user_attributes, '$.additional_password') "
+ "FROM mysql.user WHERE user='" + user_def.name + "'"
+ };
+
+ if (mysql_query_t(mysql, ext_auths_query.c_str())) {
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
+ return { EXIT_FAILURE, user_creds_t {} };
+ }
+
+ MYSQL_RES* myres = mysql_store_result(mysql);
+ MYSQL_ROW myrow = mysql_fetch_row(myres);
+
+ if (myrow && myrow[0]) {
+ const char* p_addl_pass { myrow[1] };
+
+ if (p_addl_pass) {
+ p_creds_res = {
+ EXIT_SUCCESS,
+ user_creds_t {
+ user_def,
+ mf_unique_ptr { strdup(myrow[0]) },
+ mf_unique_ptr { strdup(p_addl_pass) }
+ }
+ };
+ } else {
+ // MySQL wont allow that 'aditional' pass for a backend user; we don't verify user attributes
+ // right now when loading to runtime; but we wont allow user to connect with empty
+ // 'additional' passwords either;
+ if (addl_pass && strlen(addl_pass) == 0) {
+ p_creds_res = {
+ EXIT_SUCCESS,
+ user_creds_t {
+ user_def,
+ mf_unique_ptr { strdup(myrow[0]) },
+ mf_unique_ptr { strdup("") },
+ }
+ };
+ } else {
+ p_creds_res = {
+ EXIT_SUCCESS,
+ user_creds_t {
+ user_def,
+ mf_unique_ptr { strdup(myrow[0]) },
+ mf_unique_ptr { nullptr },
+ }
+ };
+ }
+ }
+ } else {
+ diag("Empty result; no auth_string found for user user:'%s'", user_def.name.c_str());
+ }
+
+ mysql_free_result(myres);
+
+ return p_creds_res;
+}
+
+/**
+ * @brief TODO: Limitation ProxySQL doesn't allow a clear-text pass to start with '*'. See #1762.
+ * @param pass Password to check.
+ * @return 'true' if password is an invalid clear-text pass for ProxySQL.
+ */
+bool chk_inv_pass(const char* pass) {
+ if (is_empty_pass(pass)) {
+ return true;
+ } else {
+ if (strlen(pass)) {
+ return pass[0] == '*';
+ } else {
+ return false;
+ }
+ }
+}
+
+pair create_mysql_user_rnd_creds(MYSQL* mysql, const user_def_t& user_def) {
+ const string CREATE_USER {
+ "CREATE USER '" + user_def.name + "'@'%' IDENTIFIED WITH"
+ " '" + user_def.auth + "' BY RANDOM PASSWORD"
+ };
+ const string DROP_USER { "DROP USER IF EXISTS '" + user_def.name + "'"};
+
+ mf_unique_ptr addl_text_pass { nullptr };
+
+ {
+ // NOTE: Required due to potential pass recreation
+ MYSQL_QUERY_T_(mysql, DROP_USER.c_str());
+ MYSQL_QUERY_T_(mysql, CREATE_USER.c_str());
+
+ MYSQL_RES* myres = mysql_store_result(mysql);
+ MYSQL_ROW myrow = mysql_fetch_row(myres);
+
+ if (myrow && myrow[2]) {
+ addl_text_pass = mf_unique_ptr(strdup(myrow[2]));
+ }
+
+ mysql_free_result(myres);
+ }
+
+ const string ALTER_USER {
+ "ALTER USER '" + user_def.name + "'@'%' IDENTIFIED WITH"
+ " '" + user_def.auth + "' BY RANDOM PASSWORD RETAIN CURRENT PASSWORD"
+ };
+
+ mf_unique_ptr prim_text_pass {};
+
+ {
+ MYSQL_QUERY_T_(mysql, ALTER_USER.c_str());
+
+ MYSQL_RES* myres = mysql_store_result(mysql);
+ MYSQL_ROW myrow = mysql_fetch_row(myres);
+
+ if (myrow && myrow[2]) {
+ prim_text_pass = mf_unique_ptr(strdup(myrow[2]));
+ }
+
+ mysql_free_result(myres);
+ }
+
+ return {
+ EXIT_SUCCESS,
+ user_def_t {
+ user_def.name,
+ std::move(prim_text_pass),
+ std::move(addl_text_pass),
+ user_def.auth,
+ user_def.rand_pass
+ }
+ };
+}
+
+pair create_mysql_user_exp_creds(MYSQL* mysql, const user_def_t& user_def) {
+ const char* addl_pass { user_def.addl_pass.get() };
+ const char* prim_pass { user_def.prim_pass.get() };
+
+ if (addl_pass && strlen(addl_pass)) {
+ const string CREATE_USER {
+ "CREATE USER '" + user_def.name + "'@'%' IDENTIFIED WITH"
+ " '" + user_def.auth + "' BY '" + user_def.addl_pass.get() + "'"
+ };
+ const string GRANT_USER_PRIVS { "GRANT ALL on *.* to '" + user_def.name + "'@'%'" };
+
+ MYSQL_QUERY_T_(mysql, CREATE_USER.c_str());
+ MYSQL_QUERY_T_(mysql, GRANT_USER_PRIVS.c_str());
+
+ if (prim_pass && strlen(prim_pass)) {
+ const string ALTER_USER_RETAIN {
+ "ALTER USER '" + user_def.name + "'@'%' IDENTIFIED BY '" + prim_pass + "'"
+ " RETAIN CURRENT PASSWORD"
+ };
+ MYSQL_QUERY_T_(mysql, ALTER_USER_RETAIN.c_str());
+ } else {
+ const string ALTER_USER_NO_RETAIN {
+ "ALTER USER '" + user_def.name + "'@'%' IDENTIFIED BY ''"
+ };
+ // When new password is empty; retaining the previous one isn't possible
+ MYSQL_QUERY_T_(mysql, ALTER_USER_NO_RETAIN.c_str());
+ }
+ } else {
+ string CREATE_USER { "CREATE USER '" + user_def.name + "'@'%'" };
+ string GRANT_USER_PRIVS { "GRANT ALL on *.* to '" + user_def.name + "'@'%'" };
+
+ if (prim_pass) {
+ CREATE_USER += " IDENTIFIED WITH '" + user_def.auth + "' BY '" + prim_pass + "'";
+ }
+
+ MYSQL_QUERY_T_(mysql, CREATE_USER.c_str());
+ MYSQL_QUERY_T_(mysql, GRANT_USER_PRIVS.c_str());
+ }
+
+ return { EXIT_SUCCESS, user_def };
+}
+
+pair create_mysql_user(MYSQL* mysql, const user_def_t& user_def) {
+ if (user_def.rand_pass) {
+ pair rnd_user_def {};
+
+ while (
+ rnd_user_def.first == EXIT_SUCCESS &&
+ (chk_inv_pass(rnd_user_def.second.prim_pass.get())
+ || chk_inv_pass(rnd_user_def.second.addl_pass.get()))
+ ) {
+ rnd_user_def = create_mysql_user_rnd_creds(mysql, user_def);
+ }
+
+ return rnd_user_def;
+ } else {
+ return create_mysql_user_exp_creds(mysql, user_def);
+ }
+}
+
+pair create_backend_user(MYSQL* mysql, const user_def_t& user_def) {
+ const pair c_user_def { create_mysql_user(mysql, user_def) };
+
+ if (c_user_def.first == EXIT_SUCCESS) {
+ pair p_creds_res { ext_user_auth_strs(mysql, c_user_def.second) };
+
+ return p_creds_res;
+ } else {
+ return { EXIT_FAILURE, user_creds_t {} };
+ }
+}
+
+/**
+ * @brief Configure the backend MySQL 8 users for frontend-backend connection creation.
+ * @param mysql Already opened MySQL connection.
+ * @param backend_users The users to be created in the MySQL server.
+ * @return A pair of kind `{err_code, user_creds}`.
+ */
+pair> config_mysql_backend_users(
+ MYSQL* mysql, const vector& users_defs
+) {
+ for (const auto& u : users_defs) {
+ MYSQL_QUERY_T__(mysql, ("DROP USER IF EXISTS '" + u.name + "'").c_str());
+ }
+
+ vector f_users_creds {};
+
+ for (const auto& user_def : users_defs) {
+ pair creds_res { create_backend_user(mysql, user_def) };
+
+ if (creds_res.first == EXIT_SUCCESS) {
+ f_users_creds.push_back(std::move(creds_res.second));
+ } else {
+ return { EXIT_FAILURE, vector {} };
+ }
+ }
+
+ return { EXIT_SUCCESS, std::move(f_users_creds) };
+}
+
+int config_proxysql_users(MYSQL* admin, const test_conf_t& test_conf, const vector& users) {
+ const string DEF_HG { std::to_string(TAP_MYSQL8_BACKEND_HG) };
+
+ for (const auto& u : users) {
+ MYSQL_QUERY_T(admin, ("DELETE FROM mysql_users WHERE username='" + u.user_def.name + "'").c_str());
+ }
+
+ // Ensure cleanup of previously cached clear_text 'caching_sha2' passwords
+ MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
+ MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
+
+ for (const auto& u : users) {
+ string attrs {};
+
+ // A user may not have an additional_password configured
+ if (u.user_def.addl_pass) {
+ string addl_pass {};
+
+ // NOTE: If the primary pass is empty, addl pass must be empty **by spec**
+ if (is_empty_pass(u.user_def.prim_pass.get())) {
+ addl_pass = {};
+ } else if (test_conf.hashed_pass) {
+ const char* c_addl_pass { u.hashed_addl_pass_bin.get() };
+
+ if (c_addl_pass) {
+ addl_pass = c_addl_pass;
+ }
+ } else {
+ addl_pass = u.user_def.addl_pass.get();
+ }
+
+ const string hex_addl_pass { hex(addl_pass) };
+ attrs = "{\"additional_password\": \"" + hex_addl_pass + "\"}";
+ }
+
+ string insert_query {};
+
+ if (u.user_def.prim_pass) {
+ const string prim_pass {
+ test_conf.hashed_pass && strlen(u.hashed_prim_pass_bin.get()) > 0 ?
+ "UNHEX('" + string { u.hashed_prim_pass_bin.get() } + "')" :
+ "'" + string { u.user_def.prim_pass.get() } + "'"
+ };
+
+ if (u.user_def.addl_pass) {
+ insert_query = {
+ "INSERT INTO mysql_users (username,password,default_hostgroup,attributes) "
+ "VALUES ('" + u.user_def.name + "'," + prim_pass + "," + DEF_HG + ",'" + attrs + "')"
+ };
+ } else {
+ insert_query = {
+ "INSERT INTO mysql_users (username,password,default_hostgroup) "
+ "VALUES ('" + u.user_def.name + "'," + prim_pass + "," + DEF_HG + ")"
+ };
+ }
+ } else {
+ insert_query = {
+ "INSERT INTO mysql_users (username,default_hostgroup,attributes) "
+ "VALUES ('" + u.user_def.name + "'," + DEF_HG + ",'" + attrs + "')"
+ };
+ }
+
+ MYSQL_QUERY_T(admin, insert_query.c_str());
+ }
+
+ MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
+
+ return EXIT_SUCCESS;
+}
+
+FILE* F_SSLKEYLOGFILE = nullptr;
+
+void ssl_keylog_callback(SSL*, const char* line) {
+ if (!F_SSLKEYLOGFILE) { return; }
+
+ if (strlen(line) != 0) {
+ fprintf(F_SSLKEYLOGFILE, "%s\n", line);
+ }
+}
+
+string to_string(const test_conf_t& conf) {
+ return "{ "
+ "\"req_auth\":'" + conf.req_auth + "', "
+ "\"def_auth\":'" + conf.def_auth + "', "
+ "\"hashed_pass\":'" + std::to_string(conf.hashed_pass) + "'"
+ "\"use_ssl\":'" + std::to_string(conf.use_ssl) + "'"
+ " }";
+}
+
+string to_string(const PASS_TYPE::E type) {
+ if (type == PASS_TYPE::PRIMARY) {
+ return "PRIM";
+ } else if (type == PASS_TYPE::ADDITIONAL) {
+ return "ADDL";
+ } else {
+ return "UNKN";
+ }
+}
+
+string to_string(const test_creds_t& creds) {
+ return "{ "
+ "\"name\":'" + creds.name + "', "
+ "\"pass\":'" + (creds.pass ? creds.pass.get() : "NULL") + "', "
+ "\"type\":'" + to_string(creds.info.type) + "'"
+ " }";
+}
+
+string to_string(const sess_info_t& sess) {
+ return "{ "
+ "\"recv_pkts\":'" + std::to_string(sess.recv_pkts) + "', "
+ "\"sent_pkts\":'" + std::to_string(sess.sent_pkts) + "', "
+ "\"auth_switch_sent\":'" + std::to_string(sess.switching_auth_sent) + "'"
+ " }";
+}
+
+string to_string(const user_auth_stats_t& stats) {
+ return "{ "
+ "\"prim_pass_auths\":'" + std::to_string(stats.prim_pass_auths) + "', "
+ "\"addl_pass_auths\":'" + std::to_string(stats.addl_pass_auths) + "', "
+ "\"full_sha2_auths\":'" + std::to_string(stats.full_sha2_auths) +
+ " }";
+}
+
+PASS_TYPE::E get_matching_pass(const user_creds_t& creds, const char* p) {
+ const char* prim_pass { creds.user_def.prim_pass.get() };
+ const char* addl_pass { creds.user_def.addl_pass.get() };
+
+ if (match_pass(prim_pass, p)) {
+ return PASS_TYPE::PRIMARY;
+ } else if (match_pass(addl_pass, p)) {
+ // This is an impossible scenario for MySQL passwords; we exclude it in this section of the test.
+ // Configuration should have enforced empty 'primary' and 'additional' passwords.
+ if (is_empty_pass(prim_pass) && !is_empty_pass(addl_pass)) {
+ return PASS_TYPE::UNKNOWN;
+ } else {
+ return PASS_TYPE::ADDITIONAL;
+ }
+ } else {
+ return PASS_TYPE::UNKNOWN;
+ }
+}
+
+test_creds_t map_user_creds(const vector& users_creds, const test_creds_t& test_creds) {
+ const auto& creds_it {
+ std::find_if(users_creds.begin(), users_creds.end(),
+ [&test_creds] (const user_creds_t& creds) -> bool {
+ return creds.user_def.name == test_creds.name;
+ }
+ )
+ };
+
+ // cover the possibility for unknown users to be tested
+ if (creds_it == std::end(users_creds)) {
+ test_creds_t new_creds { test_creds };
+ new_creds.info.type = PASS_TYPE::UNKNOWN;
+ new_creds.info.auth = {};
+
+ return new_creds;
+ } else {
+ test_creds_t new_creds { test_creds };
+ new_creds.info.auth = creds_it->user_def.auth;
+ new_creds.info.type = get_matching_pass(*creds_it, test_creds.pass.get());
+
+ return new_creds;
+ }
+}
+
+bool chk_exp_scs_basic(const test_conf_t& conf, const test_creds_t& creds) {
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ // empty passwords allowed for 'primary'
+ if (!creds.pass || (creds.pass && strlen(creds.pass.get()) == 0)) {
+ return true;
+ }
+ } else {
+ // empty passwords not allowed for 'additional'
+ if (!creds.pass || (creds.pass && strlen(creds.pass.get()) == 0)) {
+ return false;
+ }
+ }
+
+ // unknown passtype implies invalids creds; always a failure
+ if (creds.info.type == PASS_TYPE::UNKNOWN) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool chk_exp_seq_fail_except(
+ const test_conf_t& conf,
+ const test_creds_t& creds,
+ const user_auth_stats_t& auth_info
+) {
+ // Short circuit for empty pass; no exceptional failures
+ if (is_empty_pass(creds.pass.get())) {
+ return false;
+ }
+
+ // TODO: MAKE EXPLICIT TEST
+ //
+ // 'caching_sha2_password' auth should fail for NON-SSL if no previous scs auth:
+ // - No clear_text pass on ProxySQL side
+ // - Full authentication is required
+ if (!conf.use_ssl && conf.hashed_pass && creds.info.auth == "caching_sha2_password") {
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ return auth_info.prim_pass_auths == 0;
+ } else {
+ return auth_info.addl_pass_auths == 0;
+ }
+ }
+ // TODO: MAKE EXPLICIT TEST
+ //
+ // Auth method requested by ProxySQL will be 'caching_sha2_password'; if the 'clear_text_pass'
+ // is unknown, we are forced to request it from client, so a secure channel is needed. ProxySQL
+ // doesn't infer that the credentials stored are SHA1, otherwise, could avoid SHA2 full-auth.
+ if (
+ !conf.use_ssl && conf.hashed_pass && creds.info.auth == "mysql_native_password"
+ && conf.req_auth == "caching_sha2_password" && conf.def_auth == "caching_sha2_password"
+ ) {
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ return auth_info.prim_pass_auths == 0;
+ } else {
+ return auth_info.addl_pass_auths == 0;
+ }
+ }
+ // TODO: MAKE EXPLICIT TEST
+ //
+ // Since the 'req_auth' is not "caching_sha2_password"; but pass is stored as SHA2 ProxySQL
+ // will be forced to request an auth-switch to the user; and then proceed with caching_sha2
+ // full auth; this is currently unsupported.
+ if (
+ conf.use_ssl && conf.hashed_pass && creds.info.auth == "caching_sha2_password"
+ && conf.req_auth != "caching_sha2_password"
+ ) {
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ return auth_info.prim_pass_auths == 0;
+ } else {
+ return auth_info.addl_pass_auths == 0;
+ }
+ }
+ // TODO: MAKE EXPLICIT TEST
+ //
+ // Since the pass store as SHA2, the clear-text is required, instead of initiating full-auth
+ // ProxySQL requests an auth switch to the client to 'mysql_native_password'. After the switch
+ // auth response, ProxySQL wont be able to verify the request, and auth will fail.
+ if (
+ conf.use_ssl && conf.hashed_pass && creds.info.auth == "caching_sha2_password"
+ && conf.req_auth == "caching_sha2_password" && conf.def_auth != "caching_sha2_password"
+ ) {
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ return auth_info.prim_pass_auths == 0;
+ } else {
+ return auth_info.addl_pass_auths == 0;
+ }
+ }
+
+ return false;
+}
+
+bool chk_seq_exp_scs(const test_conf_t& conf, const test_creds_t& creds, const user_auth_stats_t& auth_info) {
+ return chk_exp_scs_basic(conf, creds) && !chk_exp_seq_fail_except(conf, creds, auth_info);
+}
+
+bool chk_exp_auth_switch(const test_conf_t& conf, const test_creds_t& creds) {
+ return
+ // TODO: Limitation; for empty passwords ProxySQL asks auth_switch
+ // !is_empty_pass(creds.pass) &&
+ conf.hashed_pass &&
+ conf.req_auth != conf.def_auth &&
+ conf.req_auth != creds.info.auth &&
+ conf.req_auth != "mysql_clear_password";
+}
+
+string map_auth_switch(int64_t auth_switch) {
+ if (auth_switch == 0) {
+ return "mysql_native_password";
+ } else if (auth_switch == 1) {
+ return "mysql_clear_password";
+ } else if (auth_switch == 2) {
+ return "caching_sha2_password";
+ } else {
+ return "unknown_plugin";
+ }
+}
+
+string get_exp_auth_switch(const test_conf_t& conf, const test_creds_t& creds, const sess_info_t& sess_info) {
+ string exp_auth_switch_type {};
+
+ if (chk_exp_auth_switch(conf, creds)) {
+ // TODO: Limitation; for empty passwords ProxySQL asks auth_switch
+ if (is_empty_pass(creds.pass.get())) {
+ exp_auth_switch_type = "mysql_native_password";
+ } else {
+ exp_auth_switch_type = conf.def_auth;
+ }
+ } else {
+ // TODO: Limitation; for empty passwords ProxySQL asks auth_switch
+ if (
+ is_empty_pass(creds.pass.get()) && conf.req_auth != conf.def_auth
+ && conf.req_auth != "mysql_clear_password"
+ ) {
+ exp_auth_switch_type = "mysql_native_password";
+ }
+ // TODO: Limitation; ProxySQL asks for futile auth-switch. Example case:
+ // - creds:`{ "name":'dualpass9', "pass":'newpass9', "type":'PRIM' }`
+ // - conf:`{ "req_auth":'mysql_native_password', "def_auth":'caching_sha2_password', "hashed_pass":'1'"use_ssl":'0' }`
+ //
+ // ProxySQL asks for a auth-switch to "mysql_native_password"; even if client requested.
+ // Then accepts the auth attempt with the auth-switch-response.
+ else if (
+ conf.hashed_pass && creds.info.auth == "mysql_native_password" &&
+ conf.req_auth == "mysql_native_password" && conf.def_auth == "caching_sha2_password"
+ ) {
+ exp_auth_switch_type = "mysql_native_password";
+ }
+ // TODO: Limitation; ProxySQL asks for futile auth-switch; password is 'clear_text'
+ else if (
+ (conf.req_auth == "mysql_native_password" && conf.def_auth == "caching_sha2_password") ||
+ (conf.req_auth == "caching_sha2_password" && conf.def_auth == "mysql_native_password")
+ ) {
+ exp_auth_switch_type = "mysql_native_password";
+ } else {
+ exp_auth_switch_type = "unknown_plugin";
+ }
+ }
+
+ return exp_auth_switch_type;
+}
+
+bool detect_sha2_cached_auth(const sess_info_t& sess_info) {
+ return
+ sess_info.switching_auth_sent == -1 &&
+ sess_info.recv_pkts == 4 && sess_info.sent_pkts == 3;
+}
+
+bool detect_sha2_full_auth(const sess_info_t& sess_info) {
+ return
+ sess_info.switching_auth_sent == -1 &&
+ sess_info.recv_pkts == 4 && sess_info.sent_pkts == 3;
+}
+
+bool chk_exp_sha2_full_auth(
+ const test_conf_t& conf, const test_creds_t& creds, const user_auth_stats_t& auth_info
+) {
+ if (!is_empty_pass(creds.pass.get()) && conf.hashed_pass && creds.info.auth == "caching_sha2_password") {
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ return auth_info.prim_pass_auths == 0;
+ } else {
+ return auth_info.addl_pass_auths == 0;
+ }
+ }
+ // TODO: MAKE EXPLICIT TEST
+ //
+ // ProxySQL fails to detect that the stored password is 'mysql_native_password';
+ // instead of requesting the consequent auth switch, the clear_text password is required, so
+ // full auth is requested.
+ else if (
+ !is_empty_pass(creds.pass.get()) && conf.hashed_pass &&
+ conf.def_auth == "caching_sha2_password" &&
+ conf.req_auth == "caching_sha2_password" &&
+ creds.info.auth == "mysql_native_password"
+ ) {
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ return auth_info.prim_pass_auths == 0;
+ } else {
+ return auth_info.addl_pass_auths == 0;
+ }
+ } else {
+ return false;
+ }
+}
+
+bool chk_exp_fail_except_no_warmup(const test_conf_t& conf, const test_creds_t& creds) {
+ return chk_exp_seq_fail_except(conf, creds, user_auth_stats_t { {}, 0, 0 });
+}
+
+bool chk_exp_fail_no_warmup(const test_conf_t& conf, const test_creds_t& creds) {
+ return !chk_exp_scs_basic(conf, creds) || chk_exp_fail_except_no_warmup(conf, creds);
+}
+
+bool chk_exp_scs_no_warmup(const test_conf_t& conf, const test_creds_t& creds) {
+ return chk_exp_scs_basic(conf, creds) && !chk_exp_fail_except_no_warmup(conf, creds);
+}
+
+/**
+ * @brief Gen all combinations of tests configs.
+ * @param def_auths Defaults auths to set for ProxySQL.
+ * @param req_auths Auth methods for client to request.
+ * @param hash_pass If passwords should be hashed or not.
+ * @param use_ssl If connection is started with SSL or not.
+ * @return Vector with the combinations.
+ */
+vector get_conf_combs(
+ const vector& def_auths,
+ const vector& req_auths,
+ const vector& hash_pass,
+ const vector& use_ssl
+) {
+ vector confs {};
+
+ for (const auto& def_auth : def_auths) {
+ for (const auto& req_auth : req_auths) {
+ for (const auto& hashed : hash_pass) {
+ for (const auto& ssl : use_ssl) {
+ confs.push_back({def_auth, req_auth, hashed, ssl});
+ }
+ }
+ }
+ }
+
+ return confs;
+}
+
+user_auth_stats_t update_auth_reg(MYSQL* mysql, const string& user, const char* pass, auth_reg_t& auth_reg) {
+ user_auth_stats_t auth_info {};
+
+ if (mysql) {
+ auto it = auth_reg.find(user);
+
+ if (it == auth_reg.end()) {
+ std::cerr << "AuthRegistry; invalid user missing entry user:'" << user << "'\n";
+ auth_reg.insert({ user, {} });
+ } else {
+ auth_info = it->second;
+
+ const char* user_prim_pass { it->second.user_def.prim_pass.get() };
+ const char* user_addl_pass { it->second.user_def.addl_pass.get() };
+
+ const char* def_auth { nullptr };
+ mysql_get_option(mysql, MYSQL_DEFAULT_AUTH, &def_auth);
+
+ if (def_auth && strcmp(def_auth, "caching_sha2_password") == 0) {
+ if (is_empty_pass(pass) && is_empty_pass(user_prim_pass)) {
+ it->second.prim_pass_auths += 1;
+ } else if (is_empty_pass(pass) && is_empty_pass(user_addl_pass)) {
+ it->second.addl_pass_auths += 1;
+ } else if (pass && user_prim_pass && (strcmp(pass, user_prim_pass) == 0)) {
+ it->second.prim_pass_auths += 1;
+ } else if (pass && user_addl_pass && (strcmp(pass, user_addl_pass) == 0)) {
+ it->second.addl_pass_auths += 1;
+ } else {
+ diag("WARNING: Auth worked using invalid password, failure expected");
+ }
+ }
+ }
+ }
+
+ return auth_info;
+}
+
+pair count_exp_scs(
+ const vector& confs,
+ const vector& user_creds,
+ const vector& test_creds
+) {
+ pair stats {};
+
+ for (const test_conf_t& conf : confs) {
+ auth_reg_t auth_reg { create_auth_reg(user_creds) };
+
+ for (const test_creds_t& creds : test_creds) {
+ test_creds_t f_creds { map_user_creds(user_creds, creds) };
+
+ auto it = auth_reg.find(creds.name);
+ if (it == auth_reg.end()) {
+ std::cerr << "Testing invalid user for failure user:'" << creds.name << "'\n";
+ stats.second += 1;
+
+ continue;
+ }
+
+ bool exp_scs = chk_seq_exp_scs(conf, f_creds, it->second);
+
+ if (exp_scs) {
+ MYSQL* mock = mysql_init(NULL);
+ mysql_options(mock, MYSQL_DEFAULT_AUTH, conf.req_auth.c_str());
+ update_auth_reg(mock, creds.name, creds.pass.get(), auth_reg);
+ mysql_close(mock);
+
+ stats.first += 1;
+ } else {
+ stats.second += 1;
+ }
+ }
+ }
+
+ return stats;
+}
+
+int config_mysql_conn(const CommandLine& cl, const test_conf_t& conf, MYSQL* proxy) {
+ unsigned long cflags = 0;
+ mysql_options(proxy, MYSQL_DEFAULT_AUTH, conf.req_auth.c_str());
+
+ if (conf.req_auth == "mysql_clear_password") {
+ bool enable_cleartext = true;
+ mysql_options(proxy, MYSQL_ENABLE_CLEARTEXT_PLUGIN, &enable_cleartext);
+ }
+
+ if (conf.use_ssl) {
+ mysql_ssl_set(proxy, NULL, NULL, NULL, NULL, NULL);
+ cflags = CLIENT_SSL;
+
+ if (getenv("SSLKEYLOGFILE") && F_SSLKEYLOGFILE) {
+ mysql_options(proxy, MARIADB_OPT_SSL_KEYLOG_CALLBACK, reinterpret_cast(ssl_keylog_callback));
+ }
+ }
+
+ return cflags;
+}
+
+void test_creds_frontend_backend(
+ const CommandLine& cl, const test_conf_t& conf, const test_creds_t& creds, auth_reg_t& auth_reg
+) {
+ MYSQL* proxy = mysql_init(NULL);
+ int cflags = config_mysql_conn(cl, conf, proxy);
+
+ diag("Performing connection attempt creds:`%s`", to_string(creds).c_str());
+ MYSQL* myconn {
+ mysql_real_connect(proxy, cl.host, creds.name.c_str(), creds.pass.get(), NULL, cl.port, NULL, cflags)
+ };
+
+ user_auth_stats_t auth_info { update_auth_reg(myconn, creds.name, creds.pass.get(), auth_reg) };
+ bool exp_success = chk_seq_exp_scs(conf, creds, auth_info);
+
+ if (exp_success) {
+ ok(
+ myconn != nullptr,
+ "Connection attempt should SUCCEED creds:`%s`, conf:`%s`, auth_info:`%s`, error:'%s'",
+ to_string(creds).c_str(), to_string(conf).c_str(), to_string(auth_info).c_str(), mysql_error(proxy)
+ );
+
+ sess_info_t sess_info { ext_sess_info(proxy) };
+ diag("Extracted session info sess_info:`%s`", to_string(sess_info).c_str());
+
+ const string exp_auth_switch_type { get_exp_auth_switch(conf, creds, sess_info) };
+ const string act_auth_switch_type { map_auth_switch(sess_info.switching_auth_sent) };
+
+ ok(
+ exp_auth_switch_type == act_auth_switch_type,
+ "Found auth switch should match expected exp:'%s', act:'%s'",
+ exp_auth_switch_type.c_str(), act_auth_switch_type.c_str()
+ );
+
+ const bool exp_full_sha2 = chk_exp_sha2_full_auth(conf, creds, auth_info);
+ const bool act_full_sha2 = detect_sha2_full_auth(sess_info);
+
+ ok(
+ exp_full_sha2 == act_full_sha2,
+ "Found full 'caching_sha2' full auth should match expected exp:'%d', act:'%d'",
+ exp_full_sha2, act_full_sha2
+ );
+
+ // Check new connection creation on ProxySQL side
+ int rc = mysql_query(proxy, "/* create_new_connection=1 */ DO 1");
+
+ ok(
+ rc == 0 && mysql_errno(proxy) == 0,
+ "Query and backend connection creation should succeed myerrno:'%d', myerror:'%s'",
+ mysql_errno(proxy), mysql_error(proxy)
+ );
+ } else {
+ const char* myerror = mysql_error(proxy);
+ uint32_t myerrno = mysql_errno(proxy);
+
+ ok(
+ myconn == nullptr && mysql_errno(proxy),
+ "Connection attempt should FAIL creds:`%s`, conf:`%s`, auth_info:`%s`, errno:'%d', error:'%s'",
+ to_string(creds).c_str(), to_string(conf).c_str(), to_string(auth_info).c_str(), myerrno, myerror
+ );
+ }
+
+ mysql_close(proxy);
+}
+
+void test_creds_frontend(
+ const CommandLine& cl,
+ const test_conf_t& conf,
+ const test_creds_t& creds,
+ const chk_exp_scs_t& chk_exp_scs
+) {
+ MYSQL* proxy = mysql_init(NULL);
+ int cflags = config_mysql_conn(cl, conf, proxy);
+
+ const string creds_str { to_string(creds) };
+ const uint64_t th_id { pthread_self() };
+
+ diag("Performing connection attempt thread:`%lu`, creds:`%s`", th_id, creds_str.c_str());
+ MYSQL* myconn {
+ mysql_real_connect(proxy, cl.host, creds.name.c_str(), creds.pass.get(), NULL, cl.port, NULL, cflags)
+ };
+
+ bool exp_scs = chk_exp_scs(conf, creds);
+ if (exp_scs) {
+ ok(
+ myconn != nullptr,
+ "Connection attempt should SUCCEED thread:`%lu`, creds:`%s`, conf:`%s`, error:'%s'",
+ pthread_self(), to_string(creds).c_str(), to_string(conf).c_str(), mysql_error(proxy)
+ );
+
+ sess_info_t sess_info { ext_sess_info(proxy) };
+ diag("Extracted session info thread:`%lu`, sess_info:`%s`", th_id, to_string(sess_info).c_str());
+
+ const string exp_auth_switch_type { get_exp_auth_switch(conf, creds, sess_info) };
+ const string act_auth_switch_type { map_auth_switch(sess_info.switching_auth_sent) };
+
+ ok(
+ exp_auth_switch_type == act_auth_switch_type,
+ "Found auth switch should match expected thread:`%lu`, exp:'%s', act:'%s'",
+ pthread_self(), exp_auth_switch_type.c_str(), act_auth_switch_type.c_str()
+ );
+ } else {
+ const char* myerror = mysql_error(proxy);
+ uint32_t myerrno = mysql_errno(proxy);
+
+ ok(
+ myconn == nullptr && mysql_errno(proxy),
+ "Connection attempt should FAIL thread:`%lu`, creds:`%s`, conf:`%s`, errno:'%d', error:'%s'",
+ pthread_self(), to_string(creds).c_str(), to_string(conf).c_str(), myerrno, myerror
+ );
+ }
+
+ mysql_close(proxy);
+}
+
+user_auth_stats_t check_auth_creds(
+ const CommandLine& cl, const test_conf_t& conf, const test_creds_t& creds
+) {
+ MYSQL* proxy = mysql_init(NULL);
+ int cflags = config_mysql_conn(cl, conf, proxy);
+
+ const string creds_str { to_string(creds) };
+ const uint64_t th_id { pthread_self() };
+
+ diag("Performing connection attempt thread:`%lu`, creds:`%s`", th_id, creds_str.c_str());
+ MYSQL* myconn {
+ mysql_real_connect(proxy, cl.host, creds.name.c_str(), creds.pass.get(), NULL, cl.port, NULL, cflags)
+ };
+
+ user_auth_stats_t auth_stats {};
+
+ if (myconn) {
+ sess_info_t sess_info { ext_sess_info(proxy) };
+ diag("Extracted session info thread:`%lu`, sess_info:`%s`", th_id, to_string(sess_info).c_str());
+
+ bool full_sha2_auth = detect_sha2_cached_auth(sess_info);
+
+ if (creds.info.type == PASS_TYPE::PRIMARY) {
+ auth_stats = user_auth_stats_t { user_def_t { creds.name }, 1, 0, full_sha2_auth };
+ } else {
+ auth_stats = user_auth_stats_t { user_def_t { creds.name }, 0, 1, full_sha2_auth };
+ }
+ } else {
+ auth_stats = user_auth_stats_t { user_def_t { creds.name }, 0, 0, 0 };
+ }
+
+ mysql_close(proxy);
+
+ return auth_stats;
+}
+
+const vector backend_users {
+ { "dualpass1", MF_CHAR_(nullptr), MF_CHAR_(nullptr), "mysql_native_password" },
+ { "dualpass2", MF_CHAR_(""), MF_CHAR_(nullptr), "mysql_native_password" },
+ { "dualpass3", MF_CHAR_("newpass3"), MF_CHAR_(nullptr), "mysql_native_password" },
+ { "dualpass4", MF_CHAR_(nullptr), MF_CHAR_(""), "mysql_native_password" },
+ { "dualpass5", MF_CHAR_(""), MF_CHAR_(""), "mysql_native_password" },
+ { "dualpass6", MF_CHAR_("newpass6"), MF_CHAR_(""), "mysql_native_password" },
+ { "dualpass7", MF_CHAR_(""), MF_CHAR_("oldpass7"), "mysql_native_password" },
+ { "dualpass8", MF_CHAR_(""), MF_CHAR_("oldpass8"), "mysql_native_password" },
+ { "dualpass9", MF_CHAR_("newpass9"), MF_CHAR_("oldpass9"), "mysql_native_password" },
+
+ { "dualpass11", MF_CHAR_(nullptr), MF_CHAR_(nullptr), "caching_sha2_password" },
+ { "dualpass12", MF_CHAR_(""), MF_CHAR_(nullptr), "caching_sha2_password" },
+ { "dualpass13", MF_CHAR_("newpass13"), MF_CHAR_(nullptr), "caching_sha2_password" },
+ { "dualpass14", MF_CHAR_(nullptr), MF_CHAR_(""), "caching_sha2_password" },
+ { "dualpass15", MF_CHAR_(""), MF_CHAR_(""), "caching_sha2_password" },
+ { "dualpass16", MF_CHAR_("newpass16"), MF_CHAR_(""), "caching_sha2_password" },
+ { "dualpass17", MF_CHAR_(""), MF_CHAR_("oldpass17"), "caching_sha2_password" },
+ { "dualpass18", MF_CHAR_(""), MF_CHAR_("oldpass18"), "caching_sha2_password" },
+ { "dualpass19", MF_CHAR_("newpass19"), MF_CHAR_("oldpass19"), "caching_sha2_password" },
+};
+
+/**
+ * @brief Minimal test cases for default backend users.
+ */
+const vector tests_creds {
+ { "dualpass1", MF_CHAR_(nullptr) },
+ { "dualpass1", MF_CHAR_("") },
+ { "dualpass1", MF_CHAR_("inv_pass") },
+
+ { "dualpass2", MF_CHAR_(nullptr) },
+ { "dualpass2", MF_CHAR_("") },
+ { "dualpass2", MF_CHAR_("inv_pass") },
+
+ { "dualpass3", MF_CHAR_(nullptr) },
+ { "dualpass3", MF_CHAR_("") },
+ { "dualpass3", MF_CHAR_("inv_pass") },
+ { "dualpass3", MF_CHAR_("newpass3") },
+ { "dualpass3", MF_CHAR_("newpass3") },
+
+ { "dualpass4", MF_CHAR_(nullptr) },
+ { "dualpass4", MF_CHAR_("") },
+ { "dualpass4", MF_CHAR_("inv_pass") },
+
+ { "dualpass5", MF_CHAR_(nullptr) },
+ { "dualpass5", MF_CHAR_("") },
+ { "dualpass5", MF_CHAR_("inv_pass") },
+
+ { "dualpass6", MF_CHAR_(nullptr) },
+ { "dualpass6", MF_CHAR_("") },
+ { "dualpass6", MF_CHAR_("inv_pass") },
+ { "dualpass6", MF_CHAR_("newpass6") },
+ { "dualpass6", MF_CHAR_("newpass6") },
+
+ { "dualpass7", MF_CHAR_(nullptr) },
+ { "dualpass7", MF_CHAR_("") },
+ { "dualpass7", MF_CHAR_("inv_pass") },
+ // { "dualpass7", MF_CHAR_("oldpass7") },
+
+ { "dualpass8", MF_CHAR_(nullptr) },
+ { "dualpass8", MF_CHAR_("") },
+ { "dualpass8", MF_CHAR_("inv_pass") },
+ // { "dualpass8", MF_CHAR_("oldpass8") },
+
+ { "dualpass9", MF_CHAR_(nullptr) },
+ { "dualpass9", MF_CHAR_("") },
+ { "dualpass9", MF_CHAR_("inv_pass") },
+ // { "dualpass9", MF_CHAR_("oldpass9") },
+ { "dualpass9", MF_CHAR_("newpass9") },
+ { "dualpass9", MF_CHAR_("newpass9") },
+
+ { "dualpass11", MF_CHAR_(nullptr) },
+ { "dualpass11", MF_CHAR_("") },
+ { "dualpass11", MF_CHAR_("inv_pass") },
+
+ { "dualpass12", MF_CHAR_(nullptr) },
+ { "dualpass12", MF_CHAR_("") },
+ { "dualpass12", MF_CHAR_("inv_pass") },
+
+ { "dualpass13", MF_CHAR_(nullptr) },
+ { "dualpass13", MF_CHAR_("") },
+ { "dualpass13", MF_CHAR_("inv_pass") },
+ { "dualpass13", MF_CHAR_("newpass13") },
+ { "dualpass13", MF_CHAR_("newpass13") },
+
+ { "dualpass14", MF_CHAR_(nullptr) },
+ { "dualpass14", MF_CHAR_("") },
+ { "dualpass14", MF_CHAR_("inv_pass") },
+
+ { "dualpass15", MF_CHAR_(nullptr) },
+ { "dualpass15", MF_CHAR_("") },
+ { "dualpass15", MF_CHAR_("inv_pass") },
+
+ { "dualpass16", MF_CHAR_(nullptr) },
+ { "dualpass16", MF_CHAR_("") },
+ { "dualpass16", MF_CHAR_("inv_pass") },
+ { "dualpass16", MF_CHAR_("newpass16") },
+ { "dualpass16", MF_CHAR_("newpass16") },
+
+ { "dualpass17", MF_CHAR_(nullptr) },
+ { "dualpass17", MF_CHAR_("") },
+ { "dualpass17", MF_CHAR_("inv_pass") },
+ // { "dualpass17", MF_CHAR_("oldpass17") },
+
+ { "dualpass18", MF_CHAR_(nullptr) },
+ { "dualpass18", MF_CHAR_("") },
+ { "dualpass18", MF_CHAR_("inv_pass") },
+ // { "dualpass18", MF_CHAR_("oldpass18") },
+
+ { "dualpass19", MF_CHAR_(nullptr) },
+ { "dualpass19", MF_CHAR_("") },
+ { "dualpass19", MF_CHAR_("inv_pass") },
+ { "dualpass19", MF_CHAR_("newpass19") },
+ { "dualpass19", MF_CHAR_("newpass19") },
+
+ { "invuser20", MF_CHAR_(nullptr) },
+ { "invuser20", MF_CHAR_("") },
+ { "invuser20", MF_CHAR_("invpass20") },
+
+ { "invuser21", MF_CHAR_(nullptr) },
+ { "invuser21", MF_CHAR_("") },
+ { "invuser21", MF_CHAR_("invpass21") },
+ { "invuser21", MF_CHAR_("newpass19") }
+};
+
+int backend_conns_cleanup(MYSQL* admin) {
+ diag("Cleaning up previous backend connections...");
+ MYSQL_QUERY(admin,
+ ("UPDATE mysql_servers SET max_connections=0 "
+ "WHERE hostgroup_id=" + std::to_string(TAP_MYSQL8_BACKEND_HG)).c_str()
+ );
+ MYSQL_QUERY(admin, "LOAD MYSQL SERVERS TO RUNTIME");
+
+ // Wait for backend connection cleanup
+ int w_res = wait_target_backend_conns(admin, 0, 10, TAP_MYSQL8_BACKEND_HG);
+ if (w_res != EXIT_SUCCESS) {
+ diag("Waiting for backend connections failed res:'%d'", w_res);
+ return EXIT_FAILURE;
+ }
+
+ diag("Setup new connection limit max_connections='2000'");
+ MYSQL_QUERY(admin,
+ ("UPDATE mysql_servers SET max_connections=2000 "
+ "WHERE hostgroup_id=" + std::to_string(TAP_MYSQL8_BACKEND_HG)).c_str()
+ );
+ MYSQL_QUERY(admin, "LOAD MYSQL SERVERS TO RUNTIME");
+
+ return EXIT_SUCCESS;
+}
+
+vector>> filter_tests(
+ const vector& conf_combs,
+ const vector& users_creds,
+ const vector& tests_creds,
+ const chk_exp_scs_t& filter
+) {
+ vector>> non_warmup_tests {};
+
+ for (const auto& conf : conf_combs) {
+ vector conf_creds {};
+
+ for (const auto& creds : tests_creds) {
+ test_creds_t f_creds { map_user_creds(users_creds, creds) };
+
+ if (filter(conf, f_creds)) {
+ conf_creds.push_back(f_creds);
+ }
+ }
+
+ if (!conf_creds.empty()) {
+ non_warmup_tests.push_back({conf, conf_creds});
+ }
+ }
+
+ return non_warmup_tests;
+}
+
+bool req_sha2_auth(const test_conf_t& conf, const test_creds_t& creds) {
+ // otherwise SHA2 auth shouldn't take place
+ if (!is_empty_pass(creds.pass.get()) && conf.hashed_pass && conf.use_ssl) {
+ if (creds.info.auth == "caching_sha2_password") {
+ return true;
+ }
+ // current limitation; auth switch shouldn't be requested to 'caching_sha2_password'; since
+ // the pass isn't store as such; the real passtype should be requested
+ else if (
+ conf.def_auth == "caching_sha2_password" && conf.req_auth == "caching_sha2_password"
+ && creds.info.auth == "mysql_native_password"
+ ) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+int test_confs_creds_combs_frontend(
+ CommandLine& cl,
+ MYSQL* admin,
+ const vector& user_creds,
+ const vector>>& confs_creds_map,
+ const chk_exp_scs_t& chk_exp_scs
+) {
+ for (const auto& p_conf_creds : confs_creds_map) {
+ int rc = backend_conns_cleanup(admin);
+ if (rc) { return EXIT_FAILURE; }
+
+ // Need to use both; hashed and unhashed passwords for the backend
+ int cres = config_proxysql_users(admin, p_conf_creds.first, user_creds);
+ if (cres) { return EXIT_FAILURE; }
+
+ diag("%s", ("Switching to '" + p_conf_creds.first.def_auth + "' on ProxySQL side").c_str());
+ MYSQL_QUERY_T(admin,
+ ("SET mysql-default_authentication_plugin='" + p_conf_creds.first.def_auth + "'").c_str()
+ );
+ MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+ MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
+
+ std::vector client_threads {};
+
+ // Perform parallel fronted logging tests
+ for (uint32_t i = 0; i < TAP_NUM_CLIENT_THREADS; i++) {
+ client_threads.push_back(std::thread([&cl, &p_conf_creds, &chk_exp_scs] () {
+ for (const auto& creds : p_conf_creds.second) {
+ test_creds_frontend(cl, p_conf_creds.first, creds, chk_exp_scs);
+ }
+ }));
+ }
+
+ for (auto& cthread : client_threads) {
+ cthread.join();
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
+int test_all_confs_creds(
+ CommandLine& cl,
+ MYSQL* admin,
+ const vector& all_conf_combs,
+ const vector& users_creds,
+ const vector& tests_creds,
+ uint64_t non_warmup_tests_scs_count
+) {
+ uint64_t auth_scs_total = 0;
+ uint64_t full_sha2_total = 0;
+ uint64_t exp_sha2_auths = 0;
+
+ for (const auto& conf : all_conf_combs) {
+ int rc = backend_conns_cleanup(admin);
+ if (rc) { return EXIT_FAILURE; }
+
+ // Need to use both; hashed and unhashed passwords for the backend
+ int cres = config_proxysql_users(admin, conf, users_creds);
+ if (cres) { return EXIT_FAILURE; }
+
+ diag("%s", ("Switching to '" + conf.def_auth + "' on ProxySQL side").c_str());
+ MYSQL_QUERY_T(admin,
+ ("SET mysql-default_authentication_plugin='" + conf.def_auth + "'").c_str()
+ );
+ MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+ MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
+
+ std::vector client_thds {};
+ std::vector thds_exp_sha2_auths(TAP_NUM_CLIENT_THREADS);
+ std::vector thds_auth_regs {};
+
+ for (uint32_t i = 0; i < TAP_NUM_CLIENT_THREADS; i++) {
+ thds_auth_regs.push_back(create_auth_reg(users_creds));
+ }
+
+ for (uint32_t i = 0; i < TAP_NUM_CLIENT_THREADS; i++) {
+ client_thds.push_back(
+ std::thread(
+ [&cl, &conf, &thds_auth_regs, &thds_exp_sha2_auths, i, &users_creds, &tests_creds] () {
+ auth_reg_t& auth_reg { thds_auth_regs[i] };
+
+ for (const auto& creds : tests_creds) {
+ test_creds_t f_creds { map_user_creds(users_creds, creds) };
+ user_auth_stats_t auth_stats { check_auth_creds(cl, conf, f_creds) };
+
+ if (auth_stats.prim_pass_auths || auth_stats.addl_pass_auths) {
+ auto user_stats_it = auth_reg.find(f_creds.name);
+
+ if (user_stats_it != auth_reg.end()) {
+ if (
+ chk_exp_scs_basic(conf, f_creds) && req_sha2_auth(conf, f_creds)
+ && user_stats_it->second.prim_pass_auths == 0
+ ) {
+ thds_exp_sha2_auths[i] += 1;
+ }
+
+ user_stats_it->second.prim_pass_auths += auth_stats.prim_pass_auths;
+ user_stats_it->second.addl_pass_auths += auth_stats.addl_pass_auths;
+ user_stats_it->second.full_sha2_auths += auth_stats.full_sha2_auths;
+ }
+ }
+ }
+ }
+ )
+ );
+ }
+
+ for (auto& cthread : client_thds) {
+ cthread.join();
+ }
+
+ for (uint32_t i = 0; i < TAP_NUM_CLIENT_THREADS; i++) {
+ auth_reg_t& auth_reg { thds_auth_regs[i] };
+
+ for (const auto& auth_stats : auth_reg) {
+ auth_scs_total += auth_stats.second.prim_pass_auths;
+ auth_scs_total += auth_stats.second.addl_pass_auths;
+ full_sha2_total += auth_stats.second.full_sha2_auths;
+ }
+
+ exp_sha2_auths += thds_exp_sha2_auths[i];
+ }
+ }
+
+ uint64_t exp_scs_total = non_warmup_tests_scs_count * TAP_NUM_CLIENT_THREADS;
+ uint64_t exp_full_sha2_total = exp_sha2_auths;
+
+ ok(
+ exp_scs_total == auth_scs_total,
+ "Number of auth success should match expected exp:'%lu', act:'%lu'",
+ exp_scs_total, auth_scs_total
+ );
+
+ ok(
+ full_sha2_total < exp_full_sha2_total,
+ "Number of full SHA2 auths should be below expected exp:'%lu', act:'%lu'",
+ exp_full_sha2_total, full_sha2_total
+ );
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char** argv) {
+ CommandLine cl;
+
+ if (cl.getEnv()) {
+ diag("Failed to get the required environmental variables.");
+ return EXIT_FAILURE;
+ }
+
+ TAP_MYSQL8_BACKEND_HG = get_env_int("TAP_MYSQL8_BACKEND_HG", 30);
+ TAP_NUM_CLIENT_THREADS = get_env_int("TAP_NUM_CLIENT_THREADS", 4);
+
+ MYSQL* mysql = mysql_init(NULL);
+
+ if (!mysql_real_connect(mysql, cl.host, cl.mysql_username, cl.mysql_password, NULL, cl.mysql_port, NULL, 0)) {
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
+ return EXIT_FAILURE;
+ }
+
+ MYSQL* admin = mysql_init(NULL);
+
+ if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
+ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
+ return EXIT_FAILURE;
+ }
+
+ // Setup SSLKEYLOGFILE for debugging purposes
+ if (getenv("SSLKEYLOGFILE") != nullptr) {
+ const string datadir { string { cl.workdir } + "/test_auth_methods_datadir" };
+ const string fpath { datadir + "/sslkeylog.log" };
+ int dres = mkdir(datadir.c_str(), 0777);
+
+ if (dres == -1 && errno != EEXIST) {
+ diag("Failed to create datadir path: '%s', error: '%s'", fpath.c_str(), strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ F_SSLKEYLOGFILE = fopen(fpath.c_str(), "a+");
+ if (!F_SSLKEYLOGFILE) {
+ diag("Failed to open SSLKEYLOG file path: '%s', error: '%s'", fpath.c_str(), strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ // Backend users config
+ const auto cbres { config_mysql_backend_users(mysql, ::backend_users) };
+ if (cbres.first) { return EXIT_FAILURE; }
+
+ vector rnd_backend_users { ::backend_users };
+
+ for (user_def_t& user_def : rnd_backend_users) {
+ user_def.name = "rnd" + user_def.name;
+ user_def.rand_pass = true;
+ }
+
+ const auto rnd_cbres { config_mysql_backend_users(mysql, rnd_backend_users) };
+ if (rnd_cbres.first) { return EXIT_FAILURE; }
+
+ vector rnd_tests_creds { ::tests_creds };
+
+
+ for (test_creds_t& creds : rnd_tests_creds) {
+ creds.name = "rnd" + creds.name;
+
+ auto it = std::find_if(cbres.second.begin(), cbres.second.end(),
+ [&creds] (const user_creds_t& user_creds) {
+ return creds.name == user_creds.user_def.name;
+ }
+ );
+
+ if (it != cbres.second.end()) {
+ creds.pass = it->user_def.prim_pass ? MF_CHAR_(it->user_def.prim_pass.get()) : nullptr;
+ }
+ }
+
+ uint32_t NUM_CLIENT_THREADS = 4;
+
+ const vector def_auths {
+ "mysql_native_password",
+ "caching_sha2_password"
+ };
+ const vector req_auhts {
+ "mysql_clear_password",
+ "mysql_native_password",
+ "caching_sha2_password"
+ };
+ const vector hash_pass { false, true };
+ const vector use_ssl { false, true };
+
+ // Sequential access tests; exercising full logic
+ const vector all_conf_combs { get_conf_combs(def_auths, req_auhts, hash_pass, use_ssl) };
+ const auto scs_stats { count_exp_scs(all_conf_combs, cbres.second, tests_creds) };
+
+ pair rnd_scs_stats {};
+
+ if (getenv("TAP_DISABLE_SEQ_CHECKS_RAND_PASS") == nullptr) {
+ rnd_scs_stats = count_exp_scs(all_conf_combs, rnd_cbres.second, rnd_tests_creds);
+ }
+
+ // Partial logic tests; no-warmup, expected failure concurrent access
+ const vector>> non_warmup_tests_fail {
+ filter_tests(all_conf_combs, cbres.second, tests_creds, chk_exp_fail_no_warmup)
+ };
+ const vector>> non_warmup_tests_scs {
+ filter_tests(all_conf_combs, cbres.second, tests_creds, chk_exp_scs_no_warmup)
+ };
+
+ uint64_t non_warmup_tests_fail_count = 0;
+
+ if (getenv("TAP_DISABLE_NON_WARMUP_EXP_FAILS") == nullptr) {
+ for (const auto& p_conf_creds : non_warmup_tests_fail) {
+ non_warmup_tests_fail_count += p_conf_creds.second.size();
+ }
+ }
+
+ uint64_t non_warmup_tests_scs_count = 0;
+
+ if (getenv("TAP_DISABLE_NON_WARMUP_EXP_SCS") == nullptr) {
+ for (const auto& p_conf_creds : non_warmup_tests_scs) {
+ non_warmup_tests_scs_count += p_conf_creds.second.size();
+ }
+ }
+
+ uint64_t non_warmup_tests_scs_ratio = 0;
+
+ if (getenv("TAP_DISABLE_NON_WARMUP_EXP_RATIO") == nullptr) {
+ non_warmup_tests_scs_ratio = 2;
+ }
+
+ plan(
+ scs_stats.first * 4 + scs_stats.second
+ + rnd_scs_stats.first * 4 + rnd_scs_stats.second
+ + non_warmup_tests_fail_count * NUM_CLIENT_THREADS
+ + non_warmup_tests_scs_count * NUM_CLIENT_THREADS * 2
+ + non_warmup_tests_scs_ratio
+ );
+
+ // sequential; verify correctness in the procedure; KNOWN passwords
+ for (const auto& conf : all_conf_combs) {
+ int rc = backend_conns_cleanup(admin);
+ if (rc) { goto cleanup; }
+
+ // Need to use both; hashed and unhashed passwords for the backend
+ int cres = config_proxysql_users(admin, conf, cbres.second);
+ if (cres) { return EXIT_FAILURE; }
+
+ diag("%s", ("Switching to '" + conf.def_auth + "' on ProxySQL side").c_str());
+ MYSQL_QUERY_T(admin, ("SET mysql-default_authentication_plugin='" + conf.def_auth + "'").c_str());
+ MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+ MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
+
+ auth_reg_t auth_reg { create_auth_reg(cbres.second) };
+
+ for (const auto& creds : tests_creds) {
+ test_creds_t f_creds { map_user_creds(cbres.second, creds) };
+ test_creds_frontend_backend(cl, conf, f_creds, auth_reg);
+ }
+ }
+
+ // sequential; verify correctness in the procedure; RANDOM passwords
+ if (getenv("TAP_DISABLE_SEQ_CHECKS_RAND_PASS") == nullptr) {
+ for (const auto& conf : all_conf_combs) {
+ int rc = backend_conns_cleanup(admin);
+ if (rc) { goto cleanup; }
+
+ // Need to use both; hashed and unhashed passwords for the backend
+ int cres = config_proxysql_users(admin, conf, rnd_cbres.second);
+ if (cres) { return EXIT_FAILURE; }
+
+ diag("%s", ("Switching to '" + conf.def_auth + "' on ProxySQL side").c_str());
+ MYSQL_QUERY_T(admin, ("SET mysql-default_authentication_plugin='" + conf.def_auth + "'").c_str());
+ MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+ MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
+
+ auth_reg_t auth_reg { create_auth_reg(rnd_cbres.second) };
+
+ for (const auto& creds : rnd_tests_creds) {
+ test_creds_t f_creds { map_user_creds(rnd_cbres.second, creds) };
+ test_creds_frontend_backend(cl, conf, f_creds, auth_reg);
+ }
+ }
+ }
+
+ // concurrent; known to fail tests cases; no previous auths
+ if (getenv("TAP_DISABLE_NON_WARMUP_EXP_FAILS") == nullptr) {
+ diag("Starting frontend non-warmup FAIL tests; expected all auths to FAIL");
+
+ int res = test_confs_creds_combs_frontend(cl, admin, cbres.second, non_warmup_tests_fail,
+ [] (const test_conf_t&, const test_creds_t&) { return false; }
+ );
+
+ if (res) { goto cleanup; }
+ }
+
+ // concurrent; known to succeed tests cases; no previous auths
+ if (getenv("TAP_DISABLE_NON_WARMUP_EXP_SCS") == nullptr) {
+ diag("Starting frontend non-warmup SUCCESS tests; expected all auths to SUCCEED");
+
+ int res = test_confs_creds_combs_frontend(cl, admin, cbres.second, non_warmup_tests_scs,
+ [] (const test_conf_t&, const test_creds_t&) { return true; }
+ );
+
+ if (res) { goto cleanup; }
+ }
+
+ // concurrent; warmup phase; only number of succeed/failures is pre-known
+ if (getenv("TAP_DISABLE_NON_WARMUP_EXP_RATIO") == nullptr) {
+ diag("Starting frontend non-warmup ALL_COMBS tests; predicting SUCCESS/FAILURE ratio");
+
+ int res = test_all_confs_creds(
+ cl, admin, all_conf_combs, cbres.second, tests_creds, non_warmup_tests_scs_count
+ );
+
+ if (res) { goto cleanup; }
+ }
+
+cleanup:
+
+ mysql_close(mysql);
+ mysql_close(admin);
+
+ if (F_SSLKEYLOGFILE) {
+ fclose(F_SSLKEYLOGFILE);
+ }
+
+ return exit_status();
+}
diff --git a/test/tap/tests/test_auth_methods-t.env b/test/tap/tests/test_auth_methods-t.env
new file mode 100644
index 0000000000..f9b0991bd1
--- /dev/null
+++ b/test/tap/tests/test_auth_methods-t.env
@@ -0,0 +1,6 @@
+TAP_MYSQLUSERNAME=root
+TAP_MYSQLPASSWORD=root
+TAP_MYSQLPORT=14806
+
+TAP_MYSQL8_BACKEND_HG=30
+TAP_NUM_CLIENT_THREADS=4
diff --git a/test/tap/tests/test_change_user-t.cpp b/test/tap/tests/test_change_user-t.cpp
new file mode 100644
index 0000000000..5b1a842026
--- /dev/null
+++ b/test/tap/tests/test_change_user-t.cpp
@@ -0,0 +1,323 @@
+/**
+ * @file test_change_user-t.cpp
+ * @brief Test various mysql_change_user()
+ * @details Create connections with both mysql_native_password and caching_sha2_password
+ * and try to reset them
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "mysql.h"
+
+// copied from ma_common.h , but only the beginning
+struct st_mysql_options_extension {
+ char *plugin_dir;
+ char *default_auth;
+ // README: the struct is more complex, but we only need default_auth
+};
+
+#include "tap.h"
+#include "command_line.h"
+#include "utils.h"
+
+#include "json.hpp"
+
+using std::pair;
+using std::string;
+
+using namespace std;
+
+using nlohmann::json;
+
+#define NCONNS 16
+
+int run_queries_sets(std::vector& queries, MYSQL *my, const std::string& message_prefix) {
+ for (std::vector::iterator it = queries.begin(); it != queries.end(); it++) {
+ std::string q = *it;
+ diag("%s: %s", message_prefix.c_str(), q.c_str());
+ MYSQL_QUERY(my, q.c_str());
+ }
+ return 0;
+}
+
+void parse_result_json_column(MYSQL_RES *result, json& j) {
+ if(!result) return;
+ MYSQL_ROW row;
+
+ while ((row = mysql_fetch_row(result))) {
+ j = json::parse(row[0]);
+ }
+}
+
+
+int get_internal_session(MYSQL *my, json& j) {
+ MYSQL_QUERY(my, "PROXYSQL INTERNAL SESSION");
+ MYSQL_RES* tr_res = mysql_store_result(my);
+ parse_result_json_column(tr_res, j);
+ mysql_free_result(tr_res);
+ return 0;
+}
+
+
+MYSQL* proxy[NCONNS];
+MYSQL* admin = NULL;
+
+bool create_connections(const CommandLine& cl, const char *plugin, bool use_ssl, bool check_plugin, bool check_ssl, bool incorrect_connect_password) {
+ diag("Calling create_connections with plugin: %s , use_ssl: %d , check_plugin: %d , check_ssl: %d , incorrect_connect_password: %d",
+ plugin, use_ssl, check_plugin, check_ssl, incorrect_connect_password);
+ MYSQL * my = NULL;
+ int rc = 0;
+ for (int i=0; ioptions.extension->default_auth) != 0) {
+ ok(false, "Plugin wanted: %s , used: %s", plugin, proxy[i]->options.extension->default_auth);
+ return false;
+ }
+ }
+ } else {
+ ok(my == NULL, "Connect should fail");
+ }
+ }
+ return true;
+}
+
+void close_connections() {
+ for (int i=0; i> vec = {
+ {"mysql_native_password", false},
+ {"mysql_native_password", true},
+ {"caching_sha2_password", true}
+ };
+ for (auto it = vec.begin(); it != vec.end() ; it++) {
+ diag("%d: Starting testing plugin %s and ssl %d" , __LINE__, it->first.c_str(), it->second);
+
+ diag("Setting mysql-default_authentication_plugin='%s'", plugin);
+ vector query_set1 = {"SET mysql-default_authentication_plugin='" + string(plugin) + "'", "LOAD MYSQL VARIABLES TO RUNTIME"};
+ if (run_queries_sets( query_set1 , admin, "Running on Admin"))
+ return exit_status();
+
+ const char *auth_plugin = it->first.c_str();
+ vector query_set2 = {string(string("SET mysql-have_ssl='") + (it->second ? "true" : "false") + "'"), "LOAD MYSQL VARIABLES TO RUNTIME"};
+ if (run_queries_sets( query_set2 , admin, "Running on Admin"))
+ return exit_status();
+ if (create_connections(cl, auth_plugin, false, true, test_ssl, incorrect_connect_password) != true) {
+ return exit_status();
+ }
+ if (incorrect_connect_password == false) {
+ if (test_plugin) {
+ for (int i = 0; ifirst == "caching_sha2_password") {
+ s = string(plugin);
+ }
+ ok(j["client"]["prot"]["auth_plugin"] == s,
+ "%s: %d: Plugin wanted: %s , used: %s", plugin, __LINE__, auth_plugin, string(j["client"]["prot"]["auth_plugin"]).c_str());
+ }
+ }
+ if (change_user) {
+ for (int i = 0; i