Compare commits

..

7 Commits

Author SHA1 Message Date
Mingyuan Li
6fc95e8d43 examples: Enable libssh_scp and scp_download on Windows
Both examples only depend on getopt, which is now provided by the
bundled fallback. Include the getopt wrapper header and move them
out of the UNIX-only build guard.

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 15:11:55 +01:00
Mingyuan Li
715d79647d tests: Add dedicated unit tests for getopt abstraction
Add torture_getopt.c with 11 test cases covering basic option parsing,
arguments, optional arguments, unknown options, missing arguments,
BADARG colon behavior, double-dash termination, combined options,
optind advancement, reset behavior, and no-options edge case.
Registered in the unit test CMakeLists.txt.

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 15:11:55 +01:00
Mingyuan Li
90169c598e tests: Enable getopt tests on all platforms
Remove _MSC_VER guards from torture_options_getopt and
torture_options_getopt_o_option so they run unconditionally,
now that a bundled getopt fallback is available.

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 15:11:55 +01:00
Mingyuan Li
950f796aca options: Enable ssh_options_getopt on all platforms
Include the new platform-independent getopt wrapper header and remove
the #ifdef _MSC_VER guard that disabled ssh_options_getopt() on MSVC.
The function is now compiled unconditionally on all platforms.

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 15:11:55 +01:00
Mingyuan Li
31a882016e options: Add platform-independent getopt wrapper header
Add include/libssh/getopt.h that transparently includes the system
<getopt.h> when available, or declares the bundled fallback interface
otherwise.

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 15:11:55 +01:00
Mingyuan Li
d2f7994140 options: Add bundled getopt implementation from FreeBSD
Add a portable getopt() fallback for platforms that lack it (e.g. MSVC).
Based on FreeBSD lib/libc/stdlib/getopt.c (BSD-3-Clause), adapted by
replacing FreeBSD internal headers and _getprogname() with standard C
equivalents. Only compiled when HAVE_GETOPT is not defined.

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 15:11:55 +01:00
Mingyuan Li
9a2f23ee30 cmake: Add detection for getopt and getopt.h
Add CMake checks for the getopt function and getopt.h header to
prepare for a bundled getopt fallback on platforms that lack it
(e.g. MSVC).

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 15:11:55 +01:00
11 changed files with 468 additions and 25 deletions

View File

@@ -65,6 +65,8 @@ check_include_file(byteswap.h HAVE_BYTESWAP_H)
check_include_file(glob.h HAVE_GLOB_H)
check_include_file(valgrind/valgrind.h HAVE_VALGRIND_VALGRIND_H)
check_include_file(ifaddrs.h HAVE_IFADDRS_H)
check_include_file(getopt.h HAVE_GETOPT_H)
check_function_exists(getopt HAVE_GETOPT)
if (WIN32)
check_include_file(io.h HAVE_IO_H)

View File

@@ -67,6 +67,12 @@
/* Define to 1 if you have the <ifaddrs.h> header file. */
#cmakedefine HAVE_IFADDRS_H 1
/* Define to 1 if you have the <getopt.h> header file. */
#cmakedefine HAVE_GETOPT_H 1
/* Define to 1 if you have the `getopt' function. */
#cmakedefine HAVE_GETOPT 1
/* Define to 1 if you have the <openssl/aes.h> header file. */
#cmakedefine HAVE_OPENSSL_AES_H 1

View File

@@ -13,14 +13,6 @@ if (ARGP_INCLUDE_DIR)
endif()
if (UNIX AND NOT WIN32)
add_executable(libssh_scp libssh_scp.c ${examples_SRCS})
target_compile_options(libssh_scp PRIVATE ${DEFAULT_C_COMPILE_FLAGS})
target_link_libraries(libssh_scp ssh::ssh)
add_executable(scp_download scp_download.c ${examples_SRCS})
target_compile_options(scp_download PRIVATE ${DEFAULT_C_COMPILE_FLAGS})
target_link_libraries(scp_download ssh::ssh)
add_executable(sshnetcat sshnetcat.c ${examples_SRCS})
target_compile_options(sshnetcat PRIVATE ${DEFAULT_C_COMPILE_FLAGS})
target_link_libraries(sshnetcat ssh::ssh)
@@ -77,6 +69,18 @@ if (UNIX AND NOT WIN32)
endif()
endif (UNIX AND NOT WIN32)
if (NOT HAVE_GETOPT)
set(examples_SRCS ${examples_SRCS} ${libssh_SOURCE_DIR}/src/external/getopt.c)
endif()
add_executable(libssh_scp libssh_scp.c ${examples_SRCS})
target_compile_options(libssh_scp PRIVATE ${DEFAULT_C_COMPILE_FLAGS})
target_link_libraries(libssh_scp ssh::ssh)
add_executable(scp_download scp_download.c ${examples_SRCS})
target_compile_options(scp_download PRIVATE ${DEFAULT_C_COMPILE_FLAGS})
target_link_libraries(scp_download ssh::ssh)
if (WITH_SERVER)
add_executable(samplesshd-cb samplesshd-cb.c)
target_compile_options(samplesshd-cb PRIVATE ${DEFAULT_C_COMPILE_FLAGS})

