Exemplo n.º 1
0
static void proc_etccertsdir(const char* fullpath, struct hash* h, int tmpfile_fd)
{
	char linktarget[SYMLINK_MAX];
	ssize_t linklen;

	linklen = readlink(fullpath, linktarget, sizeof(linktarget)-1);
	if (linklen < 0)
		return;
	linktarget[linklen] = 0;

	struct hash_item *item = hash_get(h, last_component(fullpath));
	if (!item) {
		/* Symlink exists but is not wanted
		 * Delete it if it points to 'our' directory
		 */
		if (str_begins(linktarget, CERTSDIR) || str_begins(linktarget, LOCALCERTSDIR))
			unlink(fullpath);
	} else if (strcmp(linktarget, item->value) != 0) {
		/* Symlink exists but points wrong */
		unlink(fullpath);
		if (symlink(item->value, fullpath) < 0)
			fprintf(stderr, "Warning! Cannot update symlink %s -> %s\n", item->value, fullpath);
		item->value = 0;
	} else {
		/* Symlink exists and is ok */
		item->value = 0;
	}
}
Exemplo n.º 2
0
static bool read_global_ca_list(const char* file, struct hash* d, int tmpfile_fd)
{
	FILE * fp = fopen(file, "r");
	if (fp == NULL)
		return false;

	char * line = NULL;
	size_t len = 0;
	ssize_t read;

	while ((read = getline(&line, &len, fp)) != -1) {
		/* getline returns number of bytes in buffer, and buffer
		 * contains delimeter if it was found */
		if (read > 0 && line[read-1] == '\n')
			line[read-1] = 0;
		if (str_begins(line, "#") || str_begins(line, "!"))
			continue;

		char* fullpath = 0;
		if (asprintf(&fullpath,"%s%s", CERTSDIR, line) != -1) {
			proc_localglobaldir(fullpath, d, tmpfile_fd);
			free(fullpath);
		}
	}

	fclose(fp);
	free(line);
	return true;
}
Exemplo n.º 3
0
static bool test_dump_dbox(const char *path)
{
	const char *p;

	p = strrchr(path, '/');
	if (p == NULL)
		p = path;
	else
		p++;
	return str_begins(p, "m.") || str_begins(p, "u.");
}
Exemplo n.º 4
0
static bool remove_subj_leader(buffer_t *buf, size_t *start_pos,
			       bool *is_reply_or_forward_r)
{
	const char *data, *orig_data;
	bool ret = FALSE;

	/* subj-leader     = (*subj-blob subj-refwd) / WSP

	   subj-blob       = "[" *BLOBCHAR "]" *WSP
	   subj-refwd      = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":"

	   BLOBCHAR        = %x01-5a / %x5c / %x5e-7f
	                   ; any CHAR except '[' and ']' */
	orig_data = buf->data;
	orig_data += *start_pos;
	data = orig_data;

	if (*data == ' ') {
		/* independent from checks below - always removed */
		data++; orig_data++;
		*start_pos += 1;
		ret = TRUE;
	}

	while (*data == '[') {
		if (!remove_blob(&data))
			return ret;
	}

	if (str_begins(data, "RE"))
		data += 2;
	else if (str_begins(data, "FWD"))
		data += 3;
	else if (str_begins(data, "FW"))
		data += 2;
	else
		return ret;

	if (*data == ' ')
		data++;

	if (*data == '[' && !remove_blob(&data))
		return ret;

	if (*data != ':')
		return ret;

	data++;
	*start_pos += (size_t)(data - orig_data);
	*is_reply_or_forward_r = TRUE;
	return TRUE;
}
Exemplo n.º 5
0
static bool dir_readfiles(struct hash* d, const char* path,
			  filetype allowed_file_type,
			  proc_path path_processor,
			  int tmpfile_fd)
{
	DIR *dp = opendir(path);
	if (!dp)
		return false;
 
	struct dirent *dirp;
	while ((dirp = readdir(dp)) != NULL) {
		if (str_begins(dirp->d_name, "."))
			continue;

		char* fullpath = 0;
		if (asprintf(&fullpath, "%s%s", path, dirp->d_name) != -1) {
			if (is_filetype(fullpath, allowed_file_type))
				path_processor(fullpath, d, tmpfile_fd);

			free(fullpath);
		}
	}

	return closedir(dp) == 0;
}
Exemplo n.º 6
0
static int mbox_mailbox_open_existing(struct mbox_mailbox *mbox)
{
	struct mailbox *box = &mbox->box;
	const char *rootdir, *box_path = mailbox_get_path(box);
	bool move_to_memory;

	move_to_memory = want_memory_indexes(mbox->storage, box_path);

	if (box->inbox_any || strcmp(box->name, "INBOX") == 0) {
		/* if INBOX isn't under the root directory, it's probably in
		   /var/mail and we want to allow privileged dotlocking */
		rootdir = mailbox_list_get_root_forced(box->list,
						       MAILBOX_LIST_PATH_TYPE_DIR);
		if (!str_begins(box_path, rootdir))
			mbox->mbox_privileged_locking = TRUE;
	}
	if ((box->flags & MAILBOX_FLAG_KEEP_LOCKED) != 0) {
		if (mbox_lock(mbox, F_WRLCK, &mbox->mbox_global_lock_id) <= 0)
			return -1;

		if (mbox->mbox_dotlock != NULL) {
			mbox->keep_lock_to =
				timeout_add(MBOX_LOCK_TOUCH_MSECS,
					    mbox_lock_touch_timeout, mbox);
		}
	}
	return mbox_mailbox_open_finish(mbox, move_to_memory);
}
Exemplo n.º 7
0
const char *password_get_scheme(const char **password)
{
	const char *p, *scheme;

	if (*password == NULL)
		return NULL;

	if (str_begins(*password, "$1$")) {
		/* $1$<salt>$<password>[$<ignored>] */
		p = strchr(*password + 3, '$');
		if (p != NULL) {
			/* stop at next '$' after password */
			p = strchr(p+1, '$');
			if (p != NULL)
				*password = t_strdup_until(*password, p);
			return "MD5-CRYPT";
		}
	}

	if (**password != '{')
		return NULL;

	p = strchr(*password, '}');
	if (p == NULL)
		return NULL;

	scheme = t_strdup_until(*password + 1, p);
	*password = p + 1;
	return scheme;
}
Exemplo n.º 8
0
static int proxy_input_banner(struct imap_client *client,
			      struct ostream *output, const char *line)
{
	const char *const *capabilities = NULL;
	string_t *str;
	int ret;

