Commit 6ecdedbc65 for openssl.org
commit 6ecdedbc65c9f2eca90a8ea7dc97e38372c48fe4
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Fri Jun 12 00:17:10 2026 +0900
Fix s_client Sieve STARTTLS response parsing
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
MergeDate: Wed Jun 17 16:22:20 2026
(Merged from https://github.com/openssl/openssl/pull/31468)
diff --git a/apps/s_client.c b/apps/s_client.c
index fbddd5901f..2d1f61a179 100644
--- a/apps/s_client.c
+++ b/apps/s_client.c
@@ -3029,11 +3029,9 @@ re_start:
}
/*
* According to RFC 5804 § 2.2, response codes are case-
- * insensitive, make it uppercase but preserve the response.
+ * insensitive.
*/
- strncpy(sbuf, mbuf, 2);
- make_uppercase(sbuf);
- if (!HAS_PREFIX(sbuf, "OK")) {
+ if (OPENSSL_strncasecmp(mbuf, "OK", 2) != 0) {
BIO_printf(bio_err, "STARTTLS not supported: %s", mbuf);
goto shut;
}
diff --git a/test/recipes/20-test_app_s_client.t b/test/recipes/20-test_app_s_client.t
new file mode 100644
index 0000000000..162fb710da
--- /dev/null
+++ b/test/recipes/20-test_app_s_client.t
@@ -0,0 +1,110 @@
+#! /usr/bin/env perl
+# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License"). You may not use
+# this file except in compliance with the License. You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use strict;
+use warnings;
+
+use IO::Socket::INET;
+use OpenSSL::Test qw/:DEFAULT result_file with/;
+use OpenSSL::Test::Utils;
+
+setup("test_app_s_client");
+
+plan skip_all => "test_app_s_client needs sock enabled"
+ if disabled("sock");
+plan skip_all => "test_app_s_client needs IPv4"
+ unless have_IPv4();
+plan skip_all => "test_app_s_client needs fork"
+ if $^O =~ /^(VMS|MSWin32|msys)$/;
+
+plan tests => 5;
+
+my $timeout = 30;
+local $SIG{ALRM} = sub { BAIL_OUT("s_client Sieve STARTTLS test timed out") };
+alarm($timeout);
+
+my $listener = IO::Socket::INET->new(
+ LocalAddr => "127.0.0.1",
+ LocalPort => 0,
+ Listen => 1,
+ Proto => "tcp",
+ ReuseAddr => 1,
+) or BAIL_OUT("failed to create local Sieve listener: $!");
+
+my $port = $listener->sockport();
+my $command_file = result_file("sieve-starttls-command.txt");
+my $stdout_file = result_file("s_client-stdout.txt");
+my $stderr_file = result_file("s_client-stderr.txt");
+my $server_pid = fork();
+
+BAIL_OUT("failed to fork Sieve listener: $!") unless defined $server_pid;
+
+if ($server_pid == 0) {
+ eval {
+ local $SIG{ALRM} = sub { die "Sieve listener timed out\n" };
+ alarm($timeout);
+
+ my $server = $listener->accept()
+ or die "failed to accept s_client connection: $!";
+
+ $server->autoflush(1);
+ print $server "\"STARTTLS\"\r\nOK\r\n";
+
+ my $command = <$server>;
+ open my $fh, ">", $command_file
+ or die "failed to open command capture file: $!";
+ print $fh $command if defined $command;
+ close $fh;
+
+ # This stub only needs to drive s_client through the plaintext
+ # Sieve STARTTLS response parser. After sending an exact two-byte
+ # lowercase OK response, it closes instead of performing TLS. The
+ # resulting handshake failure is expected, but sanitizer failures
+ # before that are not.
+ print $server "ok";
+ close $server;
+ alarm(0);
+ };
+ warn $@ if $@;
+ exit($@ ? 1 : 0);
+}
+
+close $listener;
+
+with({ exit_checker => sub { return shift() < 128; } },
+ sub {
+ ok(run(app(["openssl", "s_client", "-brief", "-starttls", "sieve",
+ "-connect", "127.0.0.1:$port"],
+ stdin => undef, stdout => $stdout_file,
+ stderr => $stderr_file)),
+ "s_client exits without signal");
+ });
+
+waitpid($server_pid, 0);
+is($?, 0, "Sieve listener completed");
+
+my $command = "";
+if (open my $fh, "<", $command_file) {
+ local $/;
+ $command = <$fh>;
+ close $fh;
+}
+is($command, "STARTTLS\r\n", "s_client sends Sieve STARTTLS command");
+
+my $stderr = "";
+if (open my $fh, "<", $stderr_file) {
+ local $/;
+ $stderr = <$fh>;
+ close $fh;
+}
+unlike($stderr, qr/STARTTLS not supported/,
+ "s_client accepts case-insensitive two-byte OK response");
+unlike($stderr, qr/AddressSanitizer/,
+ "s_client does not trigger AddressSanitizer");
+
+alarm(0);