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);