From da30ffe01ac3e2c9fdab86edbd9ac357bdba24fd Mon Sep 17 00:00:00 2001 From: Shaun Johnson Date: Tue, 1 Mar 2022 13:49:37 -0800 Subject: [PATCH 1/4] lib-imap: Added IMAP Capability List API Added a new API to lib-imap which exposes functions for manipulating a capability list. This addition is the first step towards conditionally displaying capabilities. For example, displaying certain capabilities to the client only after TLS has been negotiated. --- src/lib-imap/Makefile.am | 2 + src/lib-imap/imap-capability-list.c | 143 ++++++++++++++++++++++++++++ src/lib-imap/imap-capability-list.h | 76 +++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 src/lib-imap/imap-capability-list.c create mode 100644 src/lib-imap/imap-capability-list.h diff --git a/src/lib-imap/Makefile.am b/src/lib-imap/Makefile.am index 2f59f93770..a929f3d1d3 100644 --- a/src/lib-imap/Makefile.am +++ b/src/lib-imap/Makefile.am @@ -10,6 +10,7 @@ libimap_la_SOURCES = \ imap-arg.c \ imap-base-subject.c \ imap-bodystructure.c \ + imap-capability-list.c \ imap-date.c \ imap-envelope.c \ imap-id.c \ @@ -26,6 +27,7 @@ headers = \ imap-arg.h \ imap-base-subject.h \ imap-bodystructure.h \ + imap-capability-list.h \ imap-date.h \ imap-envelope.h \ imap-id.h \ diff --git a/src/lib-imap/imap-capability-list.c b/src/lib-imap/imap-capability-list.c new file mode 100644 index 0000000000..db5abab7c1 --- /dev/null +++ b/src/lib-imap/imap-capability-list.c @@ -0,0 +1,143 @@ +#include "imap-capability-list.h" +#include "array.h" +#include "str.h" + +struct imap_capability_list * +imap_capability_list_create(const char *initial_entries) +{ + /* allocate the pool for the capability list */ + pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"Capability List", 512); + /* initialize a capability list object */ + struct imap_capability_list * + capability_list = p_new(pool, struct imap_capability_list, 1); + capability_list->refcount = 1; + /* initialize the capability list memory pool */ + capability_list->pool = pool; + /* initialize the capability list capability array */ + p_array_init(&(capability_list->imap_capabilities), pool, 64); + + /* make sure our initial entries are appended */ + if (initial_entries != NULL) + imap_capability_list_append_string(capability_list, initial_entries); + + return capability_list; +} + +void imap_capability_list_ref(struct imap_capability_list *capability_list) +{ + i_assert(capability_list->refcount > 0); + + capability_list->refcount++; +} + +void imap_capability_list_unref(struct imap_capability_list **_capability_list) +{ + struct imap_capability_list *capability_list = *_capability_list; + + *_capability_list = NULL; + i_assert(capability_list->refcount > 0); + + if (--capability_list->refcount > 0) + return; + + pool_unref(&capability_list->pool); +} + +void imap_capability_list_add(struct imap_capability_list *capability_list, + const char *name, + enum imap_capability_visibility visibility) +{ + /* make sure it isn't already in the list */ + i_assert(imap_capability_list_find(capability_list, name) == NULL); + + struct imap_capability capability; + /* initialize the capability object */ + capability.name = p_strdup(capability_list->pool, name); + capability.visibility = visibility; + + /* append new capability object to array */ + array_push_back(&capability_list->imap_capabilities, &capability); +} + +void imap_capability_list_remove(struct imap_capability_list *capability_list, + const char *name) +{ + /* iterate array of capabilities and find our capability */ + unsigned int i, count; + const struct imap_capability *cap = + array_get(&capability_list->imap_capabilities, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(cap[i].name, name) == 0) { + array_delete(&capability_list->imap_capabilities, i, 1); + return; + } + } + i_unreached(); +} + +void imap_capability_list_append_string(struct imap_capability_list *capability_list, + const char *name_list) +{ + T_BEGIN { + /* split the name list by space */ + const char **strings = t_strsplit_spaces(name_list, " "); + /* iterate each name and append it to list as ALWAYS */ + while (*strings != NULL) { + imap_capability_list_add(capability_list, *strings, + IMAP_CAP_VISIBILITY_ALWAYS); + strings++; + } + } T_END; +} + +static int +imap_capability_bsearch(const char *name, const struct imap_capability *cap) +{ + return strcasecmp(name, cap->name); +} + +struct imap_capability * +imap_capability_list_find(struct imap_capability_list *capability_list, + const char *name) +{ + return array_bsearch(&capability_list->imap_capabilities, + name, imap_capability_bsearch); +} + +void imap_capability_list_get_capability(struct imap_capability_list *capability_list, + string_t *cap_str, + enum imap_capability_visibility visibility) +{ + /* iterate capability list and append each capability if conditions met */ + const struct imap_capability *capp; + array_foreach(&capability_list->imap_capabilities, capp) { + /* local visibility variable will get filled with all the + visibility flags that matched the current conditions -- + then at the end if FLAG_REQUIRE_ALL is present the local + visibility variable is compared against the capability + flags to check for a perfect match */ + if (capp->visibility) { + /* grab the visibility of the current cap into a tmp variable */ + enum imap_capability_visibility tmp_visibility = capp->visibility; + /* strip off the REQUIRE_ALL and any visibility flags that don't match */ + tmp_visibility &= (visibility & ~IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL); + + bool add = FALSE; + /* now check if IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL is present */ + if ((capp->visibility & IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL) != 0) { + /* did we match all? */ + if ((capp->visibility & ~IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL) == tmp_visibility) + add = TRUE; + /* no IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL - did we match any visibility flags? */ + } else if (tmp_visibility > 0) + add = TRUE; + + /* should we add this entry? */ + if (add) { + if (str_len(cap_str) != 0) + str_append_c(cap_str, ' '); + str_append(cap_str, capp->name); + } + } + } +} diff --git a/src/lib-imap/imap-capability-list.h b/src/lib-imap/imap-capability-list.h new file mode 100644 index 0000000000..3892cf2fe9 --- /dev/null +++ b/src/lib-imap/imap-capability-list.h @@ -0,0 +1,76 @@ +#ifndef IMAP_CAPABILITY_LIST_H +#define IMAP_CAPABILITY_LIST_H + +#include "lib.h" /* for BIT(n) */ + +/* bitflags to represent different conditions of + the current imap session */ +enum imap_capability_visibility { + IMAP_CAP_VISIBILITY_NEVER = 0, /* never show this capability */ + IMAP_CAP_VISIBILITY_PREAUTH = BIT(0), /* show this cap before login */ + IMAP_CAP_VISIBILITY_POSTAUTH = BIT(1), /* show this cap after login */ + IMAP_CAP_VISIBILITY_TLS_ACTIVE = BIT(2), /* show this cap if tls/ssl is active */ + IMAP_CAP_VISIBILITY_TLS_INACTIVE = BIT(3), /* show this cap if tls/ssl is inactive */ + IMAP_CAP_VISIBILITY_NO_LOGIN = BIT(6), /* show this cap if login is disabled */ + /* NOTE: connections from localhost are 'secure' regardless of SSL/TLS! */ + IMAP_CAP_VISIBILITY_SECURE = BIT(7), /* show this cap if we are secured */ + IMAP_CAP_VISIBILITY_INSECURE = BIT(8), /* show this cap if we aren't secured */ + + /* IMAP_CAP_VISIBILITY_REQUIRE_ALL indicates that the rest + of the conditions must be met *exactly* to show this + capability, otherwise if *at least* 1 condition is met + the capability will be shown */ + IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL = BIT(30), + + IMAP_CAP_VISIBILITY_ALWAYS = 0x3FFFFFFF /* always show this cap */ +}; + +/* This structure describes a capability word that appears + in the imap and imap-login capability string. + The visibility indicates when and under which conditions + the capability word should be displayed */ +struct imap_capability { + const char *name; + enum imap_capability_visibility visibility; +}; + +/* define our array type for the imap capability array */ +ARRAY_DEFINE_TYPE(imap_capability_array, struct imap_capability); + +/* define the structure that holds the capability pool and capability array */ +struct imap_capability_list { + /* reference count */ + int refcount; + /* imap capability list memory pool */ + pool_t pool; + /* array of imap_capability objects */ + ARRAY_TYPE(imap_capability_array) imap_capabilities; +}; + +/* initialize the imap capability list */ +struct imap_capability_list * +imap_capability_list_create(const char *initial_entries); +/* increment reference to an imap capability list */ +void imap_capability_list_ref(struct imap_capability_list *capability_list); +/* decrement reference to an imap capability list */ +void imap_capability_list_unref(struct imap_capability_list **capability_list); +/* add an entry to the imap capability list */ +void imap_capability_list_add(struct imap_capability_list *capability_list, + const char *name, + enum imap_capability_visibility visibility); +/* remove an entry from the imap capability list */ +void imap_capability_list_remove(struct imap_capability_list *capability_list, + const char *name); +/* split a string by space and add each entry to the imap capability list */ +void imap_capability_list_append_string(struct imap_capability_list *capability_list, + const char *name_list); +/* find an imap capability by name */ +struct imap_capability * +imap_capability_list_find(struct imap_capability_list *capability_list, + const char *name); +/* append the capabilities to cap_str based on given IMAP_CAP_VISIBILITY_ visibility */ +void imap_capability_list_get_capability(struct imap_capability_list *capability_list, + string_t *cap_str, + enum imap_capability_visibility visibility); + +#endif From 12f4a06fd5df728eff02943f0261435a73adbb5d Mon Sep 17 00:00:00 2001 From: Shaun Johnson Date: Tue, 1 Mar 2022 14:03:47 -0800 Subject: [PATCH 2/4] imap-login: Integrated new capability list API Updated imap-login to use the new capability list API from lib-imap which will allow for pre-login IMAP capabilities to be displayed conditionally. --- src/imap-login/imap-login-client.c | 90 +++++++++++++++++++++++------- src/imap-login/imap-login-client.h | 1 + 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/imap-login/imap-login-client.c b/src/imap-login/imap-login-client.c index 29ade52e50..cb439b5fb6 100644 --- a/src/imap-login/imap-login-client.c +++ b/src/imap-login/imap-login-client.c @@ -20,6 +20,7 @@ #include "imap-quote.h" #include "imap-login-commands.h" #include "imap-login-settings.h" +#include "imap-capability-list.h" #if LOGIN_MAX_INBUF_SIZE < 1024+2 # error LOGIN_MAX_INBUF_SIZE too short to fit all ID command parameters @@ -92,31 +93,31 @@ static const char *get_capability(struct client *client) { struct imap_client *imap_client = (struct imap_client *)client; string_t *cap_str = t_str_new(256); - bool explicit_capability = FALSE; - if (*imap_client->set->imap_capability == '\0') - str_append(cap_str, CAPABILITY_BANNER_STRING); - else if (*imap_client->set->imap_capability != '+') { - explicit_capability = TRUE; - str_append(cap_str, imap_client->set->imap_capability); - } else { - str_append(cap_str, CAPABILITY_BANNER_STRING); - str_append_c(cap_str, ' '); - str_append(cap_str, imap_client->set->imap_capability + 1); - } + /* imap-login is preauth by definition */ + enum imap_capability_visibility visibility = IMAP_CAP_VISIBILITY_PREAUTH; - if (!explicit_capability) { - if (imap_client->set->imap_literal_minus) - str_append(cap_str, " LITERAL-"); - else - str_append(cap_str, " LITERAL+"); - } + /* is tls/ssl enabled? */ + if (client->tls) + visibility |= IMAP_CAP_VISIBILITY_TLS_ACTIVE; + else + visibility |= IMAP_CAP_VISIBILITY_TLS_INACTIVE; + + /* is login cmd disabled? */ + if (is_login_cmd_disabled(client)) + visibility |= IMAP_CAP_VISIBILITY_NO_LOGIN; + + /* Are we secured? (localhost? tls? etc) */ + if (imap_client->common.secured) + visibility |= IMAP_CAP_VISIBILITY_SECURE; + else + visibility |= IMAP_CAP_VISIBILITY_INSECURE; - if (client_is_tls_enabled(client) && !client->tls) - str_append(cap_str, " STARTTLS"); - if (is_login_cmd_disabled(client)) - str_append(cap_str, " LOGINDISABLED"); + /* build capability string based on IMAP_CAP_VISIBILITY_ flags */ + imap_capability_list_get_capability(imap_client->capability_list, + cap_str, visibility); + /* grab the AUTH= capabilities from auth server */ client_authenticate_get_capabilities(client, cap_str); return str_c(cap_str); } @@ -374,6 +375,7 @@ static struct client *imap_client_alloc(pool_t pool) static void imap_client_create(struct client *client, void **other_sets) { struct imap_client *imap_client = (struct imap_client *)client; + bool explicit_capability = FALSE; imap_client->set = other_sets[0]; imap_client->parser = @@ -382,6 +384,51 @@ static void imap_client_create(struct client *client, void **other_sets) IMAP_LOGIN_MAX_LINE_LENGTH); if (imap_client->set->imap_literal_minus) imap_parser_enable_literal_minus(imap_client->parser); + + /* create our capability list from CAPABILITY_BANNER_STRING */ + imap_client->capability_list = + imap_capability_list_create(NULL); + + if (*imap_client->set->imap_capability == '\0') + imap_capability_list_append_string(imap_client->capability_list, + CAPABILITY_BANNER_STRING); + else if (*imap_client->set->imap_capability != '+') { + imap_capability_list_append_string(imap_client->capability_list, + imap_client->set->imap_capability); + explicit_capability = TRUE; + } else { + /* add the capability banner string to the cap list */ + imap_capability_list_append_string(imap_client->capability_list, + CAPABILITY_BANNER_STRING); + /* add everything after the plus to our cap list */ + imap_capability_list_append_string(imap_client->capability_list, + imap_client->set->imap_capability + 1); + } + + /* Add the LITERAL+/- if the capability isn't explicitly set */ + if (!explicit_capability) { + if (imap_client->set->imap_literal_minus) + imap_capability_list_add(imap_client->capability_list, "LITERAL-", + IMAP_CAP_VISIBILITY_ALWAYS); + else + imap_capability_list_add(imap_client->capability_list, "LITERAL+", + IMAP_CAP_VISIBILITY_ALWAYS); + } + + /* add STARTTLS to cap list if it is needed */ + if (client_is_tls_enabled(client)) { + imap_capability_list_add(imap_client->capability_list, "STARTTLS", + IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL | + IMAP_CAP_VISIBILITY_PREAUTH | + IMAP_CAP_VISIBILITY_TLS_INACTIVE); + } + + /* add LOGINDISABLED to cap list */ + imap_capability_list_add(imap_client->capability_list, "LOGINDISABLED", + IMAP_CAP_VISIBILITY_FLAG_REQUIRE_ALL | + IMAP_CAP_VISIBILITY_PREAUTH | + IMAP_CAP_VISIBILITY_NO_LOGIN); + client->io = io_add_istream(client->input, client_input, client); } @@ -389,6 +436,7 @@ static void imap_client_destroy(struct client *client) { struct imap_client *imap_client = (struct imap_client *)client; + imap_capability_list_unref(&imap_client->capability_list); i_free_and_null(imap_client->proxy_backend_capability); imap_parser_unref(&imap_client->parser); } diff --git a/src/imap-login/imap-login-client.h b/src/imap-login/imap-login-client.h index 002829ba3a..65f7969b2c 100644 --- a/src/imap-login/imap-login-client.h +++ b/src/imap-login/imap-login-client.h @@ -56,6 +56,7 @@ struct imap_client { const struct imap_login_settings *set; struct imap_parser *parser; + struct imap_capability_list *capability_list; char *proxy_backend_capability; const char *cmd_tag, *cmd_name; From 9c6913154458f3fb1b9f308450898c8ade67c098 Mon Sep 17 00:00:00 2001 From: Shaun Johnson Date: Tue, 1 Mar 2022 14:07:10 -0800 Subject: [PATCH 3/4] imap: Added secured and ssl_secured flags This adds two new boolean fields: 'secured', and 'ssl_secured' to the imap client structure, these reflect the values of conn_secured and conn_ssl_secured respectively. This is a prerequisite to integrating the imap_capability_list APIs into imap because imap needs to distinguish between secured/tls/non-secured connections when determining whether conditional capabilities should be displayed or not. --- src/imap/imap-client.h | 2 ++ src/imap/main.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/imap/imap-client.h b/src/imap/imap-client.h index af8af980c7..5a1348e85b 100644 --- a/src/imap/imap-client.h +++ b/src/imap/imap-client.h @@ -238,6 +238,8 @@ struct client { bool mailbox_examined:1; bool anvil_sent:1; bool tls_compression:1; + bool secured:1; + bool ssl_secured:1; bool input_skip_line:1; /* skip all the data until we've found a new line */ bool modseqs_sent_since_sync:1; diff --git a/src/imap/main.c b/src/imap/main.c index 0bbed8a423..620f0be5a0 100644 --- a/src/imap/main.c +++ b/src/imap/main.c @@ -298,6 +298,8 @@ int client_create_from_input(const struct mail_storage_service_input *input, event, mail_user, user, imap_set, smtp_set); client->userdb_fields = input->userdb_fields == NULL ? NULL : p_strarray_dup(client->pool, input->userdb_fields); + client->secured = input->conn_secured; + client->ssl_secured = input->conn_ssl_secured; event_unref(&event); *client_r = client; return 0; From 905ec7d0dc50136a0b3ab03b13768915ec8d2a4e Mon Sep 17 00:00:00 2001 From: Shaun Johnson Date: Tue, 1 Mar 2022 14:30:31 -0800 Subject: [PATCH 4/4] imap: Integrated new capability list API Updated imap to use the new capability list API from lib-imap which will allow for post-login imap capabilities to be displayed conditionally. This ensures functionality of client_add_capability() does not change, and it introduces a new imap function client_get_capability() which is now used everywhere that client->capability_string was previously used. --- src/imap/cmd-capability.c | 2 +- src/imap/imap-client.c | 52 ++++++++++++++++++++++++++++++++------- src/imap/imap-client.h | 5 +++- src/imap/main.c | 3 ++- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/imap/cmd-capability.c b/src/imap/cmd-capability.c index 3433c89500..56e1d0c334 100644 --- a/src/imap/cmd-capability.c +++ b/src/imap/cmd-capability.c @@ -7,7 +7,7 @@ bool cmd_capability(struct client_command_context *cmd) { client_send_line(cmd->client, t_strconcat( - "* CAPABILITY ", str_c(cmd->client->capability_string), NULL)); + "* CAPABILITY ", client_gete_capability(cmd->client), NULL)); client_send_tagline(cmd, "OK Capability completed."); return TRUE; diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c index 1ae80b2728..d623629a0e 100644 --- a/src/imap/imap-client.c +++ b/src/imap/imap-client.c @@ -26,6 +26,7 @@ #include "imap-notify.h" #include "imap-commands.h" #include "imap-feature.h" +#include "imap-capability-list.h" #include @@ -157,17 +158,21 @@ struct client *client_create(int fd_in, int fd_out, bool unhibernated, client->notify_flag_changes = TRUE; p_array_init(&client->enabled_features, client->pool, 8); - client->capability_string = - str_new(client->pool, sizeof(CAPABILITY_STRING)+64); + client->capability_string = imap_capability_list_create(NULL); if (*set->imap_capability == '\0') - str_append(client->capability_string, CAPABILITY_STRING); + imap_capability_list_append_string(client->capability_list, + CAPABILITY_STRING); else if (*set->imap_capability != '+') { - str_append(client->capability_string, set->imap_capability); + imap_capability_list_append_string(client->capability_list, + set->imap_capability); } else { - str_append(client->capability_string, CAPABILITY_STRING); - str_append_c(client->capability_string, ' '); - str_append(client->capability_string, set->imap_capability + 1); + /* add the capability banner string to the cap list */ + imap_capability_list_append_string(client->capability_list, + CAPABILITY_STRING); + /* add everything after the plus to our cap list */ + imap_capability_list_append_string(client->capability_list, + client->set->imap_capability + 1); } if (client->set->imap_literal_minus) client_add_capability(client, "LITERAL-"); @@ -586,10 +591,39 @@ void client_add_capability(struct client *client, const char *capability) /* explicit capability - don't change it */ return; } - str_append_c(client->capability_string, ' '); - str_append(client->capability_string, capability); + + /* add it to our capability list as CAP_ALWAYS */ + imap_capability_list_add(client->capability_list, + capability, IMAP_CAP_VISIBILITY_ALWAYS); +} + +const char *client_get_capability(struct client *client) +{ + string_t *cap_str = t_str_new(256); + + /* imap is postauth by definition */ + enum imap_capability_visibility visibility = IMAP_CAP_VISIBILITY_POSTAUTH; + + /* is the client secured by means of ssl? */ + if (client->ssl_secured) + visibility |= IMAP_CAP_VISIBILITY_TLS_ACTIVE; + else + visibility |= IMAP_CAP_VISIBILITY_TLS_INACTIVE; + + /* Are we secured? (localhost? tls? etc) */ + if (client->secured) + visibility |= IMAP_CAP_VISIBILITY_SECURE; + else + visibility |= IMAP_CAP_VISIBILITY_INSECURE; + + /* build capability string based on IMAP_CAP_VISIBILITY_ flags */ + imap_capability_list_get_capability(client->capability_list, + cap_str, visibility); + + return str_c(cap_str); } + void client_send_line(struct client *client, const char *data) { (void)client_send_line_next(client, data); diff --git a/src/imap/imap-client.h b/src/imap/imap-client.h index 5a1348e85b..2b189599d5 100644 --- a/src/imap/imap-client.h +++ b/src/imap/imap-client.h @@ -170,7 +170,7 @@ struct client { struct mail_storage_service_user *service_user; const struct imap_settings *set; const struct smtp_submit_settings *smtp_set; - string_t *capability_string; + struct imap_capability_list *capability_list; const char *disconnect_reason; struct mail_user *user; @@ -291,6 +291,9 @@ void client_kick(struct client *client); has an explicit capability, nothing is changed. */ void client_add_capability(struct client *client, const char *capability); +/* Generate the string of capabilities from the client */ +const char *client_get_capability(struct client *client); + /* Send a line of data to client. */ void client_send_line(struct client *client, const char *data); /* Send a line of data to client. Returns 1 if ok, 0 if buffer is getting full, diff --git a/src/imap/main.c b/src/imap/main.c index 620f0be5a0..3e4c981698 100644 --- a/src/imap/main.c +++ b/src/imap/main.c @@ -345,7 +345,7 @@ static void main_stdio_run(const char *username) client_create_finish_io(client); client_send_login_reply(client->output, - str_c(client->capability_string), + client_get_capability(client), client->user->username, &request); if (client_create_finish(client, &error) < 0) i_fatal("%s", error); @@ -409,6 +409,7 @@ login_client_connected(const struct master_login_client *login_client, */ client_create_finish_io(client); client_send_login_reply(client->output, + client_get_capability(client), str_c(client->capability_string), NULL, &request); if (client_create_finish(client, &error) < 0) {