auth-worker-client.c revision f93c833d644ecff0b0f80bee4f1cdde3e697b5c8
5e0ce63bb65db34d7f48b34bbb5545fa791781c4Timo Sirainen/* Copyright (c) 2005-2011 Dovecot authors, see the included COPYING file */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
5254d77805cd35b9356d072ba325c356c43b0d51Timo Sirainen#include "auth-common.h"
5254d77805cd35b9356d072ba325c356c43b0d51Timo Sirainen#include "base64.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "ioloop.h"
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen#include "network.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "istream.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "ostream.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "hex-binary.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "str.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "process-title.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "master-service.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "auth-request.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "auth-worker-client.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include <stdlib.h>
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#define OUTBUF_THROTTLE_SIZE (1024*10)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#define CLIENT_STATE_HANDSHAKE "handshaking"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#define CLIENT_STATE_IDLE "idling"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstruct auth_worker_client {
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen int refcount;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct auth *auth;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen int fd;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct io *io;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct istream *input;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct ostream *output;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen unsigned int version_received:1;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen unsigned int dbhash_received:1;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen unsigned int error_sent:1;
2c25e1360d4b5cc55eda969a3a7204d950de5a8fTimo Sirainen};
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstruct auth_worker_list_context {
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct auth_worker_client *client;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_request *auth_request;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct userdb_iterate_context *iter;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen bool sending, sent, done;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen};
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstruct auth_worker_client *auth_worker_client;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainenstatic bool auth_worker_client_error = FALSE;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainenstatic void auth_worker_input(struct auth_worker_client *client);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic int auth_worker_output(struct auth_worker_client *client);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenvoid auth_worker_refresh_proctitle(const char *state)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen if (!global_auth_settings->verbose_proctitle || !worker)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen return;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
01af88dfbb7a022ddb3ab9fb4159f2a4a204ead3Timo Sirainen if (auth_worker_client_error)
01af88dfbb7a022ddb3ab9fb4159f2a4a204ead3Timo Sirainen state = "error";
01af88dfbb7a022ddb3ab9fb4159f2a4a204ead3Timo Sirainen else if (auth_worker_client == NULL)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen state = "waiting for connection";
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen process_title_set(t_strdup_printf("worker: %s", state));
01af88dfbb7a022ddb3ab9fb4159f2a4a204ead3Timo Sirainen}
01af88dfbb7a022ddb3ab9fb4159f2a4a204ead3Timo Sirainen
3bbe99d30871f49610aac0417ee5951d1e740b98Timo Sirainenstatic void
3bbe99d30871f49610aac0417ee5951d1e740b98Timo Sirainenauth_worker_client_check_throttle(struct auth_worker_client *client)
3bbe99d30871f49610aac0417ee5951d1e740b98Timo Sirainen{
3bbe99d30871f49610aac0417ee5951d1e740b98Timo Sirainen if (o_stream_get_buffer_used_size(client->output) >=
3bbe99d30871f49610aac0417ee5951d1e740b98Timo Sirainen OUTBUF_THROTTLE_SIZE) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen /* stop reading new requests until client has read the pending
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen replies. */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (client->io != NULL)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen io_remove(&client->io);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen}
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
01af88dfbb7a022ddb3ab9fb4159f2a4a204ead3Timo Sirainenstatic struct auth_request *
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenworker_auth_request_new(struct auth_worker_client *client, unsigned int id,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen const char *const *args)
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen{
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_request *auth_request;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen const char *key, *value;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_request = auth_request_new_dummy();
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen client->refcount++;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_request->context = client;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_request->id = id;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen for (; *args != NULL; args++) {
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen value = strchr(*args, '=');
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (value != NULL) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen key = t_strdup_until(*args, value++);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen (void)auth_request_import(auth_request, key, value);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_request_init(auth_request);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen return auth_request;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen}
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic void auth_worker_send_reply(struct auth_worker_client *client,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen string_t *str)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (shutdown_request)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen o_stream_send_str(client->output, "SHUTDOWN\n");
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen o_stream_send(client->output, str_data(str), str_len(str));
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen}
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic void verify_plain_callback(enum passdb_result result,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_request *request)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_worker_client *client = request->context;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_stream_reply *reply;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen string_t *str;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen if (request->passdb_failure && result == PASSDB_RESULT_OK)
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen result = PASSDB_RESULT_PASSWORD_MISMATCH;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen
1060afdc2fcdf647dbb3bc11647401f1b44a3a8aTimo Sirainen reply = auth_stream_reply_init(pool_datastack_create());
1060afdc2fcdf647dbb3bc11647401f1b44a3a8aTimo Sirainen auth_stream_reply_add(reply, NULL, dec2str(request->id));
1060afdc2fcdf647dbb3bc11647401f1b44a3a8aTimo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen if (result == PASSDB_RESULT_OK)
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen auth_stream_reply_add(reply, "OK", NULL);
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen else {
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen auth_stream_reply_add(reply, "FAIL", NULL);
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen auth_stream_reply_add(reply, NULL,
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen t_strdup_printf("%d", result));
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen }
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen if (result != PASSDB_RESULT_INTERNAL_FAILURE) {
9e095dd6a77097356aca8216356d4d71ef1bea45Timo Sirainen auth_stream_reply_add(reply, NULL, request->user);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen auth_stream_reply_add(reply, NULL,
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen request->passdb_password == NULL ? "" :
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen request->passdb_password);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen if (request->extra_fields != NULL) {
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen const char *fields =
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen auth_stream_reply_export(request->extra_fields);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen auth_stream_reply_import(reply, fields);
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen }
9e095dd6a77097356aca8216356d4d71ef1bea45Timo Sirainen if (request->extra_cache_fields != NULL) {
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen const char *fields =
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_stream_reply_export(request->extra_cache_fields);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_stream_reply_import(reply, fields);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen str = auth_stream_reply_get_str(reply);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen str_append_c(str, '\n');
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen auth_worker_send_reply(client, str);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen auth_request_unref(&request);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen auth_worker_client_check_throttle(client);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen auth_worker_client_unref(&client);
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen}
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainenstatic bool
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainenauth_worker_handle_passv(struct auth_worker_client *client,
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen unsigned int id, const char *const *args)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
2767104d81e97a109f0aa9758792bfa1da325a97Timo Sirainen /* verify plaintext password */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_request *auth_request;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_passdb *passdb;
1060afdc2fcdf647dbb3bc11647401f1b44a3a8aTimo Sirainen const char *password;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen unsigned int passdb_id;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen /* <passdb id> <password> [<args>] */
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) {
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen i_error("BUG: Auth worker server sent us invalid PASSV");
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen return FALSE;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen password = args[1];
411d6baa37f31d90730e90c4a28c43e1974bbe58Timo Sirainen
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen auth_request = worker_auth_request_new(client, id, args + 2);
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen auth_request->mech_password =
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen p_strdup(auth_request->pool, password);
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen if (auth_request->user == NULL || auth_request->service == NULL) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_error("BUG: PASSV had missing parameters");
143cb2e0744e647f8fc637bbdea1106c1587a4bfTimo Sirainen auth_request_unref(&auth_request);
143cb2e0744e647f8fc637bbdea1106c1587a4bfTimo Sirainen return FALSE;
143cb2e0744e647f8fc637bbdea1106c1587a4bfTimo Sirainen }
143cb2e0744e647f8fc637bbdea1106c1587a4bfTimo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen passdb = auth_request->passdb;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen while (passdb != NULL && passdb->passdb->id != passdb_id)
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen passdb = passdb->next;
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen if (passdb == NULL) {
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen /* could be a masterdb */
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen passdb = auth_request_get_auth(auth_request)->masterdbs;
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen while (passdb != NULL && passdb->passdb->id != passdb_id)
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen passdb = passdb->next;
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen if (passdb == NULL) {
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen i_error("BUG: PASSV had invalid passdb ID");
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen auth_request_unref(&auth_request);
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen return FALSE;
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen }
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen }
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen auth_request->passdb = passdb;
cd1eef2109b4476842b7757f1d69b104196d5941Timo Sirainen passdb->passdb->iface.
cd1eef2109b4476842b7757f1d69b104196d5941Timo Sirainen verify_plain(auth_request, password, verify_plain_callback);
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen return TRUE;
cd1eef2109b4476842b7757f1d69b104196d5941Timo Sirainen}
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainenstatic void
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainenlookup_credentials_callback(enum passdb_result result,
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen const unsigned char *credentials, size_t size,
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen struct auth_request *request)
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen{
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen struct auth_worker_client *client = request->context;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct auth_stream_reply *reply;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen string_t *str;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (request->passdb_failure && result == PASSDB_RESULT_OK)
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen result = PASSDB_RESULT_PASSWORD_MISMATCH;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen reply = auth_stream_reply_init(pool_datastack_create());
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_stream_reply_add(reply, NULL, dec2str(request->id));
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (result != PASSDB_RESULT_OK) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_stream_reply_add(reply, "FAIL", NULL);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_stream_reply_add(reply, NULL,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen t_strdup_printf("%d", result));
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen } else {
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen auth_stream_reply_add(reply, "OK", NULL);
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen auth_stream_reply_add(reply, NULL, request->user);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen str = t_str_new(64);
411d6baa37f31d90730e90c4a28c43e1974bbe58Timo Sirainen str_printfa(str, "{%s.b64}", request->credentials_scheme);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen base64_encode(credentials, size, str);
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen auth_stream_reply_add(reply, NULL, str_c(str));
411d6baa37f31d90730e90c4a28c43e1974bbe58Timo Sirainen
411d6baa37f31d90730e90c4a28c43e1974bbe58Timo Sirainen if (request->extra_fields != NULL) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen const char *fields =
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen auth_stream_reply_export(request->extra_fields);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_stream_reply_import(reply, fields);
}
if (request->extra_cache_fields != NULL) {
const char *fields =
auth_stream_reply_export(request->extra_cache_fields);
auth_stream_reply_import(reply, fields);
}
}
str = auth_stream_reply_get_str(reply);
str_append_c(str, '\n');
auth_worker_send_reply(client, str);
auth_request_unref(&request);
auth_worker_client_check_throttle(client);
auth_worker_client_unref(&client);
}
static bool
auth_worker_handle_passl(struct auth_worker_client *client,
unsigned int id, const char *const *args)
{
/* lookup credentials */
struct auth_request *auth_request;
const char *scheme;
unsigned int passdb_id;
/* <passdb id> <scheme> [<args>] */
if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) {
i_error("BUG: Auth worker server sent us invalid PASSL");
return FALSE;
}
scheme = args[1];
auth_request = worker_auth_request_new(client, id, args + 2);
auth_request->credentials_scheme = p_strdup(auth_request->pool, scheme);
if (auth_request->user == NULL || auth_request->service == NULL) {
i_error("BUG: PASSL had missing parameters");
auth_request_unref(&auth_request);
return FALSE;
}
while (auth_request->passdb->passdb->id != passdb_id) {
auth_request->passdb = auth_request->passdb->next;
if (auth_request->passdb == NULL) {
i_error("BUG: PASSL had invalid passdb ID");
auth_request_unref(&auth_request);
return FALSE;
}
}
if (auth_request->passdb->passdb->iface.lookup_credentials == NULL) {
i_error("BUG: PASSL lookup not supported by given passdb");
auth_request_unref(&auth_request);
return FALSE;
}
auth_request->prefer_plain_credentials = TRUE;
auth_request->passdb->passdb->iface.
lookup_credentials(auth_request, lookup_credentials_callback);
return TRUE;
}
static void
set_credentials_callback(bool success, struct auth_request *request)
{
struct auth_worker_client *client = request->context;
string_t *str;
str = t_str_new(64);
str_printfa(str, "%u\t%s\n", request->id, success ? "OK" : "FAIL");
auth_worker_send_reply(client, str);
auth_request_unref(&request);
auth_worker_client_check_throttle(client);
auth_worker_client_unref(&client);
}
static bool
auth_worker_handle_setcred(struct auth_worker_client *client,
unsigned int id, const char *const *args)
{
struct auth_request *auth_request;
unsigned int passdb_id;
const char *creds;
/* <passdb id> <credentials> [<args>] */
if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) {
i_error("BUG: Auth worker server sent us invalid SETCRED");
return FALSE;
}
creds = args[1];
auth_request = worker_auth_request_new(client, id, args + 2);
if (auth_request->user == NULL || auth_request->service == NULL) {
i_error("BUG: SETCRED had missing parameters");
auth_request_unref(&auth_request);
return FALSE;
}
while (auth_request->passdb->passdb->id != passdb_id) {
auth_request->passdb = auth_request->passdb->next;
if (auth_request->passdb == NULL) {
i_error("BUG: SETCRED had invalid passdb ID");
auth_request_unref(&auth_request);
return FALSE;
}
}
auth_request->passdb->passdb->iface.
set_credentials(auth_request, creds, set_credentials_callback);
return TRUE;
}
static void
lookup_user_callback(enum userdb_result result,
struct auth_request *auth_request)
{
struct auth_worker_client *client = auth_request->context;
struct auth_stream_reply *reply = auth_request->userdb_reply;
string_t *str;
str = t_str_new(128);
str_printfa(str, "%u\t", auth_request->id);
switch (result) {
case USERDB_RESULT_INTERNAL_FAILURE:
str_append(str, "FAIL\t");
break;
case USERDB_RESULT_USER_UNKNOWN:
str_append(str, "NOTFOUND\t");
break;
case USERDB_RESULT_OK:
str_append(str, "OK\t");
str_append(str, auth_stream_reply_export(reply));
if (auth_request->userdb_lookup_failed)
str_append(str, "\ttempfail");
break;
}
str_append_c(str, '\n');
auth_worker_send_reply(client, str);
auth_request_unref(&auth_request);
auth_worker_client_check_throttle(client);
auth_worker_client_unref(&client);
}
static struct auth_userdb *
auth_userdb_find_by_id(struct auth_userdb *userdbs, unsigned int id)
{
struct auth_userdb *db;
for (db = userdbs; db != NULL; db = db->next) {
if (db->userdb->id == id)
return db;
}
return NULL;
}
static bool
auth_worker_handle_user(struct auth_worker_client *client,
unsigned int id, const char *const *args)
{
/* lookup user */
struct auth_request *auth_request;
unsigned int userdb_id;
/* <userdb id> [<args>] */
if (str_to_uint(args[0], &userdb_id) < 0) {
i_error("BUG: Auth worker server sent us invalid USER");
return FALSE;
}
auth_request = worker_auth_request_new(client, id, args + 1);
if (auth_request->user == NULL || auth_request->service == NULL) {
i_error("BUG: USER had missing parameters");
auth_request_unref(&auth_request);
return FALSE;
}
auth_request->userdb =
auth_userdb_find_by_id(auth_request->userdb, userdb_id);
if (auth_request->userdb == NULL) {
i_error("BUG: USER had invalid userdb ID");
auth_request_unref(&auth_request);
return FALSE;
}
auth_request->userdb->userdb->iface->
lookup(auth_request, lookup_user_callback);
return TRUE;
}
static void list_iter_deinit(struct auth_worker_list_context *ctx)
{
struct auth_worker_client *client = ctx->client;
string_t *str;
i_assert(client->io == NULL);
str = t_str_new(32);
if (ctx->auth_request->userdb->userdb->iface->
iterate_deinit(ctx->iter) < 0)
str_printfa(str, "%u\tFAIL\n", ctx->auth_request->id);
else
str_printfa(str, "%u\tOK\n", ctx->auth_request->id);
auth_worker_send_reply(client, str);
client->io = io_add(client->fd, IO_READ, auth_worker_input, client);
o_stream_set_flush_callback(client->output, auth_worker_output, client);
auth_request_unref(&ctx->auth_request);
auth_worker_client_unref(&client);
i_free(ctx);
}
static void list_iter_callback(const char *user, void *context)
{
struct auth_worker_list_context *ctx = context;
string_t *str;
if (user == NULL) {
if (ctx->sending)
ctx->done = TRUE;
else
list_iter_deinit(ctx);
return;
}
T_BEGIN {
str = t_str_new(128);
str_printfa(str, "%u\t*\t%s\n", ctx->auth_request->id, user);
o_stream_send(ctx->client->output, str_data(str), str_len(str));
} T_END;
if (ctx->sending) {
/* avoid recursively looping to this same function */
ctx->sent = TRUE;
return;
}
do {
ctx->sending = TRUE;
ctx->sent = FALSE;
ctx->auth_request->userdb->userdb->iface->
iterate_next(ctx->iter);
} while (ctx->sent &&
o_stream_get_buffer_used_size(ctx->client->output) == 0);
ctx->sending = FALSE;
if (ctx->done)
list_iter_deinit(ctx);
}
static int auth_worker_list_output(struct auth_worker_list_context *ctx)
{
int ret;
if ((ret = o_stream_flush(ctx->client->output)) < 0) {
list_iter_deinit(ctx);
return 1;
}
if (ret > 0) {
ctx->auth_request->userdb->userdb->iface->
iterate_next(ctx->iter);
}
return 1;
}
static bool
auth_worker_handle_list(struct auth_worker_client *client,
unsigned int id, const char *const *args)
{
struct auth_worker_list_context *ctx;
struct auth_userdb *userdb;
unsigned int userdb_id;
if (str_to_uint(args[0], &userdb_id) < 0) {
i_error("BUG: Auth worker server sent us invalid LIST");
return FALSE;
}
userdb = auth_userdb_find_by_id(client->auth->userdbs, userdb_id);
if (userdb == NULL) {
i_error("BUG: LIST had invalid userdb ID");
return FALSE;
}
ctx = i_new(struct auth_worker_list_context, 1);
ctx->client = client;
ctx->auth_request = worker_auth_request_new(client, id, args + 1);
ctx->auth_request->userdb = userdb;
if (ctx->auth_request->user == NULL ||
ctx->auth_request->service == NULL) {
i_error("BUG: LIST had missing parameters");
auth_request_unref(&ctx->auth_request);
i_free(ctx);
return FALSE;
}
io_remove(&ctx->client->io);
o_stream_set_flush_callback(ctx->client->output,
auth_worker_list_output, ctx);
ctx->iter = ctx->auth_request->userdb->userdb->iface->
iterate_init(ctx->auth_request, list_iter_callback, ctx);
ctx->auth_request->userdb->userdb->iface->iterate_next(ctx->iter);
return TRUE;
}
static bool
auth_worker_handle_line(struct auth_worker_client *client, const char *line)
{
const char *const *args;
unsigned int id;
bool ret = FALSE;
args = t_strsplit(line, "\t");
if (args[0] == NULL || args[1] == NULL || args[2] == NULL ||
str_to_uint(args[0], &id) < 0) {
i_error("BUG: Invalid input: %s", line);
return FALSE;
}
auth_worker_refresh_proctitle(args[1]);
if (strcmp(args[1], "PASSV") == 0)
ret = auth_worker_handle_passv(client, id, args + 2);
else if (strcmp(args[1], "PASSL") == 0)
ret = auth_worker_handle_passl(client, id, args + 2);
else if (strcmp(args[1], "SETCRED") == 0)
ret = auth_worker_handle_setcred(client, id, args + 2);
else if (strcmp(args[1], "USER") == 0)
ret = auth_worker_handle_user(client, id, args + 2);
else if (strcmp(args[1], "LIST") == 0)
ret = auth_worker_handle_list(client, id, args + 2);
else {
i_error("BUG: Auth-worker received unknown command: %s",
args[1]);
}
auth_worker_refresh_proctitle(CLIENT_STATE_IDLE);
return ret;
}
static bool auth_worker_verify_db_hash(const char *line)
{
string_t *str;
unsigned char passdb_md5[MD5_RESULTLEN];
unsigned char userdb_md5[MD5_RESULTLEN];
passdbs_generate_md5(passdb_md5);
userdbs_generate_md5(userdb_md5);
str = t_str_new(128);
str_append(str, "DBHASH\t");
binary_to_hex_append(str, passdb_md5, sizeof(passdb_md5));
str_append_c(str, '\t');
binary_to_hex_append(str, userdb_md5, sizeof(userdb_md5));
return strcmp(line, str_c(str)) == 0;
}
static void auth_worker_input(struct auth_worker_client *client)
{
char *line;
bool ret;
switch (i_stream_read(client->input)) {
case 0:
return;
case -1:
/* disconnected */
auth_worker_client_destroy(&client);
return;
case -2:
/* buffer full */
i_error("BUG: Auth worker server sent us more than %d bytes",
(int)AUTH_WORKER_MAX_LINE_LENGTH);
auth_worker_client_destroy(&client);
return;
}
if (!client->version_received) {
line = i_stream_next_line(client->input);
if (line == NULL)
return;
if (!version_string_verify(line, "auth-worker",
AUTH_WORKER_PROTOCOL_MAJOR_VERSION)) {
i_error("Auth worker not compatible with this server "
"(mixed old and new binaries?)");
auth_worker_client_destroy(&client);
return;
}
client->version_received = TRUE;
}
if (!client->dbhash_received) {
line = i_stream_next_line(client->input);
if (line == NULL)
return;
if (!auth_worker_verify_db_hash(line)) {
i_error("Auth worker sees different passdbs/userdbs "
"than auth server. Maybe config just changed "
"and this goes away automatically?");
auth_worker_client_destroy(&client);
return;
}
client->dbhash_received = TRUE;
auth_worker_refresh_proctitle(CLIENT_STATE_IDLE);
}
client->refcount++;
while ((line = i_stream_next_line(client->input)) != NULL) {
T_BEGIN {
ret = auth_worker_handle_line(client, line);
} T_END;
if (!ret) {
struct auth_worker_client *client2 = client;
auth_worker_client_destroy(&client2);
break;
}
}
auth_worker_client_unref(&client);
}
static int auth_worker_output(struct auth_worker_client *client)
{
if (o_stream_flush(client->output) < 0) {
auth_worker_client_destroy(&client);
return 1;
}
if (o_stream_get_buffer_used_size(client->output) <=
OUTBUF_THROTTLE_SIZE/3 && client->io == NULL) {
/* allow input again */
client->io = io_add(client->fd, IO_READ,
auth_worker_input, client);
}
return 1;
}
struct auth_worker_client *
auth_worker_client_create(struct auth *auth, int fd)
{
struct auth_worker_client *client;
client = i_new(struct auth_worker_client, 1);
client->refcount = 1;
client->auth = auth;
client->fd = fd;
client->input = i_stream_create_fd(fd, AUTH_WORKER_MAX_LINE_LENGTH,
FALSE);
client->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
o_stream_set_flush_callback(client->output, auth_worker_output, client);
client->io = io_add(fd, IO_READ, auth_worker_input, client);
auth_worker_refresh_proctitle(CLIENT_STATE_HANDSHAKE);
auth_worker_client = client;
if (auth_worker_client_error)
auth_worker_client_send_error();
return client;
}
void auth_worker_client_destroy(struct auth_worker_client **_client)
{
struct auth_worker_client *client = *_client;
*_client = NULL;
if (client->fd == -1)
return;
i_stream_close(client->input);
o_stream_close(client->output);
if (client->io != NULL)
io_remove(&client->io);
net_disconnect(client->fd);
client->fd = -1;
auth_worker_client_unref(&client);
auth_worker_client = NULL;
auth_worker_refresh_proctitle("");
master_service_client_connection_destroyed(master_service);
}
void auth_worker_client_unref(struct auth_worker_client **_client)
{
struct auth_worker_client *client = *_client;
*_client = NULL;
if (--client->refcount > 0)
return;
i_stream_unref(&client->input);
o_stream_unref(&client->output);
i_free(client);
}
void auth_worker_client_send_error(void)
{
auth_worker_client_error = TRUE;
if (auth_worker_client != NULL &&
!auth_worker_client->error_sent) {
o_stream_send_str(auth_worker_client->output, "ERROR\n");
auth_worker_client->error_sent = TRUE;
}
auth_worker_refresh_proctitle("");
}
void auth_worker_client_send_success(void)
{
auth_worker_client_error = FALSE;
if (auth_worker_client != NULL &&
auth_worker_client->error_sent) {
o_stream_send_str(auth_worker_client->output, "SUCCESS\n");
auth_worker_client->error_sent = FALSE;
}
auth_worker_refresh_proctitle(CLIENT_STATE_IDLE);
}