1 /*
2  * Copyright (c) 2010-2013 Balabit
3  * Copyright (c) 2010-2013 Balázs Scheidler
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * As an additional exemption you are allowed to compile & link against the
20  * OpenSSL libraries as published by the OpenSSL project. See the file
21  * COPYING for details.
22  *
23  */
24 
25 #include "cfg-lexer-subst.h"
26 #include "cfg-args.h"
27 #include "cfg-grammar.h"
28 
29 #include <string.h>
30 #include <stdlib.h>
31 
32 typedef enum _CfgLexerStringTrackState
33 {
34   CLS_NOT_STRING,
35   CLS_WITHIN_STRING,
36   CLS_WITHIN_STRING_QUOTE,
37   CLS_WITHIN_STRING_QUOTED_CHARACTER,
38   CLS_WITHIN_QSTRING,
39 } CfgLexerStringTrackState;
40 
41 /* substitute backtick references into lexer input */
42 struct _CfgLexerSubst
43 {
44   CfgArgs *globals;
45   CfgArgs *defs;
46   CfgArgs *args;
47   CfgLexerStringTrackState string_state;
48   GString *result_buffer;
49 };
50 
51 static const gchar *
_lookup_value(CfgLexerSubst * self,const gchar * name)52 _lookup_value(CfgLexerSubst *self, const gchar *name)
53 {
54   const gchar *arg;
55 
56   if (self->args && (arg = cfg_args_get(self->args, name)))
57     ;
58   else if (self->defs && (arg = cfg_args_get(self->defs, name)))
59     ;
60   else if (self->globals && (arg = cfg_args_get(self->globals, name)))
61     ;
62   else if ((arg = g_getenv(name)))
63     ;
64   else
65     arg = NULL;
66 
67   return arg;
68 }
69 
70 static CfgLexerStringTrackState
_track_string_state(CfgLexerSubst * self,CfgLexerStringTrackState last_state,const gchar * p)71 _track_string_state(CfgLexerSubst *self, CfgLexerStringTrackState last_state, const gchar *p)
72 {
73   switch (last_state)
74     {
75     case CLS_NOT_STRING:
76       if (*p == '"')
77         return CLS_WITHIN_STRING;
78       else if (*p == '\'')
79         return CLS_WITHIN_QSTRING;
80       return CLS_NOT_STRING;
81     case CLS_WITHIN_STRING:
82     case CLS_WITHIN_STRING_QUOTED_CHARACTER:
83       if (*p == '\\')
84         return CLS_WITHIN_STRING_QUOTE;
85       else if (*p == '"')
86         return CLS_NOT_STRING;
87       return CLS_WITHIN_STRING;
88     case CLS_WITHIN_STRING_QUOTE:
89       return CLS_WITHIN_STRING_QUOTED_CHARACTER;
90     case CLS_WITHIN_QSTRING:
91       if (*p == '\'')
92         return CLS_NOT_STRING;
93       return CLS_WITHIN_QSTRING;
94     default:
95       g_assert_not_reached();
96       break;
97     }
98   g_assert_not_reached();
99 }
100 
101 static gchar *
_extract_string_literal(const gchar * value)102 _extract_string_literal(const gchar *value)
103 {
104   CfgLexer *lexer;
105   gint token, look_ahead_token;
106   CFG_STYPE yylval, look_ahead_yylval;
107   CFG_LTYPE yylloc, look_ahead_yylloc;
108   gchar *result = NULL;
109 
110   lexer = cfg_lexer_new_buffer(configuration, value, strlen(value));
111   token = cfg_lexer_lex(lexer, &yylval, &yylloc);
112   if (token == LL_STRING)
113     {
114       look_ahead_token = cfg_lexer_lex(lexer, &look_ahead_yylval, &look_ahead_yylloc);
115       if (!look_ahead_token)
116         result = g_strdup(yylval.cptr);
117 
118       cfg_lexer_free_token(&look_ahead_yylval);
119     }
120   cfg_lexer_free_token(&yylval);
121 
122   cfg_lexer_free(lexer);
123   return result;
124 }
125 
126 static gboolean
_encode_as_string(CfgLexerSubst * self,const gchar * value,GError ** error)127 _encode_as_string(CfgLexerSubst *self, const gchar *value, GError **error)
128 {
129   gint i;
130 
131   g_return_val_if_fail(error == NULL || (*error) == NULL, FALSE);
132   for (i = 0; value[i]; i++)
133     {
134       gchar p = value[i];
135 
136       if (p == '"')
137         g_string_append(self->result_buffer, "\\\"");
138       else if (p == '\n')
139         g_string_append(self->result_buffer, "\\n");
140       else if (p == '\r')
141         g_string_append(self->result_buffer, "\\r");
142       else if (p == '\\')
143         g_string_append(self->result_buffer, "\\\\");
144       else
145         g_string_append_c(self->result_buffer, p);
146     }
147   return TRUE;
148 }
149 
150 static gboolean
_encode_as_qstring(CfgLexerSubst * self,const gchar * value,GError ** error)151 _encode_as_qstring(CfgLexerSubst *self, const gchar *value, GError **error)
152 {
153   gint i;
154 
155   g_return_val_if_fail(error == NULL || (*error) == NULL, FALSE);
156   for (i = 0; value[i]; i++)
157     {
158       gchar p = value[i];
159 
160       if (p == '\'')
161         {
162           g_set_error(error, CFG_LEXER_ERROR, CFG_LEXER_CANNOT_REPRESENT_APOSTROPHES_IN_QSTRINGS,
163                       "cannot represent apostrophes within apostroph-enclosed string");
164           return FALSE;
165         }
166       g_string_append_c(self->result_buffer, p);
167     }
168   return TRUE;
169 }
170 
171 static gboolean
_append_value(CfgLexerSubst * self,const gchar * value,GError ** error)172 _append_value(CfgLexerSubst *self, const gchar *value, GError **error)
173 {
174   g_return_val_if_fail(error == NULL || (*error) == NULL, FALSE);
175   gboolean result = TRUE;
176 
177   if (self->string_state == CLS_NOT_STRING)
178     {
179       g_string_append(self->result_buffer, value);
180     }
181   else
182     {
183       gchar *string_literal;
184 
185       string_literal = _extract_string_literal(value);
186       if (string_literal)
187         {
188           switch (self->string_state)
189             {
190             case CLS_WITHIN_STRING:
191               result = _encode_as_string(self, string_literal, error);
192               break;
193             case CLS_WITHIN_QSTRING:
194               result = _encode_as_qstring(self, string_literal, error);
195               break;
196             default:
197               g_assert_not_reached();
198               break;
199             }
200           g_free(string_literal);
201         }
202       else
203         g_string_append(self->result_buffer, value);
204     }
205   return result;
206 }
207 
208 gchar *
cfg_lexer_subst_invoke(CfgLexerSubst * self,const gchar * input,gssize input_len,gsize * output_length,GError ** error)209 cfg_lexer_subst_invoke(CfgLexerSubst *self, const gchar *input, gssize input_len, gsize *output_length, GError **error)
210 {
211   gboolean backtick = FALSE;
212   const gchar *p, *ref_start = input;
213   GString *result;
214 
215   g_return_val_if_fail(error == NULL || (*error) == NULL, FALSE);
216 
217   if (input_len < 0)
218     input_len = strlen(input);
219 
220   result = g_string_sized_new(32);
221   self->result_buffer = result;
222   p = input;
223   while (p - input < input_len)
224     {
225       self->string_state = _track_string_state(self, self->string_state, p);
226 
227       if (!backtick && (*p) == '`')
228         {
229           if (self->string_state == CLS_WITHIN_STRING_QUOTED_CHARACTER)
230             {
231               g_set_error(error, CFG_LEXER_ERROR, CFG_LEXER_BACKTICKS_CANT_BE_SUBSTITUTED_AFTER_BACKSLASH,
232                           "cannot subsitute backticked values right after a string quote character");
233               goto error;
234             }
235           /* start of reference */
236           backtick = TRUE;
237           ref_start = p + 1;
238         }
239       else if (backtick && (*p) == '`')
240         {
241           /* end of reference */
242           backtick = FALSE;
243 
244           if (ref_start == p)
245             {
246               /* empty ref, just include a ` character */
247               g_string_append_c(result, '`');
248             }
249           else
250             {
251               const gchar *value;
252               gchar *name;
253 
254               name = g_strndup(ref_start, p - ref_start);
255               value = _lookup_value(self, name);
256               g_free(name);
257               if (!_append_value(self, value ? : "", error))
258                 goto error;
259             }
260         }
261       else if (!backtick)
262         g_string_append_c(result, *p);
263 
264       p++;
265     }
266   self->result_buffer = NULL;
267 
268   if (backtick)
269     {
270       g_set_error(error, CFG_LEXER_ERROR, CFG_LEXER_MISSING_BACKTICK_PAIR, "missing closing backtick (`) character");
271       goto error;
272     }
273 
274   *output_length = result->len;
275   return g_string_free(result, FALSE);
276 error:
277   g_string_free(result, TRUE);
278   return NULL;
279 
280 }
281 
282 CfgLexerSubst *
cfg_lexer_subst_new(CfgArgs * globals,CfgArgs * defs,CfgArgs * args)283 cfg_lexer_subst_new(CfgArgs *globals, CfgArgs *defs, CfgArgs *args)
284 {
285   CfgLexerSubst *self = g_new0(CfgLexerSubst, 1);
286 
287   self->globals = globals;
288   self->defs = defs;
289   self->args = args;
290   return self;
291 }
292 
293 void
cfg_lexer_subst_free(CfgLexerSubst * self)294 cfg_lexer_subst_free(CfgLexerSubst *self)
295 {
296   cfg_args_unref(self->globals);
297   cfg_args_unref(self->defs);
298   cfg_args_unref(self->args);
299   g_free(self);
300 }
301 
302 gchar *
cfg_lexer_subst_args_in_input(CfgArgs * globals,CfgArgs * defs,CfgArgs * args,const gchar * input,gssize input_length,gsize * output_length,GError ** error)303 cfg_lexer_subst_args_in_input(CfgArgs *globals, CfgArgs *defs, CfgArgs *args, const gchar *input, gssize input_length,
304                               gsize *output_length, GError **error)
305 {
306   CfgLexerSubst *subst = cfg_lexer_subst_new(cfg_args_ref(globals), cfg_args_ref(defs), cfg_args_ref(args));
307   gchar *result;
308 
309   result = cfg_lexer_subst_invoke(subst, input, input_length, output_length, error);
310   cfg_lexer_subst_free(subst);
311   return result;
312 }
313