@@ -85,11 +85,13 @@
fail-fast: false
matrix:
arch: [x86, x64]
- test: [argv, auth_token, buffer, cryptoapi, crypto, misc, ncp, packet_id, pkt, provider, tls_crypt]
+ test: [argv, auth_token, buffer, cryptoapi, crypto, misc, ncp, packet_id, pkt, provider, tls_crypt, user_pass]
runs-on: windows-latest
name: "mingw unittest ${{ matrix.test }} - ${{ matrix.arch }} - OSSL"
steps:
+ - name: Checkout OpenVPN
+ uses: actions/checkout@v3
- name: Retrieve mingw unittest
uses: actions/download-artifact@v3
with:
@@ -97,6 +99,8 @@
path: unittests
- name: Run ${{ matrix.test }} unit test
run: ./unittests/test_${{ matrix.test }}.exe
+ env:
+ srcdir: "${{ github.workspace }}/tests/unit_tests/openvpn"
ubuntu:
strategy:
@@ -279,6 +283,7 @@
configurePreset: win-${{ matrix.arch }}-release
buildPreset: win-${{ matrix.arch }}-release
testPreset: win-${{ matrix.arch }}-release
+ testPresetAdditionalArgs: "['--output-on-failure']"
- uses: actions/upload-artifact@v3
with:
@@ -592,6 +592,7 @@
"test_packet_id"
"test_pkt"
"test_provider"
+ "test_user_pass"
)
if (WIN32)
@@ -650,6 +651,10 @@
# test_networking needs special environment
if (NOT ${test_name} STREQUAL "test_networking")
add_test(${test_name} ${test_name})
+ # for compat with autotools make check
+ set(_UT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests/unit_tests/openvpn)
+ set_tests_properties(${test_name} PROPERTIES
+ ENVIRONMENT "srcdir=${_UT_SOURCE_DIR}")
endif ()
add_executable(${test_name}
tests/unit_tests/openvpn/${test_name}.c
@@ -744,6 +749,15 @@
src/openvpn/base64.c
)
+ target_sources(test_user_pass PRIVATE
+ tests/unit_tests/openvpn/mock_get_random.c
+ tests/unit_tests/openvpn/mock_win32_execve.c
+ src/openvpn/base64.c
+ src/openvpn/console.c
+ src/openvpn/env_set.c
+ src/openvpn/run_command.c
+ )
+
if (TARGET test_argv)
target_link_options(test_argv PRIVATE -Wl,--wrap=parse_line)
target_sources(test_argv PRIVATE
@@ -118,12 +118,31 @@
#define GET_USER_PASS_INLINE_CREDS (1<<10) /* indicates that auth_file is actually inline creds */
+/**
+ * Retrieves the user credentials from various sources depending on the flags.
+ *
+ * @param up The user_pass structure to store the retrieved credentials.
+ * @param auth_file The path to the authentication file. Might be NULL.
+ * @param prefix The prefix to prepend to user prompts.
+ * @param flags Additional flags to control the behavior of the function.
+ * @param auth_challenge The authentication challenge string.
+ * @return true if the user credentials were successfully retrieved, false otherwise.
+ */
bool get_user_pass_cr(struct user_pass *up,
const char *auth_file,
const char *prefix,
const unsigned int flags,
const char *auth_challenge);
+/**
+ * Retrieves the user credentials from various sources depending on the flags.
+ *
+ * @param up The user_pass structure to store the retrieved credentials.
+ * @param auth_file The path to the authentication file. Might be NULL.
+ * @param prefix The prefix to prepend to user prompts.
+ * @param flags Additional flags to control the behavior of the function.
+ * @return true if the user credentials were successfully retrieved, false otherwise.
+ */
static inline bool
get_user_pass(struct user_pass *up,
const char *auth_file,
@@ -524,4 +524,10 @@
#define ENABLE_MEMSTATS
#endif
+#ifdef _MSC_VER
+#ifndef PATH_MAX
+#define PATH_MAX MAX_PATH
+#endif
+#endif
+
#endif /* ifndef SYSHEAD_H */
@@ -1,5 +1,7 @@
AUTOMAKE_OPTIONS = foreign
+EXTRA_DIST = input
+
AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING) Unit-Tests'
test_binaries=
@@ -9,7 +11,7 @@
endif
test_binaries += crypto_testdriver packet_id_testdriver auth_token_testdriver ncp_testdriver misc_testdriver \
- pkt_testdriver
+ pkt_testdriver user_pass_testdriver
if HAVE_LD_WRAP_SUPPORT
if !WIN32
test_binaries += tls_crypt_testdriver
@@ -216,6 +218,22 @@
$(top_srcdir)/src/openvpn/base64.c
+user_pass_testdriver_CFLAGS = @TEST_CFLAGS@ \
+ -I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn
+user_pass_testdriver_LDFLAGS = @TEST_LDFLAGS@
+
+user_pass_testdriver_SOURCES = test_user_pass.c mock_msg.c \
+ $(top_srcdir)/src/openvpn/buffer.c \
+ $(top_srcdir)/src/openvpn/console.c \
+ $(top_srcdir)/src/openvpn/env_set.c \
+ mock_win32_execve.c \
+ $(top_srcdir)/src/openvpn/run_command.c \
+ mock_get_random.c \
+ $(top_srcdir)/src/openvpn/platform.c \
+ $(top_srcdir)/src/openvpn/win32-util.c \
+ $(top_srcdir)/src/openvpn/base64.c
+
+
ncp_testdriver_CFLAGS = @TEST_CFLAGS@ \
-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \
$(OPTIONAL_CRYPTO_CFLAGS)
new file mode 100644
@@ -0,0 +1 @@
+fuser
new file mode 100644
@@ -0,0 +1,2 @@
+fuser
+fpassword
new file mode 100644
@@ -0,0 +1,260 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2023 OpenVPN Inc <sales@openvpn.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#undef ENABLE_SYSTEMD
+
+#include "syshead.h"
+#include "manage.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "misc.c"
+
+struct management *management; /* global */
+
+/* mocking */
+bool
+query_user_exec_builtin(void)
+{
+ /* Loop through configured query_user slots */
+ for (int i = 0; i < QUERY_USER_NUMSLOTS && query_user[i].response != NULL; i++)
+ {
+ check_expected(query_user[i].prompt);
+ strncpy(query_user[i].response, mock_ptr_type(char *), query_user[i].response_len);
+ }
+
+ return mock();
+}
+void
+management_auth_failure(struct management *man, const char *type, const char *reason)
+{
+ assert_true(0);
+}
+bool
+management_query_user_pass(struct management *man,
+ struct user_pass *up,
+ const char *type,
+ const unsigned int flags,
+ const char *static_challenge)
+{
+ assert_true(0);
+ return false;
+}
+/* stubs for some unused functions instead of pulling in too many dependencies */
+int
+parse_line(const char *line, char **p, const int n, const char *file,
+ const int line_num, int msglevel, struct gc_arena *gc)
+{
+ assert_true(0);
+ return 0;
+}
+
+/* tooling */
+static void
+reset_user_pass(struct user_pass *up)
+{
+ up->defined = false;
+ up->token_defined = false;
+ up->nocache = false;
+ strcpy(up->username, "user");
+ strcpy(up->password, "password");
+}
+
+static void
+test_get_user_pass_defined(void **state)
+{
+ struct user_pass up = { 0 };
+ reset_user_pass(&up);
+ up.defined = true;
+ assert_true(get_user_pass_cr(&up, NULL, "UT", 0, NULL));
+}
+
+static void
+test_get_user_pass_needok(void **state)
+{
+ struct user_pass up = { 0 };
+ reset_user_pass(&up);
+ unsigned int flags = GET_USER_PASS_NEED_OK;
+
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "NEED-OK|UT|user:");
+ will_return(query_user_exec_builtin, "");
+ will_return(query_user_exec_builtin, true);
+ /*FIXME: query_user_exec() called even though nothing queued */
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, NULL, "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.password, "ok");
+
+ reset_user_pass(&up);
+
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "NEED-OK|UT|user:");
+ will_return(query_user_exec_builtin, "cancel");
+ will_return(query_user_exec_builtin, true);
+ /*FIXME: query_user_exec() called even though nothing queued */
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, NULL, "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.password, "cancel");
+}
+
+static void
+test_get_user_pass_inline_creds(void **state)
+{
+ struct user_pass up = { 0 };
+ reset_user_pass(&up);
+ unsigned int flags = GET_USER_PASS_INLINE_CREDS;
+
+ /*FIXME: query_user_exec() called even though nothing queued */
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, "iuser\nipassword", "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "iuser");
+ assert_string_equal(up.password, "ipassword");
+
+ reset_user_pass(&up);
+
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "Enter UT Password:");
+ will_return(query_user_exec_builtin, "cpassword");
+ will_return(query_user_exec_builtin, true);
+ /* will try to retrieve missing password from stdin */
+ assert_true(get_user_pass_cr(&up, "iuser", "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "iuser");
+ assert_string_equal(up.password, "cpassword");
+
+ reset_user_pass(&up);
+
+ flags |= GET_USER_PASS_PASSWORD_ONLY;
+ /*FIXME: query_user_exec() called even though nothing queued */
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, "ipassword\n", "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "user");
+ assert_string_equal(up.password, "ipassword");
+
+ reset_user_pass(&up);
+
+ flags |= GET_USER_PASS_PASSWORD_ONLY;
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "Enter UT Password:");
+ will_return(query_user_exec_builtin, "cpassword");
+ will_return(query_user_exec_builtin, true);
+ /* will try to retrieve missing password from stdin */
+ assert_true(get_user_pass_cr(&up, "", "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "user");
+ assert_string_equal(up.password, "cpassword");
+}
+
+static void
+test_get_user_pass_authfile_stdin(void **state)
+{
+ struct user_pass up = { 0 };
+ reset_user_pass(&up);
+ unsigned int flags = 0;
+
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "Enter UT Username:");
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "Enter UT Password:");
+ will_return(query_user_exec_builtin, "cuser");
+ will_return(query_user_exec_builtin, "cpassword");
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, "stdin", "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "cuser");
+ assert_string_equal(up.password, "cpassword");
+
+ reset_user_pass(&up);
+
+ flags |= GET_USER_PASS_PASSWORD_ONLY;
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "Enter UT Password:");
+ will_return(query_user_exec_builtin, "cpassword");
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, "stdin", "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "user");
+ assert_string_equal(up.password, "cpassword");
+}
+
+static void
+test_get_user_pass_authfile_file(void **state)
+{
+ struct user_pass up = { 0 };
+ reset_user_pass(&up);
+ unsigned int flags = 0;
+
+ const char *srcdir = getenv("srcdir");
+ assert_non_null(srcdir);
+ char authfile[PATH_MAX] = { 0 };
+
+ snprintf(authfile, PATH_MAX, "%s/%s", srcdir, "input/user_pass.txt");
+ /*FIXME: query_user_exec() called even though nothing queued */
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, authfile, "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "fuser");
+ assert_string_equal(up.password, "fpassword");
+
+ reset_user_pass(&up);
+
+ snprintf(authfile, PATH_MAX, "%s/%s", srcdir, "input/user_only.txt");
+ expect_string(query_user_exec_builtin, query_user[i].prompt, "Enter UT Password:");
+ will_return(query_user_exec_builtin, "cpassword");
+ will_return(query_user_exec_builtin, true);
+ /* will try to retrieve missing password from stdin */
+ assert_true(get_user_pass_cr(&up, authfile, "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "fuser");
+ assert_string_equal(up.password, "cpassword");
+
+ reset_user_pass(&up);
+
+ flags |= GET_USER_PASS_PASSWORD_ONLY;
+ snprintf(authfile, PATH_MAX, "%s/%s", srcdir, "input/user_only.txt");
+ /*FIXME: query_user_exec() called even though nothing queued */
+ will_return(query_user_exec_builtin, true);
+ assert_true(get_user_pass_cr(&up, authfile, "UT", flags, NULL));
+ assert_true(up.defined);
+ assert_string_equal(up.username, "user");
+ assert_string_equal(up.password, "fuser");
+}
+
+const struct CMUnitTest user_pass_tests[] = {
+ cmocka_unit_test(test_get_user_pass_defined),
+ cmocka_unit_test(test_get_user_pass_needok),
+ cmocka_unit_test(test_get_user_pass_inline_creds),
+ cmocka_unit_test(test_get_user_pass_authfile_stdin),
+ cmocka_unit_test(test_get_user_pass_authfile_file),
+};
+
+int
+main(void)
+{
+ return cmocka_run_group_tests(user_pass_tests, NULL, NULL);
+}