diff --git a/docs/imap.md b/docs/imap.md index 77c7fa4..dc7dd08 100644 --- a/docs/imap.md +++ b/docs/imap.md @@ -37,6 +37,10 @@ ad-hoc downgrade. AUTH capabilities are generated from the GNU GSASL mechanisms which Bongo has actually enabled. Password mechanisms are advertised only on a TLS connection; unencrypted connections offer STARTTLS and keep LOGIN disabled. +STARTTLS rejects arguments and repeated or post-authentication use, discards +already buffered cleartext before the TLS handshake, and closes the connection +without attempting a plaintext IMAP response if negotiation fails. A client +which ignores `LOGINDISABLED` receives a tagged `NO` without a password lookup. Relevant standards are [RFC 9051](https://www.rfc-editor.org/rfc/rfc9051.html), [RFC 3501](https://www.rfc-editor.org/rfc/rfc3501.html), diff --git a/docs/protocols.md b/docs/protocols.md index 5626c48..c60b6c8 100644 --- a/docs/protocols.md +++ b/docs/protocols.md @@ -34,6 +34,10 @@ POP3 implements the normal transaction commands and advertises `TOP`, `RESP-CODES`, `AUTH-RESP-CODE`, `PIPELINING`, `EXPIRE NEVER`, `UIDL`, and `UTF8`. STLS is offered before authentication; SASL and password login are advertised only according to the TLS policy. POP3S provides implicit TLS. +After a successful `UTF8` command, `STLS` is no longer advertised and is +rejected so that the negotiated UTF-8 session state cannot cross into TLS. +Both IMAP STARTTLS and POP3 STLS discard buffered cleartext before negotiating +TLS. ## Server-side filtering diff --git a/docs/release-testing-0.7.md b/docs/release-testing-0.7.md index d5cada3..1b03a6d 100644 --- a/docs/release-testing-0.7.md +++ b/docs/release-testing-0.7.md @@ -154,7 +154,7 @@ inputs and outputs is absent from the ZIP. | ID | Test | R1 | R2 | R3 | | --- | --- | --- | --- | --- | | IMAP-01 | Capability list matches implemented rev2/rev1/legacy behaviour | | | | -| IMAP-02 | Port 143 STARTTLS and pre-TLS `LOGINDISABLED` | | | | +| IMAP-02 | Port 143 STARTTLS, pre-TLS `LOGINDISABLED`, tagged `NO` for plaintext LOGIN, repeated STARTTLS rejection | | | | | IMAP-03 | Port 993 TLS, LOGIN, SASL PLAIN, and SASL LOGIN | | | | | IMAP-04 | CAPABILITY, NOOP, LOGOUT, ID, and NAMESPACE | | | | | IMAP-05 | LIST, LSUB, extended LIST, LIST-STATUS, hierarchy quoting | | | | @@ -171,18 +171,18 @@ inputs and outputs is absent from the ZIP. | IMAP-16 | ENABLE UTF8=ACCEPT and UTF-8 mailbox/message names | | | | | IMAP-17 | SPECIAL-USE and CREATE-SPECIAL-USE folders | | | | | IMAP-18 | Old-client plain IMAP4 subset without rev1/rev2 assumptions | | | | -| IMAP-19 | Invalid states, malformed sets/literals, oversized/pipelined input | | | | +| IMAP-19 | Invalid states, malformed sets/literals, oversized input, and STARTTLS plaintext-pipeline injection resistance | | | | | IMAP-20 | Reconnect, simultaneous clients, restart, mailbox consistency | | | | ## POP3 and ManageSieve | ID | Test | R1 | R2 | R3 | | --- | --- | --- | --- | --- | -| POP-01 | Port 110 CAPA/STLS; credentials unavailable before TLS | | | | +| POP-01 | Port 110 CAPA/STLS; credentials unavailable before TLS; buffered plaintext discarded | | | | | POP-02 | Port 995 TLS with USER/PASS and shared GSASL mechanisms | | | | | POP-03 | STAT, LIST, UIDL, RETR, TOP, and dot transparency | | | | | POP-04 | DELE, RSET, NOOP, QUIT commit, and disconnect rollback | | | | -| POP-05 | UTF8, PIPELINING, response codes, malformed input, timeout | | | | +| POP-05 | UTF8, STLS rejection after UTF8, PIPELINING, response codes, malformed input, timeout | | | | | SIEVE-01 | Port 4190 greeting, STARTTLS, and GSASL authentication | | | | | SIEVE-02 | CAPABILITY, HAVESPACE, PUT/CHECK/LIST/GETSCRIPT | | | | | SIEVE-03 | SETACTIVE, RENAME, DELETE, LOGOUT, and persistence | | | | diff --git a/src/agents/imap/CMakeLists.txt b/src/agents/imap/CMakeLists.txt index 5a3b2e6..7128106 100644 --- a/src/agents/imap/CMakeLists.txt +++ b/src/agents/imap/CMakeLists.txt @@ -1,6 +1,7 @@ add_executable(bongoimap imapd.c capabilities.c + protocol.c enable.c utf8-mailbox.c append-message.c @@ -46,6 +47,15 @@ if(BUILD_TESTING) ) add_test(NAME imap-capabilities COMMAND imap-capabilities-test) + add_executable(imap-protocol-test + tests/protocol-test.c + protocol.c + ) + target_include_directories(imap-protocol-test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) + add_test(NAME imap-protocol COMMAND imap-protocol-test) + add_executable(imap-enable-test tests/enable-test.c enable.c diff --git a/src/agents/imap/imapd.h b/src/agents/imap/imapd.h index 2392937..34467f6 100755 --- a/src/agents/imap/imapd.h +++ b/src/agents/imap/imapd.h @@ -98,6 +98,8 @@ typedef enum { STATUS_INVALID_ARGUMENT, STATUS_INVALID_STATE, STATUS_UNKNOWN_COMMAND, + STATUS_LOGIN_DISABLED, + STATUS_TLS_UNAVAILABLE, STATUS_TLS_NEGOTIATION_FAILURE, STATUS_CHARSET_NOT_SUPPORTED, STATUS_UID_NOT_FOUND, @@ -156,6 +158,8 @@ static ImapErrorString ImapErrorStrings[] = { { STATUS_INVALID_ARGUMENT, "%s BAD %s invalid arguments\r\n" }, { STATUS_INVALID_STATE, "%s BAD %s invalid command in current state\r\n" }, { STATUS_UNKNOWN_COMMAND, "* BAD command unrecognized\r\n" }, + { STATUS_LOGIN_DISABLED, "%s NO %s plaintext LOGIN disabled; use STARTTLS\r\n" }, + { STATUS_TLS_UNAVAILABLE, "%s NO %s TLS unavailable\r\n" }, { STATUS_TLS_NEGOTIATION_FAILURE, "%s BAD %s negotiation failed\r\n" }, { 0, NULL } }; diff --git a/src/agents/imap/protocol.c b/src/agents/imap/protocol.c new file mode 100644 index 0000000..835a8ae --- /dev/null +++ b/src/agents/imap/protocol.c @@ -0,0 +1,57 @@ +/**************************************************************************** + * + * Copyright (c) 2001 Novell, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Novell, Inc. + * + * To contact Novell about this file by physical or electronic mail, you + * may find current contact information at www.novell.com. + * + ****************************************************************************/ + +#include "protocol.h" + +#include +#include + +static int +EqualsIgnoreCase(const char *left, const char *right) +{ + if (!left || !right) return 0; + while (*left && *right) { + if (toupper((unsigned char)*left) != toupper((unsigned char)*right)) + return 0; + left++; + right++; + } + return *left == '\0' && *right == '\0'; +} + +IMAPStartTlsStatus +IMAPStartTlsCheck(const char *command_line, int authenticated, + int tls_active, int tls_available) +{ + if (!EqualsIgnoreCase(command_line, "STARTTLS")) + return IMAP_STARTTLS_INVALID_ARGUMENTS; + if (authenticated || tls_active) return IMAP_STARTTLS_INVALID_STATE; + if (!tls_available) return IMAP_STARTTLS_UNAVAILABLE; + return IMAP_STARTTLS_READY; +} + +IMAPLoginStatus +IMAPLoginCheck(int authenticated, int tls_active) +{ + if (authenticated) return IMAP_LOGIN_INVALID_STATE; + if (!tls_active) return IMAP_LOGIN_DISABLED; + return IMAP_LOGIN_READY; +} diff --git a/src/agents/imap/protocol.h b/src/agents/imap/protocol.h new file mode 100644 index 0000000..799ac66 --- /dev/null +++ b/src/agents/imap/protocol.h @@ -0,0 +1,43 @@ +/**************************************************************************** + * + * Copyright (c) 2001 Novell, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Novell, Inc. + * + * To contact Novell about this file by physical or electronic mail, you + * may find current contact information at www.novell.com. + * + ****************************************************************************/ + +#ifndef BONGO_IMAP_PROTOCOL_H +#define BONGO_IMAP_PROTOCOL_H + +typedef enum { + IMAP_STARTTLS_READY = 0, + IMAP_STARTTLS_INVALID_ARGUMENTS, + IMAP_STARTTLS_INVALID_STATE, + IMAP_STARTTLS_UNAVAILABLE +} IMAPStartTlsStatus; + +typedef enum { + IMAP_LOGIN_READY = 0, + IMAP_LOGIN_DISABLED, + IMAP_LOGIN_INVALID_STATE +} IMAPLoginStatus; + +IMAPStartTlsStatus IMAPStartTlsCheck(const char *command_line, + int authenticated, int tls_active, + int tls_available); +IMAPLoginStatus IMAPLoginCheck(int authenticated, int tls_active); + +#endif diff --git a/src/agents/imap/session.c b/src/agents/imap/session.c index 69a03f5..7a2393e 100644 --- a/src/agents/imap/session.c +++ b/src/agents/imap/session.c @@ -29,6 +29,7 @@ #include #include "imapd.h" +#include "protocol.h" void ImapAnnotateAliasClient(ImapSession *session) @@ -298,10 +299,20 @@ ImapCommandLogin(void *param) { ImapSession *session = (ImapSession *)param; long ccode; + IMAPLoginStatus status; char *username = NULL; char *password = NULL; - if (session->client.state == STATE_FRESH && session->client.conn->ssl.enable) { + status = IMAPLoginCheck(session->client.state >= STATE_AUTH, + session->client.conn->ssl.enable); + if (status == IMAP_LOGIN_DISABLED) + return(SendError(session->client.conn, session->command.tag, "LOGIN", + STATUS_LOGIN_DISABLED)); + if (status == IMAP_LOGIN_INVALID_STATE) + return(SendError(session->client.conn, session->command.tag, "LOGIN", + STATUS_INVALID_STATE)); + + if (status == IMAP_LOGIN_READY) { ccode = GrabTwoArguments(session, session->command.buffer + 6, &username, &password); if (ccode == STATUS_CONTINUE) { X500Encode(username); @@ -335,27 +346,48 @@ int ImapCommandStartTls(void *param) { long ccode; + IMAPStartTlsStatus status; ImapSession *session = (ImapSession *)param; - if (session->client.state < STATE_AUTH) { - if (Imap.server.ssl.config.options) { - if (((ccode = ConnWriteF(session->client.conn, "%s OK Begin TLS negotiation\r\n",session->command.tag)) != -1) && (ccode = ConnFlush(session->client.conn))) { - session->client.conn->ssl.enable = TRUE; - if (ConnNegotiate(session->client.conn, Imap.server.ssl.context)) { - return(STATUS_CONTINUE); - } + status = IMAPStartTlsCheck(session->command.buffer, + session->client.state >= STATE_AUTH, + session->client.conn->ssl.enable, + Imap.server.ssl.context != NULL); + if (status == IMAP_STARTTLS_INVALID_ARGUMENTS) + return(SendError(session->client.conn, session->command.tag, + "STARTTLS", STATUS_INVALID_ARGUMENT)); + if (status == IMAP_STARTTLS_INVALID_STATE) + return(SendError(session->client.conn, session->command.tag, + "STARTTLS", STATUS_INVALID_STATE)); + if (status == IMAP_STARTTLS_UNAVAILABLE) + return(SendError(session->client.conn, session->command.tag, + "STARTTLS", STATUS_TLS_UNAVAILABLE)); - return(SendError(session->client.conn, session->command.tag, "STARTTLS", STATUS_TLS_NEGOTIATION_FAILURE)); - } + ccode = ConnWriteF(session->client.conn, + "%s OK Begin TLS negotiation\r\n", + session->command.tag); + if (ccode == -1 || ConnFlush(session->client.conn) == -1) + return(STATUS_ABORT); - return(STATUS_ABORT); - } - - return(SendError(session->client.conn, session->command.tag, "", STATUS_UNKNOWN_COMMAND)); - } - - return(SendError(session->client.conn, session->command.tag, "STARTTLS", STATUS_INVALID_STATE)); + /* + * Never carry pipelined cleartext across STARTTLS. RFC 9051 requires + * extra bytes already received after the command to become TLS input or + * to be discarded. Discarding them avoids plaintext command injection + * and matches the POP3 STLS path. + */ + session->client.conn->receive.read = + session->client.conn->receive.write = + session->client.conn->receive.buffer; + session->client.conn->receive.remaining = CONN_TCP_MTU; + session->client.conn->receive.buffer[0] = '\0'; + session->client.conn->ssl.enable = TRUE; + session->client.enabledCapabilities = 0; + if (ConnNegotiate(session->client.conn, Imap.server.ssl.context)) + return(STATUS_CONTINUE); + /* TLS bytes may already have been exchanged; no IMAP response is safe. */ + ConnClose(session->client.conn); + return(STATUS_ABORT); } diff --git a/src/agents/imap/tests/protocol-test.c b/src/agents/imap/tests/protocol-test.c new file mode 100644 index 0000000..702361c --- /dev/null +++ b/src/agents/imap/tests/protocol-test.c @@ -0,0 +1,46 @@ +/**************************************************************************** + * + * Copyright (c) 2001 Novell, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Novell, Inc. + * + * To contact Novell about this file by physical or electronic mail, you + * may find current contact information at www.novell.com. + * + ****************************************************************************/ + +#include "protocol.h" + +#include + +int +main(void) +{ + assert(IMAPStartTlsCheck("STARTTLS", 0, 0, 1) == IMAP_STARTTLS_READY); + assert(IMAPStartTlsCheck("starttls", 0, 0, 1) == IMAP_STARTTLS_READY); + assert(IMAPStartTlsCheck("STARTTLS ", 0, 0, 1) == + IMAP_STARTTLS_INVALID_ARGUMENTS); + assert(IMAPStartTlsCheck("STARTTLS extra", 0, 0, 1) == + IMAP_STARTTLS_INVALID_ARGUMENTS); + assert(IMAPStartTlsCheck("STARTTLS", 1, 0, 1) == + IMAP_STARTTLS_INVALID_STATE); + assert(IMAPStartTlsCheck("STARTTLS", 0, 1, 1) == + IMAP_STARTTLS_INVALID_STATE); + assert(IMAPStartTlsCheck("STARTTLS", 0, 0, 0) == + IMAP_STARTTLS_UNAVAILABLE); + assert(IMAPLoginCheck(0, 1) == IMAP_LOGIN_READY); + assert(IMAPLoginCheck(0, 0) == IMAP_LOGIN_DISABLED); + assert(IMAPLoginCheck(1, 1) == IMAP_LOGIN_INVALID_STATE); + assert(IMAPLoginCheck(1, 0) == IMAP_LOGIN_INVALID_STATE); + return 0; +} diff --git a/src/agents/pop/capabilities.c b/src/agents/pop/capabilities.c index 38d3ff8..fd54d26 100644 --- a/src/agents/pop/capabilities.c +++ b/src/agents/pop/capabilities.c @@ -47,6 +47,7 @@ Append(char *buffer, size_t buffer_size, size_t *length, size_t POP3FormatCapabilities(char *buffer, size_t buffer_size, int authorization_state, int tls_active, + int utf8_active, int stls_available, const char *sasl_mechanisms) { @@ -63,7 +64,8 @@ POP3FormatCapabilities(char *buffer, size_t buffer_size, sasl_mechanisms)) || (authorization_state && tls_active && !Append(buffer, buffer_size, &length, "USER\r\n")) || - (authorization_state && !tls_active && stls_available && + (authorization_state && !tls_active && !utf8_active && + stls_available && !Append(buffer, buffer_size, &length, "STLS\r\n")) || !Append(buffer, buffer_size, &length, ".\r\n")) return 0; diff --git a/src/agents/pop/capabilities.h b/src/agents/pop/capabilities.h index f7344c4..f02b976 100644 --- a/src/agents/pop/capabilities.h +++ b/src/agents/pop/capabilities.h @@ -27,6 +27,7 @@ /* Returns the number of bytes written, or 0 if the buffer was too small. */ size_t POP3FormatCapabilities(char *buffer, size_t buffer_size, int authorization_state, int tls_active, + int utf8_active, int stls_available, const char *sasl_mechanisms); diff --git a/src/agents/pop/pop3.c b/src/agents/pop/pop3.c index 6ef3cc8..9054415 100644 --- a/src/agents/pop/pop3.c +++ b/src/agents/pop/pop3.c @@ -1136,7 +1136,7 @@ POP3CommandCapa(void *param) } capabilityLength = POP3FormatCapabilities( client->buffer, sizeof(client->buffer), authorizationState, - client->conn->ssl.enable, + client->conn->ssl.enable, client->utf8Mode, POP3.server.ssl.enable && POP3.server.ssl.context != NULL, mechanisms); ccode = capabilityLength ? ConnWrite(client->conn, client->buffer, capabilityLength) @@ -1533,6 +1533,7 @@ POP3CommandSTLS(void *param) { int ccode; BOOL result; + POP3STLSStatus status; POP3Client *client = (POP3Client *)param; if (!POP3ArgumentsEmpty(client->command + @@ -1544,19 +1545,19 @@ POP3CommandSTLS(void *param) return ConnWrite(client->conn, MSGERRSYNTAX, sizeof(MSGERRSYNTAX) - 1); - if ((client->state == POP3_CLIENT_FRESH || - client->state == POP3_CLIENT_AUTHORIZATION) && - !client->conn->ssl.enable) { - if (POP3.server.ssl.enable && POP3.server.ssl.context) { - ccode = ConnWrite(client->conn, MSGOKSTARTTLS, sizeof(MSGOKSTARTTLS) - 1); - } else { - return ConnWrite(client->conn, - "-ERR [SYS/PERM] TLS unavailable\r\n", - sizeof("-ERR [SYS/PERM] TLS unavailable\r\n") - 1); - } - } else { + status = POP3STLSCheck(client->state == POP3_CLIENT_FRESH || + client->state == POP3_CLIENT_AUTHORIZATION, + client->conn->ssl.enable, client->utf8Mode, + POP3.server.ssl.enable && POP3.server.ssl.context); + if (status == POP3_STLS_INVALID_STATE) { return(ConnWrite(client->conn, MSGERRBADSTATE, sizeof(MSGERRBADSTATE) - 1)); } + if (status == POP3_STLS_UNAVAILABLE) + return ConnWrite(client->conn, + "-ERR [SYS/PERM] TLS unavailable\r\n", + sizeof("-ERR [SYS/PERM] TLS unavailable\r\n") - 1); + + ccode = ConnWrite(client->conn, MSGOKSTARTTLS, sizeof(MSGOKSTARTTLS) - 1); if (ccode != -1) { ccode = ConnFlush(client->conn); diff --git a/src/agents/pop/protocol.c b/src/agents/pop/protocol.c index 621464c..5c35538 100644 --- a/src/agents/pop/protocol.c +++ b/src/agents/pop/protocol.c @@ -57,6 +57,16 @@ POP3ArgumentsEmpty(const char *arguments) return arguments && *arguments == '\0'; } +POP3STLSStatus +POP3STLSCheck(int authorization_state, int tls_active, int utf8_active, + int tls_available) +{ + if (!authorization_state || tls_active || utf8_active) + return POP3_STLS_INVALID_STATE; + if (!tls_available) return POP3_STLS_UNAVAILABLE; + return POP3_STLS_READY; +} + int POP3ParseMessageNumber(const char *arguments, uint32_t *message_number) { diff --git a/src/agents/pop/protocol.h b/src/agents/pop/protocol.h index bd0ed34..a6b1828 100644 --- a/src/agents/pop/protocol.h +++ b/src/agents/pop/protocol.h @@ -35,10 +35,18 @@ typedef struct { int ends_in_cr; } POP3MultilineWriter; +typedef enum { + POP3_STLS_READY = 0, + POP3_STLS_INVALID_STATE, + POP3_STLS_UNAVAILABLE +} POP3STLSStatus; + int POP3Contains8Bit(const void *data, size_t length); int POP3UTF8Valid(const void *data, size_t length); int POP3ArgumentsEmpty(const char *arguments); +POP3STLSStatus POP3STLSCheck(int authorization_state, int tls_active, + int utf8_active, int tls_available); int POP3ParseMessageNumber(const char *arguments, uint32_t *message_number); int POP3ParseTopArguments(const char *arguments, uint32_t *message_number, uint32_t *line_count); diff --git a/src/agents/pop/tests/capabilities-test.c b/src/agents/pop/tests/capabilities-test.c index 1338e65..8446b84 100644 --- a/src/agents/pop/tests/capabilities-test.c +++ b/src/agents/pop/tests/capabilities-test.c @@ -30,29 +30,35 @@ main(void) char buffer[1024]; char small[8]; - assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 0, 0, NULL) > 0); + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 0, 0, 0, + NULL) > 0); assert(!strstr(buffer, "STLS")); assert(!strstr(buffer, "SASL")); assert(!strstr(buffer, "USER")); assert(strstr(buffer, "UTF8\r\n")); - assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 0, 1, NULL) > 0); + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 0, 0, 1, + NULL) > 0); assert(strstr(buffer, "STLS\r\n")); assert(!strstr(buffer, "SASL")); - assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 1, 1, + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 1, 0, 1, "PLAIN LOGIN") > 0); assert(!strstr(buffer, "STLS")); assert(strstr(buffer, "SASL PLAIN LOGIN\r\n")); assert(strstr(buffer, "USER\r\n")); - assert(POP3FormatCapabilities(buffer, sizeof(buffer), 0, 1, 1, + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 0, 1, 0, 1, "PLAIN LOGIN") > 0); assert(!strstr(buffer, "STLS")); assert(strstr(buffer, "SASL PLAIN LOGIN\r\n")); assert(!strstr(buffer, "USER")); - assert(POP3FormatCapabilities(small, sizeof(small), 1, 1, 1, + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 0, 1, 1, + NULL) > 0); + assert(!strstr(buffer, "STLS")); + + assert(POP3FormatCapabilities(small, sizeof(small), 1, 1, 0, 1, "PLAIN LOGIN") == 0); assert(small[0] == '\0'); return 0; diff --git a/src/agents/pop/tests/protocol-test.c b/src/agents/pop/tests/protocol-test.c index e4336ec..efb5f2a 100644 --- a/src/agents/pop/tests/protocol-test.c +++ b/src/agents/pop/tests/protocol-test.c @@ -49,6 +49,11 @@ TestArguments(void) assert(POP3ArgumentsEmpty("")); assert(!POP3ArgumentsEmpty(" ")); + assert(POP3STLSCheck(1, 0, 0, 1) == POP3_STLS_READY); + assert(POP3STLSCheck(0, 0, 0, 1) == POP3_STLS_INVALID_STATE); + assert(POP3STLSCheck(1, 1, 0, 1) == POP3_STLS_INVALID_STATE); + assert(POP3STLSCheck(1, 0, 1, 1) == POP3_STLS_INVALID_STATE); + assert(POP3STLSCheck(1, 0, 0, 0) == POP3_STLS_UNAVAILABLE); assert(POP3ParseMessageNumber(" 1", &message) && message == 1); assert(POP3ParseMessageNumber("\t4294967295", &message) && message == UINT32_MAX);