	if (!str_begins(line, "* OK ")) {
		client_log_err(&client->common, t_strdup_printf(
			"proxy: Remote returned invalid banner: %s",
			str_sanitize(line, 160)));
		return -1;
	}

	str = t_str_new(128);
	if (str_begins(line + 5, "[CAPABILITY ")) {
		capabilities = t_strsplit(t_strcut(line + 5 + 12, ']'), " ");
		if (str_array_icase_find(capabilities, "SASL-IR"))
			client->proxy_sasl_ir = TRUE;
		if (str_array_icase_find(capabilities, "LOGINDISABLED"))
			client->proxy_logindisabled = TRUE;
		i_free(client->proxy_backend_capability);
		client->proxy_backend_capability =
			i_strdup(t_strcut(line + 5 + 12, ']'));
		if (str_array_icase_find(capabilities, "ID") &&
		    !client->common.proxy_not_trusted) {
			client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_ID;
			proxy_write_id(client, str);
			if (client->common.proxy_nopipelining) {
				/* write login or starttls after I OK */
				o_stream_nsend(output, str_data(str), str_len(str));
				return 0;
			}
		}
	}

	if ((ret = proxy_write_starttls(client, str)) < 0) {
		return -1;
	} else if (ret == 0) {
		if (proxy_write_login(client, str) < 0)
			return -1;
	}

