Beispiel #1
0
static void script_execute_finish(void)
{
	const char *keys_str, *username, *const *keys, *value;
	string_t *reply = t_str_new(512);
	ssize_t ret;

	keys_str = getenv(ENV_USERDB_KEYS);
	if (keys_str == NULL)
		i_fatal(ENV_USERDB_KEYS" environment missing");

	username = getenv("USER");
	if (username == NULL)
		i_fatal("USER environment missing");
	str_append(reply, username);

	for (keys = t_strsplit_spaces(keys_str, " "); *keys != NULL; keys++) {
		value = getenv(t_str_ucase(*keys));
		if (value != NULL) {
			str_append_c(reply, '\t');
			str_tabescape_write(reply,
					    t_strconcat(t_str_lcase(*keys), "=",
							value, NULL));
		}
	}
	str_append_c(reply, '\n');

	ret = fd_send(SCRIPT_COMM_FD, STDOUT_FILENO,
		      str_data(reply), str_len(reply));
	if (ret < 0)
		i_fatal("fd_send() failed: %m");
	else if (ret != (ssize_t)str_len(reply))
		i_fatal("fd_send() sent partial output");
}
static int
arg_modseq_set_ext(struct mail_search_build_context *ctx,
		   struct mail_search_arg *sarg, const char *name)
{
	const char *value;

	name = t_str_lcase(name);
	if (strncmp(name, "/flags/", 7) != 0)
		return 0;
	name += 7;

	/* set name */
	if (*name == '\\') {
		/* system flag */
		sarg->value.flags = imap_parse_system_flag(name);
		if (sarg->value.flags == 0 ||
		    sarg->value.flags == MAIL_RECENT) {
			ctx->_error = "Invalid MODSEQ system flag";
			return -1;
		}
	} else {
		sarg->value.str = p_strdup(ctx->pool, name);
	}