View File

@@ -15,6 +15,13 @@ clients must be made or how a client should react.
#include <libssh/libssh.h>
/* On platforms without getopt (e.g. MSVC), the bundled compatibility layer
* from libssh is used. When building outside of the libssh source tree,
* a getopt implementation must be provided separately. */
#ifndef HAVE_GETOPT_H
#include "libssh/getopt.h"
#endif
/** Zero a structure */
#define ZERO_STRUCT(x) memset(&(x), 0, sizeof(x))

35
include/libssh/getopt.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2026 Mingyuan Li <2560359315@qq.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#ifndef LIBSSH_GETOPT_H
#define LIBSSH_GETOPT_H
#include "config.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
/* Bundled getopt fallback (src/external/getopt.c) */
extern int opterr, optind, optopt, optreset;
extern char *optarg;
int getopt(int nargc, char *const nargv[], const char *ostr);
#endif
#endif /* LIBSSH_GETOPT_H */

View File

@@ -140,6 +140,13 @@ set(libssh_SRCS
pki_ed25519_common.c
)
if (NOT HAVE_GETOPT)
set(libssh_SRCS
${libssh_SRCS}
external/getopt.c
)
endif (NOT HAVE_GETOPT)
if (DEFAULT_C_NO_DEPRECATION_FLAGS)
set_source_files_properties(known_hosts.c
PROPERTIES

134
src/external/getopt.c vendored Normal file
View File

@@ -0,0 +1,134 @@
/* $NetBSD: getopt.c,v 1.29 2014/06/05 22:00:22 christos Exp $ */
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1987, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Adapted for libssh: removed FreeBSD internal headers (namespace.h,
* un-namespace.h, libc_private.h, unistd.h) and replaced _getprogname()
* with nargv[0] for portability on platforms lacking getopt (e.g. MSVC).
*/
#include <stdio.h>
#include <string.h>
int opterr = 1, /* if error message should be printed */
optind = 1, /* index into parent argv vector */
optopt, /* character checked for validity */
optreset; /* reset getopt */
char *optarg = NULL; /* argument associated with option */
#define BADCH (int)'?'
#define BADARG (int)':'
static char EMSG[] = "";
/*
* getopt --
* Parse argc/argv argument vector.
*/
int getopt(int nargc, char *const nargv[], const char *ostr);
int getopt(int nargc, char *const nargv[], const char *ostr)
{
static char *place = EMSG; /* option letter processing */
char *oli = NULL; /* option letter list index */
if (optreset || *place == 0) { /* update scanning pointer */
optreset = 0;
if (optind >= nargc || *(place = nargv[optind]) != '-') {
/* Argument is absent or is not an option */
place = EMSG;
return (-1);
}
place++;
optopt = *place++;
if (optopt == '-' && *place == 0) {
/* "--" => end of options */
++optind;
place = EMSG;
return (-1);
}
if (optopt == 0) {
/* Solitary '-', treat as a '-' option
if the program (eg su) is looking for it. */
place = EMSG;
if (strchr(ostr, '-') == NULL)
return (-1);
optopt = '-';
}
} else
optopt = *place++;
/* See if option letter is one the caller wanted... */
if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) {
if (*place == 0)
++optind;
if (opterr && *ostr != ':')
(void)
fprintf(stderr, "%s: illegal option -- %c\n", nargv[0], optopt);
return (BADCH);
}
/* Does this option need an argument? */
if (oli[1] != ':') {
/* don't need argument */
optarg = NULL;
if (*place == 0)
++optind;
} else {
/* Option-argument is either the rest of this argument or the
entire next argument. */
if (*place)
optarg = place;
else if (oli[2] == ':')
/*
* GNU Extension, for optional arguments if the rest of
* the argument is empty, we return NULL
*/
optarg = NULL;
else if (nargc > ++optind)
optarg = nargv[optind];
else {
/* option-argument absent */
place = EMSG;
if (*ostr == ':')
return (BADARG);
if (opterr)
(void)fprintf(stderr,
"%s: option requires an argument -- %c\n",
nargv[0],
optopt);
return (BADCH);
}
place = EMSG;
++optind;
}
return (optopt); /* return option letter */
}

