1 /*
2  * Copyright (c) 2011-2015 Balabit
3  * Copyright (c) 2011-2014 Gergely Nagy <algernon@balabit.hu>
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 #include "value-pairs/cmdline.h"
25 #include "value-pairs/internals.h"
26 
27 #include <string.h>
28 #include <stdlib.h>
29 #include <ctype.h>
30 
31 /*******************************************************************************
32  * Command line parser
33  *******************************************************************************/
34 
35 static void
vp_cmdline_parse_rekey_finish(gpointer data)36 vp_cmdline_parse_rekey_finish (gpointer data)
37 {
38   gpointer *args = (gpointer *) data;
39   ValuePairs *vp = (ValuePairs *) args[1];
40   ValuePairsTransformSet *vpts = (ValuePairsTransformSet *) args[2];
41 
42   if (vpts)
43     value_pairs_add_transforms (vp, args[2]);
44   args[2] = NULL;
45   g_free(args[3]);
46   args[3] = NULL;
47 }
48 
49 static void
vp_cmdline_start_key(gpointer data,const gchar * key)50 vp_cmdline_start_key(gpointer data, const gchar *key)
51 {
52   gpointer *args = (gpointer *) data;
53 
54   vp_cmdline_parse_rekey_finish (data);
55   args[3] = g_strdup(key);
56 }
57 
58 static ValuePairsTransformSet *
vp_cmdline_rekey_verify(gchar * key,ValuePairsTransformSet * vpts,gpointer data)59 vp_cmdline_rekey_verify (gchar *key, ValuePairsTransformSet *vpts,
60                          gpointer data)
61 {
62   gpointer *args = (gpointer *)data;
63 
64   if (!vpts)
65     {
66       if (!key)
67         return NULL;
68       vpts = value_pairs_transform_set_new (key);
69       vp_cmdline_parse_rekey_finish (data);
70       args[2] = vpts;
71       return vpts;
72     }
73   return vpts;
74 }
75 
76 /* parse a value-pair specification from a command-line like environment */
77 static gboolean
vp_cmdline_parse_scope(const gchar * option_name,const gchar * value,gpointer data,GError ** error)78 vp_cmdline_parse_scope(const gchar *option_name, const gchar *value,
79                        gpointer data, GError **error)
80 {
81   gpointer *args = (gpointer *) data;
82   ValuePairs *vp = (ValuePairs *) args[1];
83   gchar **scopes;
84   gint i;
85 
86   vp_cmdline_parse_rekey_finish (data);
87 
88   scopes = g_strsplit (value, ",", -1);
89   for (i = 0; scopes[i] != NULL; i++)
90     {
91       if (!value_pairs_add_scope (vp, scopes[i]))
92         {
93           g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
94                        "Error parsing value-pairs: unknown scope %s", scopes[i]);
95           g_strfreev (scopes);
96           return FALSE;
97         }
98     }
99   g_strfreev (scopes);
100 
101   return TRUE;
102 }
103 
104 static gboolean
vp_cmdline_parse_exclude(const gchar * option_name,const gchar * value,gpointer data,GError ** error)105 vp_cmdline_parse_exclude(const gchar *option_name, const gchar *value,
106                          gpointer data, GError **error)
107 {
108   gpointer *args = (gpointer *) data;
109   ValuePairs *vp = (ValuePairs *) args[1];
110   gchar **excludes;
111   gint i;
112 
113   vp_cmdline_parse_rekey_finish (data);
114 
115   excludes = g_strsplit(value, ",", -1);
116   for (i = 0; excludes[i] != NULL; i++)
117     value_pairs_add_glob_pattern(vp, excludes[i], FALSE);
118   g_strfreev(excludes);
119 
120   return TRUE;
121 }
122 
123 static gboolean
vp_cmdline_parse_key(const gchar * option_name,const gchar * value,gpointer data,GError ** error)124 vp_cmdline_parse_key(const gchar *option_name, const gchar *value,
125                      gpointer data, GError **error)
126 {
127   gpointer *args = (gpointer *) data;
128   ValuePairs *vp = (ValuePairs *) args[1];
129   gchar **keys;
130   gint i;
131 
132   vp_cmdline_start_key(data, value);
133 
134   keys = g_strsplit(value, ",", -1);
135   for (i = 0; keys[i] != NULL; i++)
136     value_pairs_add_glob_pattern(vp, keys[i], TRUE);
137   g_strfreev(keys);
138 
139   return TRUE;
140 }
141 
142 static gboolean
vp_cmdline_parse_rekey(const gchar * option_name,const gchar * value,gpointer data,GError ** error)143 vp_cmdline_parse_rekey(const gchar *option_name, const gchar *value,
144                        gpointer data, GError **error)
145 {
146   vp_cmdline_start_key(data, value);
147   return TRUE;
148 }
149 
150 static void
value_pairs_parse_type(gchar * spec,gchar ** value,gchar ** type)151 value_pairs_parse_type(gchar *spec, gchar **value, gchar **type)
152 {
153   char *sp, *ep;
154 
155   *type = NULL;
156   sp = spec;
157 
158   while (g_ascii_isalnum(*sp) || (*sp) == '_')
159     sp++;
160 
161   while (*sp == ' ' || *sp == '\t')
162     sp++;
163 
164   if (*sp != '(' ||
165       !((g_ascii_toupper(spec[0]) >= 'A' &&
166          g_ascii_toupper(spec[0]) <= 'Z') ||
167         spec[0] == '_'))
168     {
169       *value = spec;
170       return;
171     }
172 
173   ep = strrchr(sp, ')');
174   if (ep == NULL || ep[1] != '\0')
175     {
176       *value = spec;
177       return;
178     }
179 
180   *value = sp + 1;
181   *type = spec;
182   sp[0] = '\0';
183   ep[0] = '\0';
184 }
185 
186 static gboolean
vp_cmdline_parse_pair(const gchar * option_name,const gchar * value,gpointer data,GError ** error)187 vp_cmdline_parse_pair (const gchar *option_name, const gchar *value,
188                        gpointer data, GError **error)
189 {
190   gpointer *args = (gpointer *) data;
191   ValuePairs *vp = (ValuePairs *) args[1];
192   GlobalConfig *cfg = (GlobalConfig *) args[0];
193   gchar **kv, *v, *t;
194   gboolean res = FALSE;
195   LogTemplate *template;
196 
197   vp_cmdline_parse_rekey_finish (data);
198 
199   if (strchr(value, '=') == NULL)
200     {
201       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
202                    "Error parsing value-pairs: expected an equal sign in key=value pair");
203       return FALSE;
204     }
205 
206   kv = g_strsplit(value, "=", 2);
207   value_pairs_parse_type(kv[1], &v, &t);
208 
209   template = log_template_new(cfg, NULL);
210   if (!log_template_compile(template, v, error))
211     goto error;
212   if (!log_template_set_type_hint(template, t, error))
213     goto error;
214 
215   value_pairs_add_pair(vp, kv[0], template);
216 
217   res = TRUE;
218 error:
219   log_template_unref(template);
220   g_strfreev(kv);
221 
222   return res;
223 }
224 
225 static gboolean
vp_cmdline_parse_pair_or_key(const gchar * option_name,const gchar * value,gpointer data,GError ** error)226 vp_cmdline_parse_pair_or_key (const gchar *option_name, const gchar *value,
227                               gpointer data, GError **error)
228 {
229   if (strchr(value, '=') == NULL)
230     return vp_cmdline_parse_key(option_name, value, data, error);
231   else
232     return vp_cmdline_parse_pair(option_name, value, data, error);
233 }
234 
235 static gboolean
vp_cmdline_parse_subkeys(const gchar * option_name,const gchar * value,gpointer data,GError ** error)236 vp_cmdline_parse_subkeys(const gchar *option_name, const gchar *value,
237                          gpointer data, GError **error)
238 {
239   gpointer *args = (gpointer *) data;
240   ValuePairs *vp = (ValuePairs *) args[1];
241   ValuePairsTransformSet *vpts = (ValuePairsTransformSet *) args[2];
242 
243   if (!value[0])
244     {
245       g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
246                   "Error parsing value-pairs: --subkeys requires a non-empty argument");
247       return FALSE;
248     }
249 
250   GString *prefix = g_string_new(value);
251   g_string_append_c(prefix, '*');
252   value_pairs_add_glob_pattern(vp, prefix->str, TRUE);
253 
254   vp_cmdline_start_key(data, prefix->str);
255 
256   vpts = vp_cmdline_rekey_verify(prefix->str, vpts, data);
257   if (!vpts)
258     {
259       g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
260                   "Error parsing value-pairs: --subkeys failed to create key");
261       g_string_free(prefix, TRUE);
262       return FALSE;
263     }
264 
265   value_pairs_transform_set_add_func
266   (vpts, value_pairs_new_transform_replace_prefix(value, ""));
267 
268   g_string_free(prefix, TRUE);
269   return TRUE;
270 }
271 
272 static gboolean
vp_cmdline_parse_rekey_replace_prefix(const gchar * option_name,const gchar * value,gpointer data,GError ** error)273 vp_cmdline_parse_rekey_replace_prefix (const gchar *option_name, const gchar *value,
274                                        gpointer data, GError **error)
275 {
276   gpointer *args = (gpointer *) data;
277   ValuePairsTransformSet *vpts = (ValuePairsTransformSet *) args[2];
278   gchar *key = (gchar *) args[3];
279   gchar **kv;
280 
281   vpts = vp_cmdline_rekey_verify (key, vpts, data);
282   if (!vpts)
283     {
284       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
285                    "Error parsing value-pairs: --replace-prefix used without --key or --rekey");
286       return FALSE;
287     }
288 
289   if (!g_strstr_len (value, strlen (value), "="))
290     {
291       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
292                    "Error parsing value-pairs: rekey replace-prefix construct should be in the format string=replacement");
293       return FALSE;
294     }
295 
296   kv = g_strsplit(value, "=", 2);
297   value_pairs_transform_set_add_func
298   (vpts, value_pairs_new_transform_replace_prefix (kv[0], kv[1]));
299 
300   g_free (kv[0]);
301   g_free (kv[1]);
302   g_free (kv);
303 
304   return TRUE;
305 }
306 
307 static gboolean
vp_cmdline_parse_rekey_add_prefix(const gchar * option_name,const gchar * value,gpointer data,GError ** error)308 vp_cmdline_parse_rekey_add_prefix (const gchar *option_name, const gchar *value,
309                                    gpointer data, GError **error)
310 {
311   gpointer *args = (gpointer *) data;
312   ValuePairsTransformSet *vpts = (ValuePairsTransformSet *) args[2];
313   gchar *key = (gchar *) args[3];
314 
315   vpts = vp_cmdline_rekey_verify (key, vpts, data);
316   if (!vpts)
317     {
318       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
319                    "Error parsing value-pairs: --add-prefix used without --key or --rekey");
320       return FALSE;
321     }
322 
323   value_pairs_transform_set_add_func
324   (vpts, value_pairs_new_transform_add_prefix (value));
325   return TRUE;
326 }
327 
328 static gboolean
vp_cmdline_parse_rekey_shift(const gchar * option_name,const gchar * value,gpointer data,GError ** error)329 vp_cmdline_parse_rekey_shift (const gchar *option_name, const gchar *value,
330                               gpointer data, GError **error)
331 {
332   gpointer *args = (gpointer *) data;
333   ValuePairsTransformSet *vpts = (ValuePairsTransformSet *) args[2];
334   gchar *key = (gchar *) args[3];
335   gchar *end = NULL;
336   gint number_to_shift = strtol(value, &end, 0);
337 
338   if (number_to_shift <= 0 || *end != 0)
339     {
340       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
341                    "Error parsing value-pairs, argument to --shift is not numeric or not a positive number");
342       return FALSE;
343     }
344 
345   vpts = vp_cmdline_rekey_verify (key, vpts, data);
346   if (!vpts)
347     {
348       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
349                    "Error parsing value-pairs: --shift used without --key or --rekey");
350       return FALSE;
351     }
352 
353   value_pairs_transform_set_add_func(vpts,
354                                      value_pairs_new_transform_shift (number_to_shift));
355   return TRUE;
356 }
357 
358 static gboolean
vp_cmdline_parse_rekey_shift_levels(const gchar * option_name,const gchar * value,gpointer data,GError ** error)359 vp_cmdline_parse_rekey_shift_levels (const gchar *option_name, const gchar *value,
360                                      gpointer data, GError **error)
361 {
362   gpointer *args = (gpointer *) data;
363   ValuePairsTransformSet *vpts = (ValuePairsTransformSet *) args[2];
364   gchar *key = (gchar *) args[3];
365   gchar *end = NULL;
366   gint number_to_shift = strtol(value, &end, 0);
367 
368   if (number_to_shift <= 0 || *end != 0)
369     {
370       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
371                    "Error parsing value-pairs, argument to --shift-levels is not numeric or not a positive number");
372       return FALSE;
373     }
374 
375   vpts = vp_cmdline_rekey_verify (key, vpts, data);
376   if (!vpts)
377     {
378       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
379                    "Error parsing value-pairs: --shift-levels used without --key or --rekey");
380       return FALSE;
381     }
382 
383   value_pairs_transform_set_add_func(vpts,
384                                      value_pairs_new_transform_shift_levels (number_to_shift));
385   return TRUE;
386 }
387 
388 ValuePairs *
value_pairs_new_from_cmdline(GlobalConfig * cfg,gint * argc,gchar *** argv,gboolean ignore_unknown_options,GError ** error)389 value_pairs_new_from_cmdline (GlobalConfig *cfg,
390                               gint *argc, gchar ***argv,
391                               gboolean ignore_unknown_options,
392                               GError **error)
393 {
394   ValuePairs *vp;
395   GOptionContext *ctx;
396 
397   vp = value_pairs_new();
398 
399   GOptionEntry vp_options[] =
400   {
401     {
402       "scope", 's', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_scope,
403       NULL, NULL
404     },
405     {
406       "exclude", 'x', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_exclude,
407       NULL, NULL
408     },
409     {
410       "key", 'k', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_key,
411       NULL, NULL
412     },
413     {
414       "rekey", 'r', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_rekey,
415       NULL, NULL
416     },
417     {
418       "pair", 'p', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_pair,
419       NULL, NULL
420     },
421     {
422       "shift", 'S', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_rekey_shift,
423       NULL, NULL
424     },
425     {
426       "shift-levels", 0, 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_rekey_shift_levels,
427       NULL, NULL
428     },
429     {
430       "add-prefix", 'A', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_rekey_add_prefix,
431       NULL, NULL
432     },
433     {
434       "replace-prefix", 'R', 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_rekey_replace_prefix,
435       NULL, NULL
436     },
437     {
438       "replace", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK,
439       vp_cmdline_parse_rekey_replace_prefix, NULL, NULL
440     },
441     {
442       "subkeys", 0, 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_subkeys,
443       NULL, NULL
444     },
445     {
446       "omit-empty-values", 0, 0, G_OPTION_ARG_NONE, &vp->omit_empty_values,
447       NULL, NULL
448     },
449     {
450       G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_CALLBACK, vp_cmdline_parse_pair_or_key,
451       NULL, NULL
452     },
453     { NULL }
454   };
455   GOptionGroup *og;
456   gpointer user_data_args[4];
457   gboolean success;
458 
459   user_data_args[0] = cfg;
460   user_data_args[1] = vp;
461   user_data_args[2] = NULL;
462   user_data_args[3] = NULL;
463 
464   ctx = g_option_context_new("value-pairs");
465   og = g_option_group_new(NULL, NULL, NULL, user_data_args, NULL);
466   g_option_group_add_entries(og, vp_options);
467   g_option_context_set_main_group(ctx, og);
468   g_option_context_set_ignore_unknown_options(ctx, ignore_unknown_options);
469 
470   success = g_option_context_parse(ctx, argc, argv, error);
471   vp_cmdline_parse_rekey_finish(user_data_args);
472   g_option_context_free(ctx);
473 
474   if (!success)
475     {
476       value_pairs_unref(vp);
477       vp = NULL;
478     }
479 
480   return vp;
481 }
482