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