/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include static inline int od_auth_frontend_cleartext(od_client_t *client) { od_instance_t *instance = client->global->instance; /* AuthenticationCleartextPassword */ machine_msg_t *msg; msg = kiwi_be_write_authentication_clear_text(NULL); if (msg == NULL) return -1; int rc; rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for password response */ while (1) { msg = od_read(&client->io, UINT32_MAX); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) break; machine_msg_free(msg); } /* read password message */ kiwi_password_t client_token; kiwi_password_init(&client_token); rc = kiwi_be_read_password( machine_msg_data(msg), machine_msg_size(msg), &client_token); if (rc == -1) { od_error( &instance->logger, "auth", client, NULL, "password read error"); od_frontend_error( client, KIWI_PROTOCOL_VIOLATION, "bad password message"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } #ifdef PAM_FOUND /* support PAM authentication */ if (client->rule->auth_pam_service) { od_pam_convert_passwd(client->rule->auth_pam_data, client_token.password); rc = od_pam_auth(client->rule->auth_pam_service, client->startup.user.value, client->rule->auth_pam_data, client->io.io); kiwi_password_free(&client_token); machine_msg_free(msg); if (rc == -1) goto auth_failed; return 0; } #endif /* use remote or local password source */ kiwi_password_t client_password; kiwi_password_init(&client_password); if (client->rule->auth_query) { char peer[128]; od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); rc = od_auth_query(client->global, client->rule, peer, &client->startup.user, &client_password); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "failed to make auth_query"); od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "failed to make auth query"); kiwi_password_free(&client_token); kiwi_password_free(&client_password); machine_msg_free(msg); return -1; } if (client_password.password == NULL) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect user from %s", client->startup.database.value, client->startup.user.value, peer); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect user"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } } else { client_password.password_len = client->rule->password_len + 1; client_password.password = client->rule->password; } /* authenticate */ int check = kiwi_password_compare(&client_password, &client_token); kiwi_password_free(&client_token); machine_msg_free(msg); if (client->rule->auth_query) kiwi_password_free(&client_password); if (check) return 0; goto auth_failed; auth_failed: od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect password", client->startup.database.value, client->startup.user.value); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect password"); return -1; } static inline int od_auth_frontend_md5(od_client_t *client) { od_instance_t *instance = client->global->instance; /* generate salt */ uint32_t salt = kiwi_password_salt(&client->key, (uint32_t)machine_lrand48()); /* AuthenticationMD5Password */ machine_msg_t *msg; msg = kiwi_be_write_authentication_md5(NULL, (char *)&salt); if (msg == NULL) return -1; int rc; rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for password response */ while (1) { msg = od_read(&client->io, UINT32_MAX); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) break; machine_msg_free(msg); } /* read password message */ kiwi_password_t client_token; kiwi_password_init(&client_token); rc = kiwi_be_read_password( machine_msg_data(msg), machine_msg_size(msg), &client_token); if (rc == -1) { od_error( &instance->logger, "auth", client, NULL, "password read error"); od_frontend_error( client, KIWI_PROTOCOL_VIOLATION, "bad password message"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } /* use remote or local password source */ kiwi_password_t client_password; kiwi_password_init(&client_password); kiwi_password_t query_password; kiwi_password_init(&query_password); if (client->rule->auth_query) { char peer[128]; od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); rc = od_auth_query(client->global, client->rule, peer, &client->startup.user, &query_password); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "failed to make auth_query"); od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "failed to make auth query"); kiwi_password_free(&client_token); kiwi_password_free(&query_password); machine_msg_free(msg); return -1; } if (query_password.password == NULL) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect user from %s", client->startup.database.value, client->startup.user.value, peer); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect user"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } query_password.password_len--; } else { query_password.password_len = client->rule->password_len; query_password.password = client->rule->password; } /* prepare password hash */ rc = kiwi_password_md5(&client_password, client->startup.user.value, client->startup.user.value_len - 1, query_password.password, query_password.password_len, (char *)&salt); if (rc == -1) { od_error( &instance->logger, "auth", client, NULL, "memory allocation error"); kiwi_password_free(&client_password); kiwi_password_free(&client_token); if (client->rule->auth_query) kiwi_password_free(&query_password); machine_msg_free(msg); return -1; } /* authenticate */ int check = kiwi_password_compare(&client_password, &client_token); kiwi_password_free(&client_password); kiwi_password_free(&client_token); machine_msg_free(msg); if (client->rule->auth_query) kiwi_password_free(&query_password); if (!check) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect password", client->startup.database.value, client->startup.user.value); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect password"); return -1; } return 0; } #ifdef USE_SCRAM static inline int od_auth_frontend_scram_sha_256(od_client_t *client) { od_instance_t *instance = client->global->instance; /* request AuthenticationSASL */ machine_msg_t *msg = kiwi_be_write_authentication_sasl(NULL, "SCRAM-SHA-256"); if (msg == NULL) return -1; int rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for SASLInitialResponse */ while (1) { msg = od_read(&client->io, UINT32_MAX); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) break; machine_msg_free(msg); } /* read the SASLInitialResponse */ char *mechanism; char *auth_data; size_t auth_data_size; rc = kiwi_be_read_authentication_sasl_initial(machine_msg_data(msg), machine_msg_size(msg), &mechanism, &auth_data, &auth_data_size); if (rc == -1) { od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "malformed SASLInitialResponse message"); machine_msg_free(msg); return -1; } if (strcmp(mechanism, "SCRAM-SHA-256") != 0) { od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "unsupported SASL authorization mechanism"); machine_msg_free(msg); return -1; } /* use remote or local password source */ kiwi_password_t query_password; kiwi_password_init(&query_password); if (client->rule->auth_query) { char peer[128]; od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); rc = od_auth_query(client->global, client->rule, peer, &client->startup.user, &query_password); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "failed to make auth_query"); od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "failed to make auth query"); kiwi_password_free(&query_password); machine_msg_free(msg); return -1; } if (query_password.password == NULL) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect user from %s", client->startup.database.value, client->startup.user.value, peer); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect user"); machine_msg_free(msg); return -1; } query_password.password_len--; } else { query_password.password_len = client->rule->password_len; query_password.password = client->rule->password; } od_scram_state_t scram_state; od_scram_state_init(&scram_state); /* try to parse authentication data */ rc = od_scram_read_client_first_message( &scram_state, auth_data, auth_data_size); machine_msg_free(msg); switch (rc) { case 0: break; case -1: return -1; case -2: od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "malformed SASLInitialResponse message"); return -1; case -3: od_frontend_error(client, KIWI_FEATURE_NOT_SUPPORTED, "doesn't support channel binding at the moment"); return -1; case -4: od_frontend_error( client, KIWI_FEATURE_NOT_SUPPORTED, "doesn't support authorization identity at the moment"); return -1; case -5: od_frontend_error( client, KIWI_FEATURE_NOT_SUPPORTED, "doesn't support mandatory extensions at the moment"); return -1; } rc = od_scram_parse_verifier(&scram_state, query_password.password); if (rc == -1) rc = od_scram_init_from_plain_password(&scram_state, query_password.password); if (rc == -1) { od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "invalid user password or SCRAM secret, check your config"); return -1; } msg = od_scram_create_server_first_message(&scram_state); if (msg == NULL) { kiwi_password_free(&query_password); od_scram_state_free(&scram_state); return -1; } rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for SASLResponse */ while (1) { // TODO: here's infinite wait, need to replace it with // client_login_timeout msg = od_read(&client->io, UINT32_MAX); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) break; machine_msg_free(msg); } /* read the SASLResponse */ rc = kiwi_be_read_authentication_sasl(machine_msg_data(msg), machine_msg_size(msg), &auth_data, &auth_data_size); if (rc == -1) { od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "malformed client SASLResponse"); machine_msg_free(msg); return -1; } char *final_nonce; size_t final_nonce_size; char *client_proof; rc = od_scram_read_client_final_message(&scram_state, auth_data, auth_data_size, &final_nonce, &final_nonce_size, &client_proof); if (rc == -1) { od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "malformed client SASLResponse"); machine_msg_free(msg); return -1; } /* verify signatures */ rc = od_scram_verify_final_nonce(&scram_state, final_nonce, final_nonce_size); if (rc == -1) { od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "malformed client SASLResponse: nonce doesn't match"); machine_msg_free(msg); return -1; } rc = od_scram_verify_client_proof(&scram_state, client_proof); if (rc == -1) { od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "password authentication failed"); machine_msg_free(msg); return -1; } machine_msg_free(msg); /* SASLFinal Message */ msg = od_scram_create_server_final_message(&scram_state); if (msg == NULL) { kiwi_password_free(&query_password); od_scram_state_free(&scram_state); return -1; } rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } return 0; } #endif static inline int od_auth_frontend_cert(od_client_t *client) { od_instance_t *instance = client->global->instance; if (!client->startup.is_ssl_request) { od_error( &instance->logger, "auth", client, NULL, "TLS connection required"); od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "TLS connection required"); return -1; } /* compare client certificate common name */ od_route_t *route = client->route; int rc; if (route->rule->auth_common_name_default) { rc = machine_io_verify(client->io.io, route->rule->user_name); if (!rc) { return 0; } } od_list_t *i; od_list_foreach(&route->rule->auth_common_names, i) { od_rule_auth_t *auth; auth = od_container_of(i, od_rule_auth_t, link); rc = machine_io_verify(client->io.io, auth->common_name); if (!rc) { return 0; } } od_error(&instance->logger, "auth", client, NULL, "TLS certificate common name mismatch"); od_frontend_error( client, KIWI_INVALID_PASSWORD, "TLS certificate common name mismatch"); return -1; } static inline int od_auth_frontend_block(od_client_t *client) { od_instance_t *instance = client->global->instance; od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' is blocked", client->startup.database.value, client->startup.user.value); od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "user blocked%s%s", client->rule->db_is_default ? " (unknown database)" : "", client->rule->user_is_default ? " (unknown user)" : ""); return 0; } int od_auth_frontend(od_client_t *client) { od_instance_t *instance = client->global->instance; /* authentication mode */ int rc; switch (client->rule->auth_mode) { case OD_RULE_AUTH_CLEAR_TEXT: rc = od_auth_frontend_cleartext(client); if (rc == -1) return -1; break; case OD_RULE_AUTH_MD5: rc = od_auth_frontend_md5(client); if (rc == -1) return -1; break; #ifdef USE_SCRAM case OD_RULE_AUTH_SCRAM_SHA_256: rc = od_auth_frontend_scram_sha_256(client); if (rc == -1) return -1; break; #endif case OD_RULE_AUTH_CERT: rc = od_auth_frontend_cert(client); if (rc == -1) return -1; break; case OD_RULE_AUTH_BLOCK: od_auth_frontend_block(client); return -1; case OD_RULE_AUTH_NONE: break; default: assert(0); break; } /* pass */ machine_msg_t *msg; msg = kiwi_be_write_authentication_ok(NULL); if (msg == NULL) return -1; rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } return 0; } static inline int od_auth_backend_cleartext(od_server_t *server) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; assert(route != NULL); od_debug(&instance->logger, "auth", NULL, server, "requested clear-text authentication"); /* use storage or user password */ char *password; int password_len; if (route->rule->storage_password) { password = route->rule->storage_password; password_len = route->rule->storage_password_len; } else if (route->rule->password) { password = route->rule->password; password_len = route->rule->password_len; } else { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } /* PasswordMessage */ machine_msg_t *msg; msg = kiwi_fe_write_password(NULL, password, password_len + 1); if (msg == NULL) { od_error( &instance->logger, "auth", NULL, server, "memory allocation error"); return -1; } int rc; rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } static inline int od_auth_backend_md5(od_server_t *server, char salt[4]) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; assert(route != NULL); od_debug( &instance->logger, "auth", NULL, server, "requested md5 authentication"); /* use storage user or route user */ char *user; int user_len; if (route->rule->storage_user) { user = route->rule->storage_user; user_len = route->rule->storage_user_len; } else { user = route->rule->user_name; user_len = route->rule->user_name_len; } /* use storage or user password */ char *password; int password_len; if (route->rule->storage_password) { password = route->rule->storage_password; password_len = route->rule->storage_password_len; } else if (route->rule->password) { password = route->rule->password; password_len = route->rule->password_len; } else { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } /* prepare md5 password using server supplied salt */ kiwi_password_t client_password; kiwi_password_init(&client_password); int rc; rc = kiwi_password_md5( &client_password, user, user_len, password, password_len, salt); if (rc == -1) { od_error( &instance->logger, "auth", NULL, server, "memory allocation error"); kiwi_password_free(&client_password); return -1; } /* PasswordMessage */ machine_msg_t *msg; msg = kiwi_fe_write_password( NULL, client_password.password, client_password.password_len); kiwi_password_free(&client_password); if (msg == NULL) { od_error( &instance->logger, "auth", NULL, server, "memory allocation error"); return -1; } rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } #ifdef USE_SCRAM static inline int od_auth_backend_sasl(od_server_t *server) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; assert(route != NULL); if (server->scram_state.client_nonce != NULL) { od_error(&instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASL was already received"); return -1; } od_debug( &instance->logger, "auth", NULL, server, "requested SASL authentication"); if (!route->rule->storage_password && !route->rule->password) { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } /* SASLInitialResponse Message */ machine_msg_t *msg = od_scram_create_client_first_message(&server->scram_state); if (msg == NULL) { od_error( &instance->logger, "auth", NULL, server, "memory allocation error"); return -1; } int rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } static inline int od_auth_backend_sasl_continue(od_server_t *server, char *auth_data, size_t auth_data_size) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; assert(route != NULL); if (server->scram_state.client_nonce == NULL) { od_error(&instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASL is missing"); return -1; } if (server->scram_state.server_first_message != NULL) { od_error(&instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASLContinue was already " "received"); return -1; } /* use storage or user password */ char *password; if (route->rule->storage_password) { password = route->rule->storage_password; } else if (route->rule->password) { password = route->rule->password; } else { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } od_debug( &instance->logger, "auth", NULL, server, "continue SASL authentication"); /* SASLResponse Message */ machine_msg_t *msg = od_scram_create_client_final_message( &server->scram_state, password, auth_data, auth_data_size); if (msg == NULL) { od_error(&instance->logger, "auth", NULL, server, "malformed SASLResponse message"); return -1; } int rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } static inline int od_auth_backend_sasl_final(od_server_t *server, char *auth_data, size_t auth_data_size) { od_instance_t *instance = server->global->instance; assert(server->route); if (server->scram_state.server_first_message == NULL) { od_error(&instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASLContinue is missing"); return -1; } od_debug( &instance->logger, "auth", NULL, server, "finishing SASL authentication"); int rc = od_scram_verify_server_signature( &server->scram_state, auth_data, auth_data_size); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "server verify failed: invalid signature"); return -1; } od_scram_state_free(&server->scram_state); return 0; } #endif int od_auth_backend(od_server_t *server, machine_msg_t *msg) { od_instance_t *instance = server->global->instance; assert(*(char *)machine_msg_data(msg) == KIWI_BE_AUTHENTICATION); uint32_t auth_type; char salt[4]; char *auth_data = NULL; size_t auth_data_size = 0; int rc; rc = kiwi_fe_read_auth(machine_msg_data(msg), machine_msg_size(msg), &auth_type, salt, &auth_data, &auth_data_size); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "failed to parse authentication message"); return -1; } msg = NULL; switch (auth_type) { /* AuthenticationOk */ case 0: return 0; /* AuthenticationCleartextPassword */ case 3: rc = od_auth_backend_cleartext(server); if (rc == -1) return -1; break; /* AuthenticationMD5Password */ case 5: rc = od_auth_backend_md5(server, salt); if (rc == -1) return -1; break; #ifdef USE_SCRAM /* AuthenticationSASL */ case 10: return od_auth_backend_sasl(server); /* AuthenticationSASLContinue */ case 11: return od_auth_backend_sasl_continue( server, auth_data, auth_data_size); /* AuthenticationSASLContinue */ case 12: return od_auth_backend_sasl_final( server, auth_data, auth_data_size); #endif /* unsupported */ default: od_error(&instance->logger, "auth", NULL, server, "unsupported authentication method"); return -1; } /* wait for authentication response */ while (1) { msg = od_read(&server->io, UINT32_MAX); if (msg == NULL) { od_error(&instance->logger, "auth", NULL, server, "read error: %s", od_io_error(&server->io)); return -1; } kiwi_be_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", NULL, server, "%s", kiwi_be_type_to_string(type)); int rc; switch (type) { case KIWI_BE_AUTHENTICATION: rc = kiwi_fe_read_auth(machine_msg_data(msg), machine_msg_size(msg), &auth_type, salt, NULL, NULL); machine_msg_free(msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "failed to parse authentication message"); return -1; } if (auth_type != 0) { od_error(&instance->logger, "auth", NULL, server, "incorrect authentication flow"); return 0; } return 0; case KIWI_BE_ERROR_RESPONSE: od_backend_error( server, "auth", machine_msg_data(msg), machine_msg_size(msg)); server->error_connect = msg; return -1; default: machine_msg_free(msg); break; } } return 0; }