1 /*
2  * Copyright (c) 2002-2013 Balabit
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * As an additional exemption you are allowed to compile & link against the
19  * OpenSSL libraries as published by the OpenSSL project. See the file
20  * COPYING for details.
21  *
22  */
23 #include "cfg-lexer-subst.h"
24 #include "apphook.h"
25 #include <criterion/criterion.h>
26 #include <string.h>
27 
28 static CfgArgs *
construct_cfg_args_for_args(const gchar * additional_values[])29 construct_cfg_args_for_args(const gchar *additional_values[])
30 {
31   CfgArgs *args = cfg_args_new();
32   gint i;
33 
34   cfg_args_set(args, "arg", "arg_value");
35   cfg_args_set(args, "simple_string", "\"simple_string_value\"");
36   cfg_args_set(args, "simple_qstring", "'simple_qstring_value'");
37   cfg_args_set(args, "escaped_string", "\"escaped_string\\\"\\r\\n\"");
38 
39   cr_assert(cfg_args_contains(args, "simple-string"), "normalize: when set, must contain: simple-string");
40   cr_assert(cfg_args_contains(args, "simple_string"), "normalize: when set, must contain: simple_string");
41 
42   for (i = 0; additional_values && additional_values[i] && additional_values[i + 1]; i += 2)
43     {
44       cfg_args_set(args, additional_values[i], additional_values[i + 1]);
45     }
46   return args;
47 }
48 
49 static CfgArgs *
construct_cfg_args_for_defaults(void)50 construct_cfg_args_for_defaults(void)
51 {
52   CfgArgs *args = cfg_args_new();
53   cfg_args_set(args, "arg", "default_for_arg");
54   cfg_args_set(args, "def", "default_for_def");
55   return args;
56 }
57 
58 static CfgArgs *
construct_cfg_args_for_globals(void)59 construct_cfg_args_for_globals(void)
60 {
61   CfgArgs *args = cfg_args_new();
62   cfg_args_set(args, "arg", "global_for_arg");
63   cfg_args_set(args, "def", "global_for_def");
64   cfg_args_set(args, "globl", "global_for_globl");
65   return args;
66 }
67 
68 static CfgLexerSubst *
construct_object_with_values(const gchar * additional_values[])69 construct_object_with_values(const gchar *additional_values[])
70 {
71   return cfg_lexer_subst_new(construct_cfg_args_for_globals(),
72                              construct_cfg_args_for_defaults(),
73                              construct_cfg_args_for_args(additional_values));
74 }
75 
76 static CfgLexerSubst *
construct_object(void)77 construct_object(void)
78 {
79   return construct_object_with_values(NULL);
80 }
81 
82 static gchar *
duplicate_string_without_with_trailing_nonzero_bytes(const gchar * input)83 duplicate_string_without_with_trailing_nonzero_bytes(const gchar *input)
84 {
85   gsize input_len = strlen(input);
86   const gint canary_size = 10;
87   gchar *input_dup = malloc(input_len + canary_size);
88 
89   memcpy(input_dup, input, input_len);
90   memset(input_dup + input_len, 'A', canary_size - 1);
91   input_dup[input_len + canary_size - 1] = 0;
92   return input_dup;
93 }
94 
95 static void
_assert_invoke_result(CfgLexerSubst * subst,gchar * input_dup,gssize input_len,const gchar * expected_output)96 _assert_invoke_result(CfgLexerSubst *subst, gchar *input_dup, gssize input_len, const gchar *expected_output)
97 {
98   gchar *result;
99   gsize result_len;
100   GError *error = NULL;
101 
102   result = cfg_lexer_subst_invoke(subst, input_dup, input_len, &result_len, &error);
103   cr_assert_null(error, "Error value is non-null while no error is expected");
104   cr_assert_not_null(result, "value substitution returned an unexpected failure");
105   cr_assert_str_eq(result, expected_output, "value substitution is broken");
106   cr_assert_eq(result_len, strlen(expected_output), "length returned by invoke_result is invalid");
107   g_free(result);
108 }
109 
110 static void
assert_invoke_result_with_input_length_explicit(CfgLexerSubst * subst,const gchar * input,const gchar * expected_output)111 assert_invoke_result_with_input_length_explicit(CfgLexerSubst *subst, const gchar *input, const gchar *expected_output)
112 {
113   gchar *input_dup;
114 
115   input_dup = duplicate_string_without_with_trailing_nonzero_bytes(input);
116   _assert_invoke_result(subst, input_dup, strlen(input), expected_output);
117   g_free(input_dup);
118 }
119 
120 static void
assert_invoke_result_with_zero_terminated_input(CfgLexerSubst * subst,const gchar * input,const gchar * expected_output)121 assert_invoke_result_with_zero_terminated_input(CfgLexerSubst *subst, const gchar *input, const gchar *expected_output)
122 {
123   gchar *input_dup;
124 
125   input_dup = strdup(input);
126   _assert_invoke_result(subst, input_dup, -1, expected_output);
127   g_free(input_dup);
128 }
129 
130 static void
assert_invoke_result(CfgLexerSubst * subst,const gchar * input,const gchar * expected_output)131 assert_invoke_result(CfgLexerSubst *subst, const gchar *input, const gchar *expected_output)
132 {
133   assert_invoke_result_with_input_length_explicit(subst, input, expected_output);
134   assert_invoke_result_with_zero_terminated_input(subst, input, expected_output);
135 }
136 
137 static void
assert_invoke_failure(CfgLexerSubst * subst,const gchar * input,const gchar * expected_error)138 assert_invoke_failure(CfgLexerSubst *subst, const gchar *input, const gchar *expected_error)
139 {
140   gchar *result;
141   gsize result_len;
142   gchar *input_dup = g_strdup(input);
143   GError *error = NULL;
144 
145   result = cfg_lexer_subst_invoke(subst, input_dup, strlen(input), &result_len, &error);
146   cr_assert_null(result, "expected failure for value substitution, but success was returned");
147   cr_assert_not_null(error, "expected a non-NULL error object for failure");
148   cr_assert_str_eq(error->message, expected_error, "error message mismatch");
149   g_free(input_dup);
150 }
151 
Test(cfg_lexer_subst,test_double_backtick_replaced_with_a_single_one)152 Test(cfg_lexer_subst, test_double_backtick_replaced_with_a_single_one)
153 {
154   CfgLexerSubst *subst = construct_object();
155   assert_invoke_result(subst, "``", "`");
156   cfg_lexer_subst_free(subst);
157 }
158 
Test(cfg_lexer_subst,test_single_backtick_causes_an_error)159 Test(cfg_lexer_subst, test_single_backtick_causes_an_error)
160 {
161   CfgLexerSubst *subst = construct_object();
162   assert_invoke_failure(subst, "foo ` bar", "missing closing backtick (`) character");
163   cfg_lexer_subst_free(subst);
164 }
165 
Test(cfg_lexer_subst,test_backtick_after_quoted_character_succeeds)166 Test(cfg_lexer_subst, test_backtick_after_quoted_character_succeeds)
167 {
168   CfgLexerSubst *subst = construct_object();
169   assert_invoke_result(subst, "foo \"string \\n`arg`\" bar", "foo \"string \\narg_value\" bar");
170   cfg_lexer_subst_free(subst);
171 }
172 
Test(cfg_lexer_subst,test_backtick_as_a_quoted_character_in_a_string_results_in_failure)173 Test(cfg_lexer_subst, test_backtick_as_a_quoted_character_in_a_string_results_in_failure)
174 {
175   CfgLexerSubst *subst = construct_object();
176 
177   assert_invoke_failure(subst, "foo \"string \\`arg`\" bar",
178                         "cannot subsitute backticked values right after a string quote character");
179   cfg_lexer_subst_free(subst);
180 }
181 
Test(cfg_lexer_subst,test_value_in_normal_text_replaced_with_its_literal_value)182 Test(cfg_lexer_subst, test_value_in_normal_text_replaced_with_its_literal_value)
183 {
184   CfgLexerSubst *subst = construct_object();
185   assert_invoke_result(subst, "foo `arg` bar", "foo arg_value bar");
186   assert_invoke_result(subst, "foo `simple_string` bar", "foo \"simple_string_value\" bar");
187   assert_invoke_result(subst, "foo `simple_qstring` bar", "foo 'simple_qstring_value' bar");
188   assert_invoke_result(subst, "foo `escaped_string` bar", "foo \"escaped_string\\\"\\r\\n\" bar");
189   cfg_lexer_subst_free(subst);
190 }
191 
Test(cfg_lexer_subst,test_values_are_resolution_order_args_defaults_globals_env)192 Test(cfg_lexer_subst, test_values_are_resolution_order_args_defaults_globals_env)
193 {
194   CfgLexerSubst *subst = construct_object();
195 
196   setenv("env", "env_for_env", TRUE);
197   assert_invoke_result(subst, "foo `arg` bar", "foo arg_value bar");
198   assert_invoke_result(subst, "foo `def` bar", "foo default_for_def bar");
199   assert_invoke_result(subst, "foo `globl` bar", "foo global_for_globl bar");
200   assert_invoke_result(subst, "foo `env` bar", "foo env_for_env bar");
201   cfg_lexer_subst_free(subst);
202 }
203 
Test(cfg_lexer_subst,test_values_are_inserted_within_strings)204 Test(cfg_lexer_subst, test_values_are_inserted_within_strings)
205 {
206   CfgLexerSubst *subst = construct_object();
207 
208   assert_invoke_result(subst, "foo \"`arg`\" bar", "foo \"arg_value\" bar");
209   assert_invoke_result(subst, "foo '`arg`' bar", "foo 'arg_value' bar");
210   cfg_lexer_subst_free(subst);
211 }
212 
Test(cfg_lexer_subst,test_string_literals_are_inserted_into_strings_without_quotes)213 Test(cfg_lexer_subst, test_string_literals_are_inserted_into_strings_without_quotes)
214 {
215   const gchar *additional_values[] =
216   {
217     "simple_string_with_whitespace", "  \"string_with_whitespace\"   ",
218     NULL, NULL
219   };
220   CfgLexerSubst *subst = construct_object_with_values(additional_values);
221 
222   /* double quotes */
223   assert_invoke_result(subst, "foo \"x `simple_string` y\" bar", "foo \"x simple_string_value y\" bar");
224   /* apostrophes */
225   assert_invoke_result(subst, "foo 'x `simple_string` y' bar", "foo 'x simple_string_value y' bar");
226   assert_invoke_result(subst, "foo \"x `simple_string_with_whitespace` y\" bar",
227                        "foo \"x string_with_whitespace y\" bar");
228   cfg_lexer_subst_free(subst);
229 }
230 
Test(cfg_lexer_subst,test_incorrect_strings_and_multiple_tokens_are_inserted_verbatim)231 Test(cfg_lexer_subst, test_incorrect_strings_and_multiple_tokens_are_inserted_verbatim)
232 {
233   const gchar *additional_values[] =
234   {
235     "half_string", "\"halfstring",
236     "tokens_that_start_with_string", "\"str\", token",
237     "tokens_enclosed_in_strings", "\"str1\", token, \"str2\"",
238     NULL, NULL
239   };
240   CfgLexerSubst *subst = construct_object_with_values(additional_values);
241 
242   assert_invoke_result(subst, "foo \"x `simple_string` y\" bar", "foo \"x simple_string_value y\" bar");
243   assert_invoke_result(subst, "foo \"x `half_string` y\" bar", "foo \"x \"halfstring y\" bar");
244   assert_invoke_result(subst, "foo \"x `tokens_that_start_with_string` y\" bar", "foo \"x \"str\", token y\" bar");
245   assert_invoke_result(subst, "foo \"x `tokens_enclosed_in_strings` y\" bar",
246                        "foo \"x \"str1\", token, \"str2\" y\" bar");
247   cfg_lexer_subst_free(subst);
248 }
249 
Test(cfg_lexer_subst,test_strings_with_special_chars_are_properly_encoded_in_strings)250 Test(cfg_lexer_subst, test_strings_with_special_chars_are_properly_encoded_in_strings)
251 {
252   const gchar *additional_values[] =
253   {
254     "string_with_characters_that_need_quoting", "\"quote: \\\", newline: \\r\\n, backslash: \\\\\"",
255     NULL, NULL
256   };
257   CfgLexerSubst *subst = construct_object_with_values(additional_values);
258 
259   assert_invoke_result(subst, "foo \"x `string_with_characters_that_need_quoting` y\" bar",
260                        "foo \"x quote: \\\", newline: \\r\\n, backslash: \\\\ y\" bar");
261   cfg_lexer_subst_free(subst);
262 }
263 
Test(cfg_lexer_subst,test_strings_with_embedded_apostrophe_cause_an_error_when_encoding_in_qstring)264 Test(cfg_lexer_subst, test_strings_with_embedded_apostrophe_cause_an_error_when_encoding_in_qstring)
265 {
266   const gchar *additional_values[] =
267   {
268     "string_with_apostrophe", "\"'foo'\"",
269     NULL, NULL
270   };
271   CfgLexerSubst *subst = construct_object_with_values(additional_values);
272 
273   assert_invoke_result(subst, "foo \"x `string_with_apostrophe` y\" bar", "foo \"x 'foo' y\" bar");
274   assert_invoke_failure(subst, "foo 'x `string_with_apostrophe` y' bar",
275                         "cannot represent apostrophes within apostroph-enclosed string");
276   cfg_lexer_subst_free(subst);
277 }
278 
Test(cfg_lexer_subst,test_tracking_string_state)279 Test(cfg_lexer_subst, test_tracking_string_state)
280 {
281   const gchar *additional_values[] =
282   {
283     "quoted_escaped_newline", "\"\\n\"",
284     NULL, NULL
285   };
286 
287   CfgLexerSubst *subst = construct_object_with_values(additional_values);
288 
289   assert_invoke_result(subst, "\"hello\\n\" `quoted_escaped_newline`", "\"hello\\n\" \"\\n\"");
290   assert_invoke_result(subst, "\"hello\\n\\n\" `quoted_escaped_newline`", "\"hello\\n\\n\" \"\\n\"");
291   assert_invoke_result(subst, "\"hello\\n\\n \" `quoted_escaped_newline`", "\"hello\\n\\n \" \"\\n\"");
292   cfg_lexer_subst_free(subst);
293 }
294 
295 TestSuite(cfg_lexer_subst, .init = app_startup, .fini = app_shutdown);
296