View File

@@ -46,6 +46,7 @@
#include "libssh/options.h"
#include "libssh/config_parser.h"
#include "libssh/gssapi.h"
#include "libssh/getopt.h"
#include "libssh/token.h"
#ifdef WITH_SERVER
#include "libssh/server.h"
@@ -1838,13 +1839,6 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value)
*/
int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
{
#ifdef _MSC_VER
(void)session;
(void)argcptr;
(void)argv;
/* Not supported with a Microsoft compiler */
return -1;
#else
char *user = NULL;
char *cipher = NULL;
char *identity = NULL;
@@ -2008,7 +2002,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
}
return SSH_OK;
#endif
}
/**

View File

@@ -6,6 +6,7 @@ set(LIBSSH_UNIT_TESTS
torture_bytearray
torture_callbacks
torture_crypto
torture_getopt
torture_init
torture_list
torture_misc

View File

@@ -0,0 +1,263 @@
#include "config.h"
#define LIBSSH_STATIC
#include "libssh/getopt.h"
#include "torture.h"
#include <string.h>
/*
* Dedicated unit tests for the getopt abstraction layer.
* On systems with native getopt, this tests the system implementation.
* On MSVC, this tests the bundled fallback in src/external/getopt.c.
*/
static int setup(void **state)
{
(void)state;
/* Reset getopt state before each test */
optind = 1;
optopt = 0;
optarg = NULL;
opterr = 1;
return 0;
}
static void torture_getopt_basic(void **state)
{
const char *argv[] = {"prog", "-a", "-b", NULL};
int argc = 3;
int opt;
(void)state;
opt = getopt(argc, (char *const *)argv, "ab");
assert_int_equal(opt, 'a');
opt = getopt(argc, (char *const *)argv, "ab");
assert_int_equal(opt, 'b');
opt = getopt(argc, (char *const *)argv, "ab");
assert_int_equal(opt, -1);
}
static void torture_getopt_with_argument(void **state)
{
const char *argv[] = {"prog", "-f", "filename", NULL};
int argc = 3;
int opt;
(void)state;
opt = getopt(argc, (char *const *)argv, "f:");
assert_int_equal(opt, 'f');
assert_non_null(optarg);
assert_string_equal(optarg, "filename");
opt = getopt(argc, (char *const *)argv, "f:");
assert_int_equal(opt, -1);
}
static void torture_getopt_optional_argument(void **state)
{
int opt;
(void)state;
/* "f::" with no attached value: optarg must be NULL */
{
const char *argv[] = {"prog", "-f", NULL};
int argc = 2;
optind = 1;
optarg = NULL;
opt = getopt(argc, (char *const *)argv, "f::");
assert_int_equal(opt, 'f');
assert_null(optarg);
assert_int_equal(optind, 2);
}
/* "f::" with attached value in same argv element: "-ffile" */
{
const char *argv[] = {"prog", "-ffile", NULL};
int argc = 2;
optind = 1;
optarg = NULL;
opt = getopt(argc, (char *const *)argv, "f::");
assert_int_equal(opt, 'f');
assert_non_null(optarg);
assert_string_equal(optarg, "file");
assert_int_equal(optind, 2);
}
/* "f::" with value as separate argv element: optarg should be NULL
* because the GNU :: extension only consumes attached arguments */
{
const char *argv[] = {"prog", "-f", "file", NULL};
int argc = 3;
optind = 1;
optarg = NULL;
opt = getopt(argc, (char *const *)argv, "f::");
assert_int_equal(opt, 'f');
assert_null(optarg);
assert_int_equal(optind, 2);
}
}
static void torture_getopt_unknown_option(void **state)
{
const char *argv[] = {"prog", "-z", NULL};
int argc = 2;
int opt;
(void)state;
opterr = 0; /* suppress error output */
opt = getopt(argc, (char *const *)argv, "ab");
assert_int_equal(opt, '?');
assert_int_equal(optopt, 'z');
}
static void torture_getopt_missing_argument(void **state)
{
const char *argv[] = {"prog", "-f", NULL};
int argc = 2;
int opt;
(void)state;
opterr = 0;
opt = getopt(argc, (char *const *)argv, "f:");
assert_int_equal(opt, '?');
assert_int_equal(optopt, 'f');
}
static void torture_getopt_missing_argument_colon(void **state)
{
const char *argv[] = {"prog", "-f", NULL};
int argc = 2;
int opt;
(void)state;
/* Leading ':' in optstring: missing argument returns ':' not '?' */
opterr = 0;
opt = getopt(argc, (char *const *)argv, ":f:");
assert_int_equal(opt, ':');
assert_int_equal(optopt, 'f');
/* Note: optind value after error is implementation-defined,
* differs between glibc (2), musl (3), and FreeBSD (2) */
}
static void torture_getopt_double_dash(void **state)
{
const char *argv[] = {"prog", "--", "-a", NULL};
int argc = 3;
int opt;
(void)state;
opt = getopt(argc, (char *const *)argv, "a");
assert_int_equal(opt, -1);
assert_int_equal(optind, 2);
}
static void torture_getopt_combined_options(void **state)
{
const char *argv[] = {"prog", "-abc", NULL};
int argc = 2;
int opt;
(void)state;
opt = getopt(argc, (char *const *)argv, "abc");
assert_int_equal(opt, 'a');
opt = getopt(argc, (char *const *)argv, "abc");
assert_int_equal(opt, 'b');
opt = getopt(argc, (char *const *)argv, "abc");
assert_int_equal(opt, 'c');
opt = getopt(argc, (char *const *)argv, "abc");
assert_int_equal(opt, -1);
}
static void torture_getopt_optind_advance(void **state)
{
const char *argv[] = {"prog", "-a", "nonoption", NULL};
int argc = 3;
int opt;
(void)state;
opt = getopt(argc, (char *const *)argv, "a");
assert_int_equal(opt, 'a');
assert_int_equal(optind, 2);
opt = getopt(argc, (char *const *)argv, "a");
assert_int_equal(opt, -1);
/* optind should point to the non-option argument */
assert_int_equal(optind, 2);
}
static void torture_getopt_reset(void **state)
{
const char *argv[] = {"prog", "-a", "-b", NULL};
int argc = 3;
int opt;
(void)state;
opt = getopt(argc, (char *const *)argv, "ab");
assert_int_equal(opt, 'a');
/* Reset and parse again */
optind = 1;
opt = getopt(argc, (char *const *)argv, "ab");
assert_int_equal(opt, 'a');
}
static void torture_getopt_no_options(void **state)
{
const char *argv[] = {"prog", NULL};
int argc = 1;
int opt;
(void)state;
opt = getopt(argc, (char *const *)argv, "ab");
assert_int_equal(opt, -1);
}
int torture_run_tests(void)
{
int rc;
struct CMUnitTest tests[] = {
cmocka_unit_test_setup(torture_getopt_basic, setup),
cmocka_unit_test_setup(torture_getopt_with_argument, setup),
cmocka_unit_test_setup(torture_getopt_optional_argument, setup),
cmocka_unit_test_setup(torture_getopt_unknown_option, setup),
cmocka_unit_test_setup(torture_getopt_missing_argument, setup),
cmocka_unit_test_setup(torture_getopt_missing_argument_colon, setup),
cmocka_unit_test_setup(torture_getopt_double_dash, setup),
cmocka_unit_test_setup(torture_getopt_combined_options, setup),
cmocka_unit_test_setup(torture_getopt_optind_advance, setup),
cmocka_unit_test_setup(torture_getopt_reset, setup),
cmocka_unit_test_setup(torture_getopt_no_options, setup),
};
ssh_init();
torture_filter_tests(tests);
rc = cmocka_run_group_tests(tests, NULL, NULL);
ssh_finalize();
return rc;
}

View File

@@ -1673,12 +1673,6 @@ static void torture_options_getopt(void **state)
/* Test with all the supported options */
rc = ssh_options_getopt(session, &argc, (char **)argv);
#ifdef _MSC_VER
UNUSED_VAR(new_level);
/* Not supported in windows */
assert_ssh_return_code_equal(session, rc, -1);
#else
assert_ssh_return_code(session, rc);
/* Restore the log level to previous value first */
@@ -1812,12 +1806,10 @@ static void torture_options_getopt(void **state)
assert_int_equal(argc, 1);
assert_string_equal(argv[0], EXECUTABLE_NAME);
#endif /* _NSC_VER */
}
static void torture_options_getopt_o_option(void **state)
{
#ifndef _MSC_VER
ssh_session session = *state;
int rc;
enum ssh_config_opcode_e opcode =
@@ -1914,7 +1906,6 @@ static void torture_options_getopt_o_option(void **state)
opcode = ssh_config_get_opcode((char *)"rekeylimit");
assert_int_equal(session->opts.options_seen[opcode], 1);
#endif /* _MSC_VER */
}
static void torture_options_plus_sign(void **state)