	o_stream_nsend(output, str_data(str), str_len(str));
	return 0;
}
Exemplo n.º 9
0
static struct userdb_module *
userdb_lua_preinit(pool_t pool, const char *args)
{
	struct dlua_userdb_module *module;
	const char *cache_key = "%u";
	bool blocking = TRUE;

	module = p_new(pool, struct dlua_userdb_module, 1);
	const char *const *fields = t_strsplit_spaces(args, " ");
	while(*fields != NULL) {
		if (str_begins(*fields, "file=")) {
			 module->file = p_strdup(pool, (*fields)+5);
		} else if (str_begins(*fields, "blocking=")) {
			const char *value = (*fields)+9;
			if (strcmp(value, "yes") == 0) {
				blocking = TRUE;
			} else if (strcmp(value, "no") == 0) {
				blocking = FALSE;
			} else {
				i_fatal("Invalid value %s. "
					"Field blocking must be yes or no",
					value);
			}
		} else if (str_begins(*fields, "cache_key=")) {
			if (*((*fields)+10) != '\0')
				cache_key = (*fields)+10;
			else /* explicitly disable auth caching for lua */
				cache_key = NULL;
		} else {
			i_fatal("Unsupported parameter %s", *fields);
		}
		fields++;
	}

	if (module->file == NULL)
		i_fatal("userdb-lua: Missing mandatory file= parameter");

	module->module.blocking = blocking;
	if (cache_key != NULL) {
		module->module.default_cache_key =
			auth_cache_parse_key(pool, cache_key);
	}
	return &module->module;
}
Exemplo n.º 10
0
static void auth_server_connection_input(struct auth_server_connection *conn)
{
	struct istream *input;
	const char *line, *error;
	int ret;

	switch (i_stream_read(conn->input)) {
	case 0:
		return;
	case -1:
		/* disconnected */
		error = conn->input->stream_errno != 0 ?
			strerror(conn->input->stream_errno) : "EOF";
		auth_server_connection_reconnect(conn, error);
		return;
	case -2:
		/* buffer full - can't happen unless auth is buggy */
		i_error("BUG: Auth server sent us more than %d bytes of data",
			AUTH_SERVER_CONN_MAX_LINE_LENGTH);
		auth_server_connection_disconnect(conn, "buffer full");
		return;
	}

	if (!conn->version_received) {
		line = i_stream_next_line(conn->input);
		if (line == NULL)
			return;

		/* make sure the major version matches */
		if (!str_begins(line, "VERSION\t") ||
		    !str_uint_equals(t_strcut(line + 8, '\t'),
				     AUTH_CLIENT_PROTOCOL_MAJOR_VERSION)) {
			i_error("Authentication server not compatible with "
				"this client (mixed old and new binaries?)");
			auth_server_connection_disconnect(conn,
				"incompatible server");
			return;
		}
		conn->version_received = TRUE;
	}

	input = conn->input;
	i_stream_ref(input);
	while ((line = i_stream_next_line(input)) != NULL && !input->closed) {
		T_BEGIN {
			ret = auth_server_connection_input_line(conn, line);
		} T_END;

		if (ret < 0) {
			auth_server_connection_disconnect(conn, t_strdup_printf(
				"Received broken input: %s", line));
			break;
		}
	}
	i_stream_unref(&input);
}
Exemplo n.º 11
0
static bool user_callback(const char *reply, void *context)
{
	struct auth_request *request = context;
	enum userdb_result result;
	const char *username, *args;

	if (str_begins(reply, "FAIL\t")) {
		result = USERDB_RESULT_INTERNAL_FAILURE;
		args = reply + 5;
	} else if (str_begins(reply, "NOTFOUND\t")) {
		result = USERDB_RESULT_USER_UNKNOWN;
		args = reply + 9;
	} else if (str_begins(reply, "OK\t")) {
		result = USERDB_RESULT_OK;
		username = reply + 3;
		args = strchr(username, '\t');
		if (args == NULL)
			args = "";
		else
			username = t_strdup_until(username, args++);
		if (username[0] != '\0' && strcmp(request->user, username) != 0)
			request->user = p_strdup(request->pool, username);
	} else {
		result = USERDB_RESULT_INTERNAL_FAILURE;
		i_error("BUG: auth-worker sent invalid user reply");
		args = "";
	}

	if (*args != '\0') {
		auth_fields_import(request->userdb_reply, args, 0);
		if (auth_fields_exists(request->userdb_reply, "tempfail"))
			request->userdb_lookup_tempfailed = TRUE;
	}

        auth_request_userdb_callback(result, request);
	auth_request_unref(&request);
	return TRUE;
}
Exemplo n.º 12
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 (str_begins(entry, IMAP_METADATA_PRIVATE_PREFIX)) {
		*key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX);
		*type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
	} else {
		i_assert(str_begins(entry, IMAP_METADATA_SHARED_PREFIX));
		*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 (str_begins(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) {
		/* 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;
}
Exemplo n.º 13
0
static bool iter_callback(const char *reply, void *context)
{
	struct blocking_userdb_iterate_context *ctx = context;

	if (str_begins(reply, "*\t")) {
		if (ctx->destroyed)
			return TRUE;
		ctx->next = FALSE;
		ctx->ctx.callback(reply + 2, ctx->ctx.context);
		return ctx->next || ctx->destroyed;
	}

	if (strcmp(reply, "OK") != 0)
		ctx->ctx.failed = TRUE;
	if (!ctx->destroyed)
		ctx->ctx.callback(NULL, ctx->ctx.context);
	auth_request_unref(&ctx->ctx.auth_request);
	return TRUE;
}
Exemplo n.º 14
0
static void get_metadata_precache_fields(struct mailbox *box,
					 struct mailbox_metadata *metadata_r)
{
	const struct mail_cache_field *fields;
	unsigned int i, count;
	enum mail_fetch_field cache = 0;

	fields = mail_cache_register_get_list(box->cache,
					      pool_datastack_create(), &count);
	for (i = 0; i < count; i++) {
		const char *name = fields[i].name;

		if (str_begins(name, "hdr.") ||
		    strcmp(name, "date.sent") == 0 ||
		    strcmp(name, "imap.envelope") == 0)
			cache |= MAIL_FETCH_STREAM_HEADER;
		else if (strcmp(name, "mime.parts") == 0 ||
			 strcmp(name, "binary.parts") == 0 ||
			 strcmp(name, "imap.body") == 0 ||
			 strcmp(name, "imap.bodystructure") == 0 ||
			 strcmp(name, "body.snippet") == 0)
			cache |= MAIL_FETCH_STREAM_BODY;
		else if (strcmp(name, "date.received") == 0)
			cache |= MAIL_FETCH_RECEIVED_DATE;
		else if (strcmp(name, "date.save") == 0)
			cache |= MAIL_FETCH_SAVE_DATE;
		else if (strcmp(name, "size.virtual") == 0)
			cache |= MAIL_FETCH_VIRTUAL_SIZE;
		else if (strcmp(name, "size.physical") == 0)
			cache |= MAIL_FETCH_PHYSICAL_SIZE;
		else if (strcmp(name, "pop3.uidl") == 0)
			cache |= MAIL_FETCH_UIDL_BACKEND;
		else if (strcmp(name, "pop3.order") == 0)
			cache |= MAIL_FETCH_POP3_ORDER;
		else if (strcmp(name, "guid") == 0)
			cache |= MAIL_FETCH_GUID;
		else if (strcmp(name, "flags") == 0) {
			/* just ignore for now at least.. */
		} else if (box->storage->set->mail_debug)
			i_debug("Ignoring unknown cache field: %s", name);
	}
	metadata_r->precache_fields = cache;
}
Exemplo n.º 15
0
static bool remove_subj_fwd_hdr(buffer_t *buf, size_t *start_pos,
				bool *is_reply_or_forward_r)
{
	const char *data = buf->data;
	size_t size = buf->used;

	/* subj-fwd        = subj-fwd-hdr subject subj-fwd-trl
	   subj-fwd-hdr    = "[fwd:"
	   subj-fwd-trl    = "]" */

	if (!str_begins(data + *start_pos, "[FWD:"))
		return FALSE;

	if (data[size-2] != ']')
		return FALSE;

	*is_reply_or_forward_r = TRUE;

	buffer_set_used_size(buf, size-2);
	buffer_append_c(buf, '\0');

	*start_pos += 5;
	return TRUE;
}
Exemplo n.º 16
0
static bool client_exec_script(struct master_service_connection *conn)
{
	ARRAY_TYPE(const_string) envs;
	const char *const *args;
	string_t *input;
	void *buf;
	size_t prev_size, scanpos;
	bool header_complete = FALSE, noreply = FALSE;
	ssize_t ret;
	int status;
	pid_t pid;

	net_set_nonblock(conn->fd, FALSE);
	input = t_buffer_create(IO_BLOCK_SIZE);

	/* Input contains:

	   VERSION .. <lf>
	   [alarm=<secs> <lf>]
	   "noreply" | "-" (or anything really) <lf>

	   arg 1 <lf>
	   arg 2 <lf>
	   ...
	   <lf>
	   DATA

	   This is quite a horrible protocol. If alarm is specified, it MUST be
	   before "noreply". If "noreply" isn't given, something other string
	   (typically "-") must be given which is eaten away.
	*/		
	alarm(SCRIPT_READ_TIMEOUT_SECS);
	scanpos = 1;
	while (!header_complete) {
		const unsigned char *pos, *end;

		prev_size = input->used;
		buf = buffer_append_space_unsafe(input, IO_BLOCK_SIZE);

		/* peek in socket input buffer */
		ret = recv(conn->fd, buf, IO_BLOCK_SIZE, MSG_PEEK);
		if (ret <= 0) {
			buffer_set_used_size(input, prev_size);
			if (strchr(str_c(input), '\n') != NULL)
				script_verify_version(t_strcut(str_c(input), '\n'));

			if (ret < 0)
				i_fatal("recv(MSG_PEEK) failed: %m");

			i_fatal("recv(MSG_PEEK) failed: disconnected");
		}

		/* scan for final \n\n */
		pos = CONST_PTR_OFFSET(input->data, scanpos);
		end = CONST_PTR_OFFSET(input->data, prev_size + ret);
		for (; pos < end; pos++) {
			if (pos[-1] == '\n' && pos[0] == '\n') {
				header_complete = TRUE;
				pos++;
				break;
			}
		}
		scanpos = pos - (const unsigned char *)input->data;

		/* read data for real (up to and including \n\n) */
		ret = recv(conn->fd, buf, scanpos-prev_size, 0);
		if (prev_size+ret != scanpos) {
			if (ret < 0)
				i_fatal("recv() failed: %m");
			if (ret == 0)
				i_fatal("recv() failed: disconnected");
			i_fatal("recv() failed: size of definitive recv() differs from peek");
		}
		buffer_set_used_size(input, scanpos);
	}
	alarm(0);

	/* drop the last two LFs */
	buffer_set_used_size(input, scanpos-2);

	args = t_strsplit(str_c(input), "\n");
	script_verify_version(*args); args++;
	t_array_init(&envs, 16);
	if (*args != NULL) {
		const char *p;

		if (str_begins(*args, "alarm=")) {
			unsigned int seconds;
			if (str_to_uint(*args + 6, &seconds) < 0)
				i_fatal("invalid alarm option");
			alarm(seconds);
			args++;
		}
		while (str_begins(*args, "env_")) {
			const char *envname, *env;

			env = t_str_tabunescape(*args+4);
			p = strchr(env, '=');
			if (p == NULL)
				i_fatal("invalid environment variable");
			envname = t_strdup_until(*args+4, p);

			if (str_array_find(accepted_envs, envname))
				array_append(&envs, &env, 1);
			args++;
		}
		if (strcmp(*args, "noreply") == 0) {
			noreply = TRUE;
		}
		if (**args == '\0')
			i_fatal("empty options");
		args++;
	}
	array_append_zero(&envs);

	if (noreply) {
		/* no need to fork and check exit status */
		exec_child(conn, args, array_idx(&envs, 0));
		i_unreached();
	}

	if ((pid = fork()) == (pid_t)-1) {
		i_error("fork() failed: %m");
		return FALSE;
	}

	if (pid == 0) {
		/* child */
		exec_child(conn, args, array_idx(&envs, 0));
		i_unreached();
	}

	/* parent */

	/* check script exit status */
	if (waitpid(pid, &status, 0) < 0) {
		i_error("waitpid() failed: %m");
		return FALSE;
	} else if (WIFEXITED(status)) {
		ret = WEXITSTATUS(status);
		if (ret != 0) {
			i_error("Script terminated abnormally, exit status %d", (int)ret);
			return FALSE;
		}
	} else if (WIFSIGNALED(status)) {
		i_error("Script terminated abnormally, signal %d", WTERMSIG(status));
		return FALSE;
	} else if (WIFSTOPPED(status)) {
		i_fatal("Script stopped, signal %d", WSTOPSIG(status));
		return FALSE;
	} else {
		i_fatal("Script terminated abnormally, return status %d", status);
		return FALSE;
	}
	return TRUE;
}
Exemplo n.º 17
0
int imap_proxy_parse_line(struct client *client, const char *line)
{
	struct imap_client *imap_client = (struct imap_client *)client;
	struct ostream *output;
	string_t *str;
	const unsigned char *data;
	size_t data_len;
	const char *error;
	int ret;

	i_assert(!client->destroyed);

	output = login_proxy_get_ostream(client->login_proxy);
	if (!imap_client->proxy_seen_banner) {
		/* this is a banner */
		imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_BANNER;
		imap_client->proxy_seen_banner = TRUE;
		if (proxy_input_banner(imap_client, output, line) < 0) {
			client_proxy_failed(client, TRUE);
			return -1;
		}
		return 0;
	} else if (*line == '+') {
		/* AUTHENTICATE started. finish it. */
		if (client->proxy_sasl_client == NULL) {
			/* used literals with LOGIN command, just ignore. */
			return 0;
		}
		imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_AUTHENTICATE;
		imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_AUTH_CONTINUE;

		str = t_str_new(128);
		if (line[1] != ' ' ||
		    base64_decode(line+2, strlen(line+2), NULL, str) < 0) {
			client_log_err(client,
				"proxy: Server sent invalid base64 data in AUTHENTICATE response");
			client_proxy_failed(client, TRUE);
			return -1;
		}
		ret = dsasl_client_input(client->proxy_sasl_client,
					 str_data(str), str_len(str), &error);
		if (ret == 0) {
			ret = dsasl_client_output(client->proxy_sasl_client,
						  &data, &data_len, &error);
		}
		if (ret < 0) {
			client_log_err(client, t_strdup_printf(
				"proxy: Server sent invalid authentication data: %s",
				error));
			client_proxy_failed(client, TRUE);
			return -1;
		}
		i_assert(ret == 0);

		str_truncate(str, 0);
		base64_encode(data, data_len, str);
		str_append(str, "\r\n");

		imap_client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_AUTH_CONTINUE;
		o_stream_nsend(output, str_data(str), str_len(str));
		return 0;
	} else if (str_begins(line, "S ")) {
		imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_STARTTLS;
		imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_STARTTLS;

		if (!str_begins(line, "S OK ")) {
			/* STARTTLS failed */
			client_log_err(client, t_strdup_printf(
				"proxy: Remote STARTTLS failed: %s",
				str_sanitize(line + 5, 160)));
			client_proxy_failed(client, TRUE);
			return -1;
		}
		/* STARTTLS successful, begin TLS negotiation. */
		if (login_proxy_starttls(client->login_proxy) < 0) {
			client_proxy_failed(client, TRUE);
			return -1;
		}
		/* i/ostreams changed. */
		output = login_proxy_get_ostream(client->login_proxy);
		str = t_str_new(128);
		if (proxy_write_login(imap_client, str) < 0) {
			client_proxy_failed(client, TRUE);
			return -1;
		}
		o_stream_nsend(output, str_data(str), str_len(str));
		return 1;
	} else if (str_begins(line, "L OK ")) {
		/* Login successful. Send this line to client. */
		imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_LOGIN;
		imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_LOGIN;
		str = t_str_new(128);
		client_send_login_reply(imap_client, str, line + 5);
		o_stream_nsend(client->output, str_data(str), str_len(str));

		client_proxy_finish_destroy_client(client);
		return 1;
	} else if (str_begins(line, "L ")) {
		imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_LOGIN;
		imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_LOGIN;

		line += 2;
		if (client->set->auth_verbose) {
			const char *log_line = line;

			if (strncasecmp(log_line, "NO ", 3) == 0)
				log_line += 3;
			client_proxy_log_failure(client, log_line);
		}
#define STR_NO_IMAP_RESP_CODE_AUTHFAILED "NO ["IMAP_RESP_CODE_AUTHFAILED"]"
		if (str_begins(line, STR_NO_IMAP_RESP_CODE_AUTHFAILED)) {
			/* the remote sent a generic "authentication failed"
			   error. replace it with our one, so that in case
			   the remote is sending a different error message
			   an attacker can't find out what users exist in
			   the system. */
			client_send_reply_code(client, IMAP_CMD_REPLY_NO,
					       IMAP_RESP_CODE_AUTHFAILED,
					       AUTH_FAILED_MSG);
		} else if (str_begins(line, "NO [")) {
			/* remote sent some other resp-code. forward it. */
			client_send_raw(client, t_strconcat(
				imap_client->cmd_tag, " ", line, "\r\n", NULL));
		} else {
			/* there was no [resp-code], so remote isn't Dovecot
			   v1.2+. we could either forward the line as-is and
			   leak information about what users exist in this
			   system, or we could hide other errors than password
			   failures. since other errors are pretty rare,
			   it's safer to just hide them. they're still
			   available in logs though. */
			client_send_reply_code(client, IMAP_CMD_REPLY_NO,
					       IMAP_RESP_CODE_AUTHFAILED,
					       AUTH_FAILED_MSG);
		}

		client->proxy_auth_failed = TRUE;
		client_proxy_failed(client, FALSE);
		return -1;
	} else if (strncasecmp(line, "* CAPABILITY ", 13) == 0) {
		i_free(imap_client->proxy_backend_capability);
		imap_client->proxy_backend_capability = i_strdup(line + 13);
		return 0;
	} else if (str_begins(line, "C ")) {
		/* Reply to CAPABILITY command we sent */
		imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_CAPABILITY;
		imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_CAPABILITY;
		if (str_begins(line, "C OK ") &&
		    client->proxy_password != NULL) {
			/* pipelining was disabled, send the login now. */
			str = t_str_new(128);
			if (proxy_write_login(imap_client, str) < 0)
				return -1;
			o_stream_nsend(output, str_data(str), str_len(str));
			return 1;
		}
		return 0;
	} else if (strncasecmp(line, "I ", 2) == 0) {
		/* Reply to ID command we sent, ignore it unless
		   pipelining is disabled, in which case send
		   either STARTTLS or login */
		imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_ID;
		imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_ID;

		if (client->proxy_nopipelining) {
			str = t_str_new(128);
			if ((ret = proxy_write_starttls(imap_client, str)) < 0) {
				return -1;
			} else if (ret == 0) {
				if (proxy_write_login(imap_client, str) < 0)
					return -1;
			}
			o_stream_nsend(output, str_data(str), str_len(str));
			return 1;
		}
		return 0;
	} else if (strncasecmp(line, "* ID ", 5) == 0) {
		/* Reply to ID command we sent, ignore it */
		return 0;
	} else if (str_begins(line, "* ")) {
		/* untagged reply. just forward it. */
		client_send_raw(client, t_strconcat(line, "\r\n", NULL));
		return 0;
	} else {
		/* tagged reply, shouldn't happen. */
		client_log_err(client, t_strdup_printf(
			"proxy: Unexpected input, ignoring: %s",
			str_sanitize(line, 160)));
		return 0;
	}
}