summaryrefslogtreecommitdiffstats
path: root/src/director/login-connection.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/director/login-connection.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/director/login-connection.c')
-rw-r--r--src/director/login-connection.c346
1 files changed, 346 insertions, 0 deletions
diff --git a/src/director/login-connection.c b/src/director/login-connection.c
new file mode 100644
index 0000000..78de3b9
--- /dev/null
+++ b/src/director/login-connection.c
@@ -0,0 +1,346 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "net.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "llist.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "director.h"
+#include "director-request.h"
+#include "mail-host.h"
+#include "auth-client-interface.h"
+#include "auth-connection.h"
+#include "login-connection.h"
+
+#include <unistd.h>
+
+#define AUTHREPLY_PROTOCOL_MAJOR_VERSION 1
+#define AUTHREPLY_PROTOCOL_MINOR_VERSION 0
+
+struct login_connection {
+ struct login_connection *prev, *next;
+
+ int refcount;
+ enum login_connection_type type;
+
+ int fd;
+ struct io *io;
+ struct istream *input;
+ struct ostream *output;
+ struct auth_connection *auth;
+ struct director *dir;
+
+ bool handshaked:1;
+ bool destroyed:1;
+};
+
+struct login_host_request {
+ struct login_connection *conn;
+ char *line, *username;
+
+ struct ip_addr local_ip;
+ in_port_t local_port;
+ in_port_t dest_port;
+ bool director_proxy_maybe;
+};
+
+static struct login_connection *login_connections;
+
+static void auth_input_line(const char *line, void *context);
+static void login_connection_unref(struct login_connection **_conn);
+
+static void login_connection_input(struct login_connection *conn)
+{
+ struct ostream *output;
+ unsigned char buf[4096];
+ ssize_t ret;
+
+ ret = read(conn->fd, buf, sizeof(buf));
+ if (ret <= 0) {
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ return;
+ if (errno != ECONNRESET)
+ e_error(conn->dir->event, "read(login connection) failed: %m");
+ }
+ login_connection_deinit(&conn);
+ return;
+ }
+ output = auth_connection_get_output(conn->auth);
+ o_stream_nsend(output, buf, ret);
+}
+
+static void login_connection_authreply_input(struct login_connection *conn)
+{
+ bool bail = FALSE;
+ const char *line;
+
+ while (!bail && (line = i_stream_read_next_line(conn->input)) != NULL) T_BEGIN {
+ if (!conn->handshaked) {
+ if (!version_string_verify(line, "director-authreply-client",
+ AUTHREPLY_PROTOCOL_MAJOR_VERSION)) {
+ e_error(conn->dir->event, "authreply client sent invalid handshake: %s", line);
+ login_connection_deinit(&conn);
+ bail = TRUE; /* don't return from within a T_BEGIN {...} T_END */
+ } else {
+ conn->handshaked = TRUE;
+ }
+ } else {
+ auth_input_line(line, conn);
+ }
+ } T_END;
+
+ if (bail)
+ return;
+
+ if (conn->input->eof) {
+ if (conn->input->stream_errno != 0 &&
+ conn->input->stream_errno != ECONNRESET) {
+ e_error(conn->dir->event, "read(authreply connection) failed: %s",
+ i_stream_get_error(conn->input));
+ }
+ login_connection_deinit(&conn);
+ }
+}
+
+static void
+login_connection_send_line(struct login_connection *conn, const char *line)
+{
+ struct const_iovec iov[2];
+
+ if (conn->destroyed)
+ return;
+
+ iov[0].iov_base = line;
+ iov[0].iov_len = strlen(line);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
+ o_stream_nsendv(conn->output, iov, N_ELEMENTS(iov));
+}
+
+static bool login_host_request_is_self(struct login_host_request *request,
+ const struct ip_addr *dest_ip)
+{
+ if (!net_ip_compare(dest_ip, &request->local_ip))
+ return FALSE;
+ if (request->dest_port != 0 && request->local_port != 0 &&
+ request->dest_port != request->local_port)
+ return FALSE;
+ return TRUE;
+}
+
+static void
+login_host_callback(const struct mail_host *host, const char *hostname,
+ const char *errormsg, void *context)
+{
+ struct login_host_request *request = context;
+ struct director *dir = request->conn->dir;
+ const char *line, *line_params;
+ unsigned int secs;
+
+ if (host == NULL) {
+ if (str_begins(request->line, "OK\t"))
+ line_params = request->line + 3;
+ else if (str_begins(request->line, "PASS\t"))
+ line_params = request->line + 5;
+ else
+ i_panic("BUG: Unexpected line: %s", request->line);
+
+ e_error(dir->event, "director: User %s host lookup failed: %s",
+ request->username, errormsg);
+ line = t_strconcat("FAIL\t", t_strcut(line_params, '\t'),
+ "\tcode="AUTH_CLIENT_FAIL_CODE_TEMPFAIL, NULL);
+ } else if (request->director_proxy_maybe &&
+ login_host_request_is_self(request, &host->ip)) {
+ line = request->line;
+ } else {
+ string_t *str = t_str_new(64);
+ char secs_buf[MAX_INT_STRLEN];
+
+ secs = dir->set->director_user_expire / 2;
+ str_append(str, request->line);
+ str_append(str, "\tproxy_refresh=");
+ str_append(str, dec2str_buf(secs_buf, secs));
+ str_append(str, "\thost=");
+ if (hostname == NULL || hostname[0] == '\0')
+ str_append(str, host->ip_str);
+ else {
+ str_append(str, hostname);
+ str_append(str, "\thostip=");
+ str_append(str, host->ip_str);
+ }
+ line = str_c(str);
+ }
+ login_connection_send_line(request->conn, line);
+
+ login_connection_unref(&request->conn);
+ i_free(request->username);
+ i_free(request->line);
+ i_free(request);
+}
+
+static void auth_input_line(const char *line, void *context)
+{
+ struct login_connection *conn = context;
+ struct login_host_request *request, temp_request;
+ const char *const *args, *line_params, *username = NULL, *tag = "";
+ bool proxy = FALSE, host = FALSE;
+
+ if (line == NULL) {
+ /* auth connection died -> kill also this login connection */
+ login_connection_deinit(&conn);
+ return;
+ }
+ if (conn->type != LOGIN_CONNECTION_TYPE_USERDB &&
+ str_begins(line, "OK\t"))
+ line_params = line + 3;
+ else if (conn->type == LOGIN_CONNECTION_TYPE_USERDB &&
+ str_begins(line, "PASS\t"))
+ line_params = line + 5;
+ else {
+ login_connection_send_line(conn, line);
+ return;
+ }
+
+ /* OK <id> [<parameters>] */
+ args = t_strsplit_tabescaped(line_params);
+ if (*args != NULL) {
+ /* we should always get here, but in case we don't just
+ forward as-is and let login process handle the error. */
+ args++;
+ }
+
+ i_zero(&temp_request);
+ for (; *args != NULL; args++) {
+ if (str_begins(*args, "proxy") &&
+ ((*args)[5] == '=' || (*args)[5] == '\0'))
+ proxy = TRUE;
+ else if (str_begins(*args, "host="))
+ host = TRUE;
+ else if (str_begins(*args, "lip=")) {
+ if (net_addr2ip((*args) + 4, &temp_request.local_ip) < 0)
+ e_error(conn->dir->event, "auth sent invalid lip field: %s", (*args) + 6);
+ } else if (str_begins(*args, "lport=")) {
+ if (net_str2port((*args) + 6, &temp_request.local_port) < 0)
+ e_error(conn->dir->event, "auth sent invalid lport field: %s", (*args) + 6);
+ } else if (str_begins(*args, "port=")) {
+ if (net_str2port((*args) + 5, &temp_request.dest_port) < 0)
+ e_error(conn->dir->event, "auth sent invalid port field: %s", (*args) + 6);
+ } else if (str_begins(*args, "destuser="))
+ username = *args + 9;
+ else if (str_begins(*args, "director_tag="))
+ tag = *args + 13;
+ else if (str_begins(*args, "director_proxy_maybe") &&
+ ((*args)[20] == '=' || (*args)[20] == '\0'))
+ temp_request.director_proxy_maybe = TRUE;
+ else if (str_begins(*args, "user=")) {
+ if (username == NULL)
+ username = *args + 5;
+ }
+ }
+ if ((!proxy && !temp_request.director_proxy_maybe) ||
+ host || username == NULL) {
+ login_connection_send_line(conn, line);
+ return;
+ }
+ if (*conn->dir->set->master_user_separator != '\0') {
+ /* with master user logins we still want to use only the
+ login username */
+ username = t_strcut(username,
+ *conn->dir->set->master_user_separator);
+ }
+
+ /* we need to add the host. the lookup might be asynchronous */
+ request = i_new(struct login_host_request, 1);
+ *request = temp_request;
+ request->conn = conn;
+ request->line = i_strdup(line);
+ request->username = i_strdup(username);
+
+ conn->refcount++;
+ director_request(conn->dir, username, tag, login_host_callback, request);
+}
+
+struct login_connection *
+login_connection_init(struct director *dir, int fd,
+ struct auth_connection *auth,
+ enum login_connection_type type)
+{
+ struct login_connection *conn;
+
+ conn = i_new(struct login_connection, 1);
+ conn->refcount = 1;
+ conn->fd = fd;
+ conn->dir = dir;
+ conn->output = o_stream_create_fd(conn->fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->output, TRUE);
+ if (type != LOGIN_CONNECTION_TYPE_AUTHREPLY) {
+ i_assert(auth != NULL);
+ conn->auth = auth;
+ conn->io = io_add(conn->fd, IO_READ,
+ login_connection_input, conn);
+ auth_connection_set_callback(conn->auth, auth_input_line, conn);
+ } else {
+ i_assert(auth == NULL);
+ conn->input = i_stream_create_fd(conn->fd, IO_BLOCK_SIZE);
+ conn->io = io_add(conn->fd, IO_READ,
+ login_connection_authreply_input, conn);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "VERSION\tdirector-authreply-server\t%d\t%d\n",
+ AUTHREPLY_PROTOCOL_MAJOR_VERSION,
+ AUTHREPLY_PROTOCOL_MINOR_VERSION));
+ }
+ conn->type = type;
+
+ DLLIST_PREPEND(&login_connections, conn);
+ return conn;
+}
+
+void login_connection_deinit(struct login_connection **_conn)
+{
+ struct login_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ if (conn->destroyed)
+ return;
+ conn->destroyed = TRUE;
+
+ DLLIST_REMOVE(&login_connections, conn);
+ io_remove(&conn->io);
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ if (close(conn->fd) < 0)
+ e_error(conn->dir->event, "close(login connection) failed: %m");
+ conn->fd = -1;
+
+ if (conn->auth != NULL)
+ auth_connection_deinit(&conn->auth);
+ login_connection_unref(&conn);
+
+ master_service_client_connection_destroyed(master_service);
+}
+
+static void login_connection_unref(struct login_connection **_conn)
+{
+ struct login_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ i_assert(conn->refcount > 0);
+ if (--conn->refcount == 0)
+ i_free(conn);
+}
+
+void login_connections_deinit(void)
+{
+ while (login_connections != NULL) {
+ struct login_connection *conn = login_connections;
+
+ login_connection_deinit(&conn);
+ }
+}