	/* set type */
	if (mail_search_parse_string(ctx->parser, &value) < 0)
		return -1;
	if (arg_modseq_set_type(ctx, sarg->value.modseq, value) < 0)
		return -1;
	return 1;
}
Beispiel #3
0
bool mail_user_hash(const char *username, const char *format,
		    unsigned int *hash_r, const char **error_r)
{
	unsigned char md5[MD5_RESULTLEN];
	unsigned int i, hash = 0;
	char *error_dup = NULL;
	int ret = 1;

	if (strcmp(format, "%u") == 0) {
		/* fast path */
		md5_get_digest(username, strlen(username), md5);
	} else if (strcmp(format, "%Lu") == 0) {
		/* almost as fast path */
		T_BEGIN {
			md5_get_digest(t_str_lcase(username),
				       strlen(username), md5);
		} T_END;
	} else T_BEGIN {
Beispiel #4
0
static void script_execute_finish(void)
{
	const char *keys_str, *username, *const *keys, *value;
	string_t *reply = t_str_new(512);
	ssize_t ret;

	keys_str = getenv(ENV_USERDB_KEYS);
	if (keys_str == NULL)
		i_fatal(ENV_USERDB_KEYS" environment missing");

	username = getenv("USER");
	if (username == NULL)
		i_fatal("USER environment missing");
	str_append(reply, username);

	for (keys = t_strsplit_spaces(keys_str, " "); *keys != NULL; keys++) {
		value = getenv(t_str_ucase(*keys));
		if (value != NULL) {
			str_append_c(reply, '\t');
			str_append_tabescaped(reply,
					    t_strconcat(t_str_lcase(*keys), "=",
							value, NULL));
		}
	}
	str_append_c(reply, '\n');

	/* finish by sending the fd to the mail process */
	ret = fd_send(SCRIPT_COMM_FD, STDOUT_FILENO,
		      str_data(reply), str_len(reply));
	if (ret == (ssize_t)str_len(reply)) {
		/* success */
	} else {
		if (ret < 0)
			i_error("fd_send() failed: %m");
		else
			i_error("fd_send() sent partial output");
		/* exit with 0 even though we failed. non-0 exit just makes
		   master log an unnecessary error. */
	}
}
Beispiel #5
0
static bool
imap_metadata_entry2key(struct imap_metadata_transaction *imtrans,
			const char *entry, enum mail_attribute_type *type_r,
			const char **key_r)
{
	const char *key_prefix = (imtrans->server ?
		MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER : NULL);

	/* names are case-insensitive so we'll always lowercase them */
	entry = t_str_lcase(entry);

	if (strncmp(entry, IMAP_METADATA_PRIVATE_PREFIX,
		    strlen(IMAP_METADATA_PRIVATE_PREFIX)) == 0) {
		*key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX);
		*type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
	} else {
		i_assert(strncmp(entry, IMAP_METADATA_SHARED_PREFIX,
				 strlen(IMAP_METADATA_SHARED_PREFIX)) == 0);
		*key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX);
		*type_r = MAIL_ATTRIBUTE_TYPE_SHARED;
	}
	if ((*key_r)[0] == '\0') {
		/* /private or /shared prefix has no value itself */
	} else {
		i_assert((*key_r)[0] == '/');
		*key_r += 1;
	}
	if (strncmp(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT,
		    strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) == 0) {
		/* Dovecot's internal attribute (mailbox or server).
		   don't allow accessing this. */
		return FALSE;
	}
	/* Add the server-prefix (after checking for the above internal
	   attribute). */
	if (key_prefix != NULL)
		*key_r = t_strconcat(key_prefix, *key_r, NULL);
	return TRUE;
}
Beispiel #6
0
unsigned int mail_user_hash(const char *username, const char *format)
{
	static struct var_expand_table static_tab[] = {
		{ 'u', NULL, "user" },
		{ 'n', NULL, "username" },
		{ 'd', NULL, "domain" },
		{ '\0', NULL, NULL }
	};
	struct var_expand_table *tab;
	unsigned char md5[MD5_RESULTLEN];
	unsigned int i, hash = 0;

	if (strcmp(format, "%u") == 0) {
		/* fast path */
		md5_get_digest(username, strlen(username), md5);
	} else if (strcmp(format, "%Lu") == 0) {
		/* almost as fast path */
		T_BEGIN {
			md5_get_digest(t_str_lcase(username),
				       strlen(username), md5);
		} T_END;
	} else T_BEGIN {
Beispiel #7
0
static bool
imap_metadata_entry2key(struct imap_metadata_transaction *imtrans,
			const char *entry, enum mail_attribute_type *type_r,
			const char **key_r)
{
	const char *key_prefix = (imtrans->server ?
		MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER : NULL);

	/* names are case-insensitive so we'll always lowercase them */
	entry = t_str_lcase(entry);

	if (strncmp(entry, IMAP_METADATA_PRIVATE_PREFIX,
		    strlen(IMAP_METADATA_PRIVATE_PREFIX)) == 0) {
		*key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX);
		*type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
	} else {
		i_assert(strncmp(entry, IMAP_METADATA_SHARED_PREFIX,
				 strlen(IMAP_METADATA_SHARED_PREFIX)) == 0);
		*key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX);
		*type_r = MAIL_ATTRIBUTE_TYPE_SHARED;
	}
	if ((*key_r)[0] == '\0') {
		/* /private or /shared prefix has no value itself */
	} else {
		i_assert((*key_r)[0] == '/');
		*key_r += 1;
	}
	if (key_prefix != NULL)
		*key_r = t_strconcat(key_prefix, *key_r, NULL);

	/* skip over dovecot's internal attributes. (server metadata is handled
	   inside the private metadata.) */
	return (imtrans->server ||
		strncmp(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT,
		    strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) != 0);
}
static void
cmd_mailbox_metadata_parse_key(const char *arg,
			       enum mail_attribute_type *type_r,
			       const char **key_r)
{
	arg = t_str_lcase(arg);

	if (strncmp(arg, "/private/", 9) == 0) {
		*type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
		*key_r = arg + 9;
	} else if (strncmp(arg, "/shared/", 8) == 0) {
		*type_r = MAIL_ATTRIBUTE_TYPE_SHARED;
		*key_r = arg + 8;
	} else if (strcmp(arg, "/private") == 0) {
		*type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
		*key_r = "";
	} else if (strcmp(arg, "/shared") == 0) {
		*type_r = MAIL_ATTRIBUTE_TYPE_SHARED;
		*key_r = "";
	} else {
		i_fatal_status(EX_USAGE, "Invalid metadata key '%s': "
			       "Must begin with /private or /shared", arg);
	}
}
Beispiel #9
0
static bool
test_parse_header_line(struct test_parser *parser, struct test *test,
		       const char *line, const char **error_r)
{
	struct test_connection *test_conn;
	const char *key, *value;
	unsigned int idx;

	value = strchr(line, ':');
	if (value == NULL) {
		*error_r = "Missing ':'";
		return FALSE;
	}

	for (key = value; key[-1] == ' '; key--) ;
	key = t_str_lcase(t_strdup_until(line, key));
	for (value++; *value == ' '; value++) ;

	if (strcmp(key, "capabilities") == 0) {
		test->required_capabilities = (const char *const *)
			p_strsplit_spaces(parser->pool, value, " ");
		return TRUE;
	}
	if (strcmp(key, "connections") == 0) {
		test->connection_count = strcmp(value, "n") == 0 ? 2 :
			strtoul(value, NULL, 10);
		return TRUE;
	}
	if (strncmp(key, "user ", 5) == 0 &&
	    str_to_uint(key+5, &idx) == 0 && idx != 0) {
		/* FIXME: kludgy kludgy */
		if (strcmp(value, "$user2") == 0 ||
		    strcmp(value, "${user2}") == 0) {
			test->require_user2 = TRUE;
			value = conf.username2_template;
		}
		test_conn = array_idx_modifiable(&test->connections, idx-1);
		test_conn->username = p_strdup(parser->pool, value);
		return TRUE;
	}
	if (strcmp(key, "messages") == 0) {
		test->message_count = strcmp(value, "all") == 0 ? UINT_MAX :
			strtoul(value, NULL, 10);
		return TRUE;
	}
	if (strcmp(key, "state") == 0) {
		if (strcasecmp(value, "nonauth") == 0)
			test->startup_state = TEST_STARTUP_STATE_NONAUTH;
		else if (strcasecmp(value, "auth") == 0)
			test->startup_state = TEST_STARTUP_STATE_DELETED;
		else if (strcasecmp(value, "created") == 0)
			test->startup_state = TEST_STARTUP_STATE_CREATED;
		else if (strcasecmp(value, "appended") == 0)
			test->startup_state = TEST_STARTUP_STATE_APPENDED;
		else if (strcasecmp(value, "selected") == 0)
			test->startup_state = TEST_STARTUP_STATE_SELECTED;
		else {
			*error_r = "Unknown state value";
			return FALSE;
		}
		return TRUE;
	}

	*error_r = "Unknown setting";
	return FALSE;
}
static bool cmd_compress(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client);
	const struct compression_handler *handler;
	const struct imap_arg *args;
	struct istream *old_input;
	struct ostream *old_output;
	const char *mechanism, *value;
	unsigned int level;

	/* <mechanism> */
	if (!client_read_args(cmd, 0, 0, &args))
		return FALSE;

	if (!imap_arg_get_atom(args, &mechanism) ||
	    !IMAP_ARG_IS_EOL(&args[1])) {
		client_send_command_error(cmd, "Invalid arguments.");
		return TRUE;
	}
	if (zclient->handler != NULL) {
		client_send_tagline(cmd, t_strdup_printf(
			"NO [COMPRESSIONACTIVE] COMPRESSION=%s already enabled.",
			t_str_ucase(zclient->handler->name)));
		return TRUE;
	}
	if (client->tls_compression) {
		client_send_tagline(cmd,
			"NO [COMPRESSIONACTIVE] TLS compression already enabled.");
		return TRUE;
	}

	handler = compression_lookup_handler(t_str_lcase(mechanism));
	if (handler == NULL || handler->create_istream == NULL) {
		client_send_tagline(cmd, "NO Unknown compression mechanism.");
		return TRUE;
	}

	imap_zlib_client_skip_line(client);
	client_send_tagline(cmd, "OK Begin compression.");

	value = mail_user_plugin_getenv(client->user,
					"imap_zlib_compress_level");
	if (value == NULL || str_to_uint(value, &level) < 0 ||
	    level <= 0 || level > 9)
		level = IMAP_COMPRESS_DEFAULT_LEVEL;

	old_input = client->input;
	old_output = client->output;
	client->input = handler->create_istream(old_input, FALSE);
	client->output = handler->create_ostream(old_output, level);
	/* preserve output offset so that the bytes out counter in logout
	   message doesn't get reset here */
	client->output->offset = old_output->offset;
	i_stream_unref(&old_input);
	o_stream_unref(&old_output);

	client_update_imap_parser_streams(client);
	zclient->handler = handler;
	return TRUE;
}
Beispiel #11
0
static
ssize_t i_stream_decrypt_key(struct decrypt_istream *stream, const char *malg, unsigned int rounds,
	const unsigned char *data, const unsigned char *end, buffer_t *key, size_t key_len)
{
	const char *error;
	enum dcrypt_key_type ktype;
	int keys;
	bool have_key = FALSE;
	unsigned char dgst[32];
	uint32_t val;
	buffer_t buf;

	if (data == end)
		return 0;

	keys = *data++;

	/* if we have a key, prefab the digest */
	if (stream->key_callback == NULL) {
		if (stream->priv_key == NULL) {	
			io_stream_set_error(&stream->istream.iostream, "Decryption error: no private key available");
			return -1;
		}
		buffer_create_from_data(&buf, dgst, sizeof(dgst));
		dcrypt_key_id_private(stream->priv_key, "sha256", &buf, NULL);
	}

	/* for each key */
	for(;keys>0;keys--) {
		if ((size_t)(end-data) < 1 + (ssize_t)sizeof(dgst))
			return 0;
		ktype = *data++;

		if (stream->key_callback != NULL) {
			const char *hexdgst = binary_to_hex(data, sizeof(dgst)); /* digest length */
			/* hope you going to give us right key.. */
			int ret = stream->key_callback(hexdgst, &(stream->priv_key), &error, stream->key_context);
			if (ret < 0) {
				io_stream_set_error(&stream->istream.iostream, "Private key not available: %s", error);
				return -1;
			}
			if (ret > 0) {
				dcrypt_key_ref_private(stream->priv_key);
				have_key = TRUE;
				break;
			}
		} else {
			/* see if key matches to the one we have */
			if (memcmp(dgst, data, sizeof(dgst)) == 0) {
			      	have_key = TRUE;
				break;
			}
		}
		data += sizeof(dgst);

		/* wasn't correct key, skip over some data */
		if (!get_msb32(&data, end, &val) ||
		    !get_msb32(&data, end, &val))
			return 0;
	}

	/* didn't find matching key */
	if (!have_key) {
		io_stream_set_error(&stream->istream.iostream, "Decryption error: no private key available");
		return -1;
	}

	data += sizeof(dgst);

	const unsigned char *ephemeral_key;
	uint32_t ep_key_len;
	const unsigned char *encrypted_key;
	uint32_t eklen;
	const unsigned char *ekhash;
	uint32_t ekhash_len;

	/* read ephemeral key (can be missing for RSA) */
	if (!get_msb32(&data, end, &ep_key_len) || (size_t)(end-data) < ep_key_len)
		return 0;
	ephemeral_key = data;
	data += ep_key_len;

	/* read encrypted key */
	if (!get_msb32(&data, end, &eklen) || (size_t)(end-data) < eklen)
		return 0;
	encrypted_key = data;
	data += eklen;

	/* read key data hash */
	if (!get_msb32(&data, end, &ekhash_len) || (size_t)(end-data) < ekhash_len)
		return 0;
	ekhash = data;
	data += ekhash_len;

	/* decrypt the seed */
	if (ktype == DCRYPT_KEY_RSA) {
		if (!dcrypt_rsa_decrypt(stream->priv_key, encrypted_key, eklen, key, &error)) {
			io_stream_set_error(&stream->istream.iostream, "key decryption error: %s", error);
			return -1;
		}
	} else if (ktype == DCRYPT_KEY_EC) {
		/* perform ECDHE */
		buffer_t *temp_key = buffer_create_dynamic(pool_datastack_create(), 256);
		buffer_t *secret = buffer_create_dynamic(pool_datastack_create(), 256);
		buffer_t peer_key;
		buffer_create_from_const_data(&peer_key, ephemeral_key, ep_key_len);
		if (!dcrypt_ecdh_derive_secret_local(stream->priv_key, &peer_key, secret, &error)) {
			io_stream_set_error(&stream->istream.iostream, "Key decryption error: corrupted header");
			return -1;
		}

		/* use shared secret and peer key to generate decryption key, AES-256-CBC has 32 byte key and 16 byte IV */
		if (!dcrypt_pbkdf2(secret->data, secret->used, peer_key.data, peer_key.used,
		    malg, rounds, temp_key, 32+16, &error)) {
			safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
			io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error);
			return -1;
		}

		safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
		if (temp_key->used != 32+16) {
			safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);
			io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: invalid temporary key");
			return -1;
		}
		struct dcrypt_context_symmetric *dctx;
		if (!dcrypt_ctx_sym_create("AES-256-CBC", DCRYPT_MODE_DECRYPT, &dctx, &error)) {
			safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);
			io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error);
			return -1;
		}
		const unsigned char *ptr = temp_key->data;

		/* we use ephemeral_key for IV */
		dcrypt_ctx_sym_set_key(dctx, ptr, 32);
		dcrypt_ctx_sym_set_iv(dctx, ptr+32, 16);
		safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);

		int ec = 0;
		if (!dcrypt_ctx_sym_init(dctx, &error) ||
		    !dcrypt_ctx_sym_update(dctx, encrypted_key, eklen, key, &error) ||
		    !dcrypt_ctx_sym_final(dctx, key, &error)) {
			io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: %s", error);
			ec = -1;
		}

		if (key->used != key_len) {
			io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: invalid key length");
			ec = -1;
		}

		dcrypt_ctx_sym_destroy(&dctx);
		if (ec != 0) return ec;
	} else {
		io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported key type 0x%02x", ktype);
		return -1;
	}

	/* make sure we were able to decrypt the encrypted key correctly */
	const struct hash_method *hash = hash_method_lookup(t_str_lcase(malg));
	if (hash == NULL) {
		safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used);
		io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported hash algorithm: %s", malg);
		return -1;
	}
	unsigned char hctx[hash->context_size];
	unsigned char hres[hash->digest_size];
	hash->init(hctx);
	hash->loop(hctx, key->data, key->used);
	hash->result(hctx, hres);

	for(int i = 1; i < 2049; i++) {
		uint32_t i_msb = htonl(i);

		hash->init(hctx);
		hash->loop(hctx, hres, sizeof(hres));
		hash->loop(hctx, &i_msb, sizeof(i_msb));
		hash->result(hctx, hres);
	}

	/* do the comparison */
	if (memcmp(ekhash, hres, I_MIN(ekhash_len, sizeof(hres))) != 0) {
		safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used);
		io_stream_set_error(&stream->istream.iostream, "Decryption error: corrupted header ekhash");
		return -1;
	}
	return 1;
}
Beispiel #12
0
static int
cmd_setmetadata_parse_entryvalue(struct imap_setmetadata_context *ctx,
                                 const char **entry_r,
                                 const struct imap_arg **value_r)
{
    const struct imap_arg *args;
    const char *name, *error;
    int ret;
    bool fatal;

    /* parse the entry name */
    ret = imap_parser_read_args(ctx->parser, 1,
                                IMAP_PARSE_FLAG_INSIDE_LIST, &args);
    if (ret >= 0) {
        if (ret == 0) {
            /* ')' found */
            *entry_r = NULL;
            return 1;
        }
        if (!imap_arg_get_astring(args, &name)) {
            client_send_command_error(ctx->cmd,
                                      "Entry name isn't astring");
            return -1;
        }

        ret = imap_parser_read_args(ctx->parser, 2,
                                    IMAP_PARSE_FLAG_INSIDE_LIST |
                                    IMAP_PARSE_FLAG_LITERAL_SIZE |
                                    IMAP_PARSE_FLAG_LITERAL8, &args);
    }
    if (ret < 0) {
        if (ret == -2)
            return 0;
        error = imap_parser_get_error(ctx->parser, &fatal);
        if (fatal) {
            client_disconnect_with_error(ctx->cmd->client, error);
            return -1;
        }
        client_send_command_error(ctx->cmd, error);
        return -1;
    }
    if (args[1].type == IMAP_ARG_EOL) {
        client_send_command_error(ctx->cmd, "Entry value missing");
        return -1;
    }
    if (args[1].type == IMAP_ARG_LIST) {
        client_send_command_error(ctx->cmd, "Entry value can't be a list");
        return -1;
    }
    if (!ctx->cmd_error_sent &&
            !imap_metadata_verify_entry_name(name, &error)) {
        client_send_command_error(ctx->cmd, error);
        ctx->cmd_error_sent = TRUE;
    }
    if (ctx->cmd_error_sent) {
        ctx->cmd->param_error = FALSE;
        ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;

        ctx->failed = TRUE;
        if (args[1].type == IMAP_ARG_LITERAL_SIZE) {
            /* client won't see "+ OK", so we can abort
               immediately */
            ctx->cmd->client->input_skip_line = FALSE;
            return -1;
        }
    }

    /* entry names are case-insensitive. handle this by using only
       lowercase names. */
    *entry_r = t_str_lcase(name);
    *value_r = &args[1];
    return 1;
}
int main(void)
{
	string_t *str;
	const char *user, *home, *authorized;
	const char *extra_env, *key, *value, *const *tmp;
	bool uid_found = FALSE, gid_found = FALSE;

	lib_init();
	str = t_str_new(1024);

	user = getenv("USER");
	if (user != NULL) {
		if (strchr(user, '\t') != NULL) {
			i_error("checkpassword: USER contains TAB");
			return 1;
		}
		str_printfa(str, "user="******"HOME");
	if (home != NULL) {
		if (strchr(home, '\t') != NULL) {
			i_error("checkpassword: HOME contains TAB");
			return 1;
		}
		str_printfa(str, "userdb_home=");
		str_append_tabescaped(str, home);
		str_append_c(str, '\t');
	}

	extra_env = getenv("EXTRA");
	if (extra_env != NULL) {
		for (tmp = t_strsplit(extra_env, " "); *tmp != NULL; tmp++) {
			value = getenv(*tmp);
			if (value != NULL) {
				key = t_str_lcase(*tmp);
				if (strcmp(key, "userdb_uid") == 0)
					uid_found = TRUE;
				else if (strcmp(key, "userdb_gid") == 0)
					gid_found = TRUE;
				str_append_tabescaped(str, key);
				str_append_c(str, '=');
				str_append_tabescaped(str, value);
				str_append_c(str, '\t');
			}
		}
	}
	if (!uid_found)
		str_printfa(str, "userdb_uid=%s\t",  dec2str(getuid()));
	if (!gid_found)
		str_printfa(str, "userdb_gid=%s\t",  dec2str(getgid()));

	i_assert(str_len(str) > 0);

	if (write_full(4, str_data(str), str_len(str)) < 0) {
		i_error("checkpassword: write_full() failed: %m");
		exit(111);
	}
	authorized = getenv("AUTHORIZED");
	if (authorized == NULL) {
		/* authentication */
		return 0;
	} else if (strcmp(authorized, "2") == 0) {
		/* successful passdb/userdb lookup */
		return 2;
	} else {
		i_error("checkpassword: Script doesn't support passdb/userdb lookup");
		return 111;
	}
}