From: Jens Neuhalfen <j...@neuhalfen.name> No functional changes.
Utility functions of auth-pam are split into a dedicated file. This allows the test programs to easily test these functions without adding dependencies. Add a minimal test for searchandreplace as a proof of concept. Signed-off-by: Jens Neuhalfen <j...@neuhalfen.name> --- configure.ac | 2 + src/plugins/auth-pam/Makefile.am | 1 + src/plugins/auth-pam/auth-pam.c | 91 +---------------- src/plugins/auth-pam/utils.c | 113 +++++++++++++++++++++ src/plugins/auth-pam/utils.h | 54 ++++++++++ tests/unit_tests/Makefile.am | 2 +- tests/unit_tests/plugins/Makefile.am | 3 + tests/unit_tests/plugins/auth-pam/Makefile.am | 12 +++ .../plugins/auth-pam/test_search_and_replace.c | 79 ++++++++++++++ 9 files changed, 266 insertions(+), 91 deletions(-) create mode 100644 src/plugins/auth-pam/utils.c create mode 100644 src/plugins/auth-pam/utils.h create mode 100644 tests/unit_tests/plugins/Makefile.am create mode 100644 tests/unit_tests/plugins/auth-pam/Makefile.am create mode 100644 tests/unit_tests/plugins/auth-pam/test_search_and_replace.c diff --git a/configure.ac b/configure.ac index fb3fa3c..5e69f91 100644 --- a/configure.ac +++ b/configure.ac @@ -1230,6 +1230,8 @@ AC_CONFIG_FILES([ src/plugins/down-root/Makefile tests/Makefile tests/unit_tests/Makefile + tests/unit_tests/plugins/Makefile + tests/unit_tests/plugins/auth-pam/Makefile tests/unit_tests/example_test/Makefile vendor/Makefile sample/Makefile diff --git a/src/plugins/auth-pam/Makefile.am b/src/plugins/auth-pam/Makefile.am index 2aef311..e6dc27e 100644 --- a/src/plugins/auth-pam/Makefile.am +++ b/src/plugins/auth-pam/Makefile.am @@ -18,6 +18,7 @@ dist_doc_DATA = README.auth-pam endif openvpn_plugin_auth_pam_la_SOURCES = \ + utils.c \ auth-pam.c \ pamdl.c pamdl.h \ auth-pam.exports diff --git a/src/plugins/auth-pam/auth-pam.c b/src/plugins/auth-pam/auth-pam.c index 710accc..5ad3ec8 100644 --- a/src/plugins/auth-pam/auth-pam.c +++ b/src/plugins/auth-pam/auth-pam.c @@ -39,7 +39,6 @@ #include <stdio.h> #include <string.h> #include <ctype.h> -#include <stdbool.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> @@ -48,7 +47,7 @@ #include <fcntl.h> #include <signal.h> #include <syslog.h> -#include <stdint.h> +#include "utils.h" #include <openvpn-plugin.h> @@ -117,94 +116,6 @@ struct user_pass { /* Background process function */ static void pam_server (int fd, const char *service, int verb, const struct name_value_list *name_value_list); -/* Read 'tosearch', replace all occurences of 'searchfor' with 'replacewith' and return - * a pointer to the NEW string. Does not modify the input strings. Will not enter an - * infinite loop with clever 'searchfor' and 'replacewith' strings. - * Daniel Johnson - progman2...@usa.net / djohn...@progman.us - * - * Retuns NULL when - * - any parameter is NULL - * - the worst-case result is to large ( >= SIZE_MAX) - */ -static char * -searchandreplace(const char *tosearch, const char *searchfor, const char *replacewith) -{ - if (!tosearch || !searchfor || !replacewith) return NULL; - - size_t tosearchlen = strlen(tosearch); - size_t replacewithlen = strlen(replacewith); - size_t templen = tosearchlen * replacewithlen; - - if (tosearchlen == 0 || strlen(searchfor) == 0 || replacewithlen == 0) { - return NULL; - } - - bool is_potential_integer_overflow = (templen == SIZE_MAX) || (templen / tosearchlen != replacewithlen); - - if (is_potential_integer_overflow) { - return NULL; - } - - // state: all parameters are valid - - const char *searching=tosearch; - char *scratch; - - char temp[templen+1]; - temp[0]=0; - - scratch = strstr(searching,searchfor); - if (!scratch) return strdup(tosearch); - - while (scratch) { - strncat(temp,searching,scratch-searching); - strcat(temp,replacewith); - - searching=scratch+strlen(searchfor); - scratch = strstr(searching,searchfor); - } - return strdup(temp); -} - -/* - * Given an environmental variable name, search - * the envp array for its value, returning it - * if found or NULL otherwise. - */ -static const char * -get_env (const char *name, const char *envp[]) -{ - if (envp) - { - int i; - const int namelen = strlen (name); - for (i = 0; envp[i]; ++i) - { - if (!strncmp (envp[i], name, namelen)) - { - const char *cp = envp[i] + namelen; - if (*cp == '=') - return cp + 1; - } - } - } - return NULL; -} - -/* - * Return the length of a string array - */ -static int -string_array_len (const char *array[]) -{ - int i = 0; - if (array) - { - while (array[i]) - ++i; - } - return i; -} /* * Socket read/write functions. diff --git a/src/plugins/auth-pam/utils.c b/src/plugins/auth-pam/utils.c new file mode 100644 index 0000000..4f2bec1 --- /dev/null +++ b/src/plugins/auth-pam/utils.c @@ -0,0 +1,113 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sa...@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * OpenVPN plugin module to do PAM authentication using a split + * privilege model. + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +#include <string.h> +#include <ctype.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/types.h> +#include <stdint.h> + +#include "utils.h" + +char * +searchandreplace(const char *tosearch, const char *searchfor, const char *replacewith) +{ + if (!tosearch || !searchfor || !replacewith) return NULL; + + size_t tosearchlen = strlen(tosearch); + size_t replacewithlen = strlen(replacewith); + size_t templen = tosearchlen * replacewithlen; + + if (tosearchlen == 0 || strlen(searchfor) == 0 || replacewithlen == 0) { + return NULL; + } + + bool is_potential_integer_overflow = (templen == SIZE_MAX) || (templen / tosearchlen != replacewithlen); + + if (is_potential_integer_overflow) { + return NULL; + } + + // state: all parameters are valid + + const char *searching=tosearch; + char *scratch; + + char temp[templen+1]; + temp[0]=0; + + scratch = strstr(searching,searchfor); + if (!scratch) return strdup(tosearch); + + while (scratch) { + strncat(temp,searching,scratch-searching); + strcat(temp,replacewith); + + searching=scratch+strlen(searchfor); + scratch = strstr(searching,searchfor); + } + return strdup(temp); +} + +const char * +get_env (const char *name, const char *envp[]) +{ + if (envp) + { + int i; + const int namelen = strlen (name); + for (i = 0; envp[i]; ++i) + { + if (!strncmp (envp[i], name, namelen)) + { + const char *cp = envp[i] + namelen; + if (*cp == '=') + return cp + 1; + } + } + } + return NULL; +} + +int +string_array_len (const char *array[]) +{ + int i = 0; + if (array) + { + while (array[i]) + ++i; + } + return i; +} diff --git a/src/plugins/auth-pam/utils.h b/src/plugins/auth-pam/utils.h new file mode 100644 index 0000000..d896f89 --- /dev/null +++ b/src/plugins/auth-pam/utils.h @@ -0,0 +1,54 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sa...@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PLUGIN_AUTH_PAM_UTILS__H +#define _PLUGIN_AUTH_PAM_UTILS__H + +/* Read 'tosearch', replace all occurences of 'searchfor' with 'replacewith' and return + * a pointer to the NEW string. Does not modify the input strings. Will not enter an + * infinite loop with clever 'searchfor' and 'replacewith' strings. + * Daniel Johnson - progman2...@usa.net / djohn...@progman.us + * + * Retuns NULL when + * - any parameter is NULL + * - the worst-case result is to large ( >= SIZE_MAX) + */ +char * +searchandreplace(const char *tosearch, const char *searchfor, const char *replacewith); + +/* + * Given an environmental variable name, search + * the envp array for its value, returning it + * if found or NULL otherwise. + */ +const char * +get_env (const char *name, const char *envp[]); + +/* + * Return the length of a string array + */ +int +string_array_len (const char *array[]); + +#endif diff --git a/tests/unit_tests/Makefile.am b/tests/unit_tests/Makefile.am index 18267bd..e076db8 100644 --- a/tests/unit_tests/Makefile.am +++ b/tests/unit_tests/Makefile.am @@ -1,3 +1,3 @@ AUTOMAKE_OPTIONS = foreign -SUBDIRS = example_test +SUBDIRS = example_test plugins diff --git a/tests/unit_tests/plugins/Makefile.am b/tests/unit_tests/plugins/Makefile.am new file mode 100644 index 0000000..a3696d5 --- /dev/null +++ b/tests/unit_tests/plugins/Makefile.am @@ -0,0 +1,3 @@ +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = auth-pam diff --git a/tests/unit_tests/plugins/auth-pam/Makefile.am b/tests/unit_tests/plugins/auth-pam/Makefile.am new file mode 100644 index 0000000..07233ee --- /dev/null +++ b/tests/unit_tests/plugins/auth-pam/Makefile.am @@ -0,0 +1,12 @@ +AUTOMAKE_OPTIONS = foreign + +if ENABLE_PLUGIN_AUTH_PAM +check_PROGRAMS = auth_pam_testdriver +TESTS = $(check_PROGRAMS) +endif + +sut_sourcedir = $(top_srcdir)/src/plugins/auth-pam + +auth_pam_testdriver_SOURCES = test_search_and_replace.c $(sut_sourcedir)/utils.h $(sut_sourcedir)/utils.c +auth_pam_testdriver_CFLAGS = @TEST_CFLAGS@ -I$(sut_sourcedir) +auth_pam_testdriver_LDFLAGS = @TEST_LDFLAGS@ diff --git a/tests/unit_tests/plugins/auth-pam/test_search_and_replace.c b/tests/unit_tests/plugins/auth-pam/test_search_and_replace.c new file mode 100644 index 0000000..d6b8a91 --- /dev/null +++ b/tests/unit_tests/plugins/auth-pam/test_search_and_replace.c @@ -0,0 +1,79 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "utils.h" + +static void pass_any_null_param__returns_null() { + + char DUMMY[] = "DUMMY"; + + assert_null(searchandreplace(NULL,DUMMY,DUMMY)); + assert_null(searchandreplace(DUMMY,NULL,DUMMY)); + assert_null(searchandreplace(DUMMY,DUMMY,NULL)); +} + +static void pass_any_empty_string__returns_null() { + + char DUMMY[] = "DUMMY"; + char EMPTY[] = ""; + + assert_null(searchandreplace(EMPTY,DUMMY,DUMMY)); + assert_null(searchandreplace(DUMMY,EMPTY,DUMMY)); + assert_null(searchandreplace(DUMMY,DUMMY,EMPTY)); +} + +static void replace_single_char__one_time__match_is_replaced() { + char *replaced = searchandreplace("X","X","Y"); + + assert_non_null(replaced); + assert_string_equal("Y", replaced); + + free(replaced); +} + +static void replace_single_char__multiple_times__match_all_matches_are_replaced() { + char *replaced = searchandreplace("XaX","X","Y"); + + assert_non_null(replaced); + assert_string_equal ("YaY", replaced); + + free(replaced); +} + +static void replace_longer_text__multiple_times__match_all_matches_are_replaced() { + char *replaced = searchandreplace("XXaXX","XX","YY"); + + assert_non_null(replaced); + assert_string_equal ("YYaYY", replaced); + + free(replaced); +} + +static void pattern_not_found__returns_original() { + char *replaced = searchandreplace("abc","X","Y"); + + assert_non_null(replaced); + assert_string_equal ("abc", replaced); + + free(replaced); +} + + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(pass_any_null_param__returns_null), + cmocka_unit_test(pass_any_empty_string__returns_null), + cmocka_unit_test(replace_single_char__one_time__match_is_replaced), + cmocka_unit_test(replace_single_char__multiple_times__match_all_matches_are_replaced), + cmocka_unit_test(replace_longer_text__multiple_times__match_all_matches_are_replaced), + cmocka_unit_test(pattern_not_found__returns_original), + }; + + return cmocka_run_group_tests_name("searchandreplace", tests, NULL, NULL); +} + -- 2.8.3