1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program 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
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 #include <class.h>
25 
26 #include <map.h>
27 #include <alloc.h>
28 #include <string_lib.h> /* String*() */
29 #include <regex.h>      /* CompileRegex,StringMatchFullWithPrecompiledRegex */
30 #include <files_names.h>
31 
32 
33 static void ClassDestroy(Class *cls);                /* forward declaration */
34 
ClassDestroy_untyped(void * p)35 static void ClassDestroy_untyped(void *p)
36 {
37     ClassDestroy(p);
38 }
39 
40 
41 /**
42    Define ClassMap.
43    Key: a string which is always the fully qualified class name,
44         for example "default:127_0_0_1"
45 */
46 
47 TYPED_MAP_DECLARE(Class, char *, Class *)
48 
49 TYPED_MAP_DEFINE(Class, char *, Class *,
50                  StringHash_untyped,
51                  StringEqual_untyped,
52                  free,
53                  ClassDestroy_untyped)
54 
55 struct ClassTable_
56 {
57     ClassMap *classes;
58 };
59 
60 struct ClassTableIterator_
61 {
62     MapIterator iter;
63     char *ns;
64     bool is_hard;
65     bool is_soft;
66 };
67 
68 
ClassInit(Class * cls,const char * ns,const char * name,bool is_soft,ContextScope scope,StringSet * tags,const char * comment)69 static void ClassInit(Class *cls,
70                       const char *ns, const char *name,
71                       bool is_soft, ContextScope scope, StringSet *tags,
72                       const char *comment)
73 {
74     assert(cls != NULL);
75 
76     if (ns == NULL || strcmp(ns, "default") == 0)
77     {
78         cls->ns = NULL;
79     }
80     else
81     {
82         cls->ns = xstrdup(ns);
83     }
84 
85     cls->name = xstrdup(name);
86     CanonifyNameInPlace(cls->name);
87 
88     cls->is_soft = is_soft;
89     cls->scope = scope;
90     cls->tags = (tags ? tags : StringSetNew());
91     if (!is_soft && !StringSetContains(cls->tags, "hardclass"))
92     {
93         StringSetAdd(cls->tags, xstrdup("hardclass"));
94     }
95     cls->comment = SafeStringDuplicate(comment);
96 }
97 
ClassDestroySoft(Class * cls)98 static void ClassDestroySoft(Class *cls)
99 {
100     if (cls != NULL)
101     {
102         free(cls->ns);
103         free(cls->name);
104         StringSetDestroy(cls->tags);
105         free(cls->comment);
106     }
107 }
108 
ClassDestroy(Class * cls)109 static void ClassDestroy(Class *cls)
110 {
111     if (cls)
112     {
113         ClassDestroySoft(cls);
114         free(cls);
115     }
116 }
117 
ClassTableNew(void)118 ClassTable *ClassTableNew(void)
119 {
120     ClassTable *table = xmalloc(sizeof(*table));
121 
122     table->classes = ClassMapNew();
123 
124     return table;
125 }
126 
ClassTableDestroy(ClassTable * table)127 void ClassTableDestroy(ClassTable *table)
128 {
129     if (table)
130     {
131         ClassMapDestroy(table->classes);
132         free(table);
133     }
134 }
135 
ClassTablePut(ClassTable * table,const char * ns,const char * name,bool is_soft,ContextScope scope,StringSet * tags,const char * comment)136 bool ClassTablePut(ClassTable *table,
137                    const char *ns, const char *name,
138                    bool is_soft, ContextScope scope, StringSet *tags, const char *comment)
139 {
140     assert(name);
141     assert(is_soft || (!ns || strcmp("default", ns) == 0)); // hard classes should have default namespace
142     assert(is_soft || scope == CONTEXT_SCOPE_NAMESPACE); // hard classes cannot be local
143 
144     if (ns == NULL)
145     {
146         ns = "default";
147     }
148 
149     Class *cls = xmalloc(sizeof(*cls));
150     ClassInit(cls, ns, name, is_soft, scope, tags, comment);
151 
152     /* (cls->name != name) because canonification has happened. */
153     char *fullname = StringConcatenate(3, ns, ":", cls->name);
154 
155     Log(LOG_LEVEL_DEBUG, "Setting %sclass: %s",
156         is_soft ? "" : "hard ",
157         fullname);
158 
159     return ClassMapInsert(table->classes, fullname, cls);
160 }
161 
ClassTableGet(const ClassTable * table,const char * ns,const char * name)162 Class *ClassTableGet(const ClassTable *table, const char *ns, const char *name)
163 {
164     if (ns == NULL)
165     {
166         ns = "default";
167     }
168 
169     char fullname[ strlen(ns) + 1 + strlen(name) + 1 ];
170     xsnprintf(fullname, sizeof(fullname), "%s:%s", ns, name);
171 
172     return ClassMapGet(table->classes, fullname);
173 }
174 
ClassTableMatch(const ClassTable * table,const char * regex)175 Class *ClassTableMatch(const ClassTable *table, const char *regex)
176 {
177     ClassTableIterator *it = ClassTableIteratorNew(table, NULL, true, true);
178     Class *cls = NULL;
179 
180     pcre *pattern = CompileRegex(regex);
181     if (pattern == NULL)
182     {
183         // TODO: perhaps pcre has can give more info on this error?
184         Log(LOG_LEVEL_ERR, "Unable to pcre compile regex '%s'", regex);
185         return NULL;
186     }
187 
188     while ((cls = ClassTableIteratorNext(it)))
189     {
190         bool matched;
191         if (cls->ns)
192         {
193             char *class_expr = ClassRefToString(cls->ns, cls->name);
194             matched = StringMatchFullWithPrecompiledRegex(pattern, class_expr);
195             free(class_expr);
196         }
197         else
198         {
199             matched = StringMatchFullWithPrecompiledRegex(pattern, cls->name);
200         }
201 
202         if (matched)
203         {
204             break;
205         }
206     }
207 
208     pcre_free(pattern);
209 
210     ClassTableIteratorDestroy(it);
211     return cls;
212 }
213 
ClassTableRemove(ClassTable * table,const char * ns,const char * name)214 bool ClassTableRemove(ClassTable *table, const char *ns, const char *name)
215 {
216     if (ns == NULL)
217     {
218         ns = "default";
219     }
220 
221     char fullname[ strlen(ns) + 1 + strlen(name) + 1 ];
222     xsnprintf(fullname, sizeof(fullname), "%s:%s", ns, name);
223 
224     return ClassMapRemove(table->classes, fullname);
225 }
226 
ClassTableClear(ClassTable * table)227 bool ClassTableClear(ClassTable *table)
228 {
229     bool has_classes = (ClassMapSize(table->classes) > 0);
230     ClassMapClear(table->classes);
231     return has_classes;
232 }
233 
ClassTableIteratorNew(const ClassTable * table,const char * ns,bool is_hard,bool is_soft)234 ClassTableIterator *ClassTableIteratorNew(const ClassTable *table,
235                                           const char *ns,
236                                           bool is_hard, bool is_soft)
237 {
238     ClassTableIterator *iter = xmalloc(sizeof(*iter));
239 
240     iter->ns = ns ? xstrdup(ns) : NULL;
241     iter->iter = MapIteratorInit(table->classes->impl);
242     iter->is_soft = is_soft;
243     iter->is_hard = is_hard;
244 
245     return iter;
246 }
247 
ClassTableIteratorNext(ClassTableIterator * iter)248 Class *ClassTableIteratorNext(ClassTableIterator *iter)
249 {
250     MapKeyValue *keyvalue;
251 
252     while ((keyvalue = MapIteratorNext(&iter->iter)) != NULL)
253     {
254         Class *cls = keyvalue->value;
255 
256         /* Make sure we never store "default" as namespace in the ClassTable,
257          * instead we have always ns==NULL in that case. */
258         CF_ASSERT_FIX(cls->ns == NULL ||
259                       strcmp(cls->ns, "default") != 0,
260                       (cls->ns = NULL),
261                       "Class table contained \"default\" namespace,"
262                       " should never happen!");
263 
264         const char *key_ns = cls->ns ? cls->ns : "default";
265 
266         if (iter->ns && strcmp(key_ns, iter->ns) != 0)
267         {
268             continue;
269         }
270 
271         if (iter->is_soft && !iter->is_soft)
272         {
273             continue;
274         }
275         if (iter->is_hard && !iter->is_hard)
276         {
277             continue;
278         }
279 
280         return cls;
281     }
282 
283     return NULL;
284 }
285 
ClassTableIteratorDestroy(ClassTableIterator * iter)286 void ClassTableIteratorDestroy(ClassTableIterator *iter)
287 {
288     if (iter)
289     {
290         free(iter->ns);
291         free(iter);
292     }
293 }
294 
ClassRefParse(const char * expr)295 ClassRef ClassRefParse(const char *expr)
296 {
297     char *name_start = strchr(expr, ':');
298     if (!name_start)
299     {
300         return (ClassRef) { .ns = NULL, .name = xstrdup(expr) };
301     }
302     else
303     {
304         char *ns = NULL;
305         if ((name_start - expr) > 0)
306         {
307             ns = xstrndup(expr, name_start - expr);
308         }
309         else
310         {
311             // this would be invalid syntax
312             ns = xstrdup("");
313         }
314         char *name = xstrdup(name_start + 1);
315         return (ClassRef) { .ns = ns, .name = name };
316     }
317 }
318 
ClassRefToString(const char * ns,const char * name)319 char *ClassRefToString(const char *ns, const char *name)
320 {
321     assert(name != NULL);
322 
323     if (ns == NULL ||
324         strcmp("default", ns) == 0)
325     {
326         return xstrdup(name);
327     }
328     else
329     {
330         return StringConcatenate(3, ns, ":", name);
331     }
332 }
333 
ClassRefIsQualified(ClassRef ref)334 bool ClassRefIsQualified(ClassRef ref)
335 {
336     return ref.ns != NULL;
337 }
338 
ClassRefQualify(ClassRef * ref,const char * ns)339 void ClassRefQualify(ClassRef *ref, const char *ns)
340 {
341     free(ref->ns);
342     ref->ns = xstrdup(ns);
343 }
344 
ClassRefDestroy(ClassRef ref)345 void ClassRefDestroy(ClassRef ref)
346 {
347     free(ref.ns);
348     free(ref.name);
349 }
350