socketpair type derived from tlsdata->ipproto
[tlspool] / pulleyback / parse.c
1 /* pulleyback/parse.c -- Backend from SteamWorks Pulley to TLS Pool.
2  *
3  * This is a backend for the Pulley component in SteamWorks, serving as
4  * an output driver towards the databases of the TLS Pool.  This is meant
5  * to enable LDAP-based configuration of the TLS Pool, so as to facilitate
6  * provisioning of its security settings by a trusted upstream party.
7  * 
8  * From: Rick van Rein <rick@openfortress.nl>
9  */
10
11
12 #include <ctype.h>
13 #include <syslog.h>
14 #include <string.h>
15 #include <assert.h>
16
17 #include <syslog.h>
18
19 #include "poolback.h"
20
21
22
23 /* The syntax for the parameter names, covering:
24  *  - "config" as a string reference to a TLS Pool configuration file
25  *  - "type" as a string reference to a database type
26  *  - "args" as a comma-separated list of arguments (count must match arg)
27  *  - "subtype" as a comma-separated list of fields for all but type="disclose"
28  */
29 static const syntax_keywordlist syntax_parameters = {
30         { "config",  "config parameter",  { MXG_CONFIG,  MXG_NONE } },
31         { "type",    "type parameter",    { MXG_TYPE,    MXG_NONE } },
32         { "args",    "args parameter",    { MXG_ARGS,    MXG_NONE } },
33         { "subtype", "subtype parameter", { MXG_SUBTYPE, MXG_NONE } },
34         { "valexp",  "valexp parameter",  { MXG_VALEXP,  MXG_NONE } },
35         KEYWORD_LISTEND
36 };
37
38
39 /* The exclusions due to the "type" values is formulated as a syntax
40  */
41 static const syntax_keywordlist exclusion_type = {
42         { "disclose", "disclose driver type", { MXG_SUBTYPE,
43                                                 MXG_PKCS11,
44                                                 MXG_CRED,
45                                                 MXG_VALEXP,
46                                                 MXG_CREDTYPE,
47                                                 MXG_ROLE,
48                                                 MXG_TRUSTKIND,
49                                                 MXG_NONE } },
50         { "localid",  "localid driver type",  { MXG_REMOTEID,
51                                                 MXG_TRUSTKIND,
52                                                 MXG_NONE } },
53         { "trust",    "trust driver type",    { MXG_REMOTEID,
54                                                 MXG_LOCALID,
55                                                 MXG_PKCS11,
56                                                 MXG_NONE } },
57         KEYWORD_LISTEND
58 };
59
60 /* In the same order as the exclusion_type string, the updated functions
61  * for the recognised database types.
62  */
63 static update_fun *update_type [] = {
64         update_disclose,
65         update_localid,
66         update_trust
67 };
68
69
70 /* The syntax for the "args" word list.  The formatting is such that the
71  * first (and only) MXG_ entry indicates the type if it happens to be one of
72  * the words that can be instantiated -- namely, MXG_ROLE and MXG_CREDTYPE.
73  */
74 static const syntax_keywordlist syntax_args = {
75         { "localid",  "localid argument",  { MXG_LOCALID, MXG_NONE } },
76         { "remoteid", "remoteid argument", { MXG_REMOTEID, MXG_NONE } },
77         { "pkcs11",   "pkcs11 argument",   { MXG_PKCS11, MXG_NONE } },
78         { "cred",     "cred argument",     { MXG_CRED, MXG_NONE } },
79         { "valexp",   "valexp argument",   { MXG_VALEXP, MXG_NONE } },
80         { "credtype", "credtype argument", { MXG_CREDTYPE, MXG_NONE } },
81         { "role",     "role argument",     { MXG_ROLE, MXG_NONE } },
82         KEYWORD_LISTEND
83 };
84
85
86 /* The syntax for the "subtype" word list.  This is also used to parse
87  * dynamically bound parameters for MXG_ROLE or MXG_CREDTYPE; there, it
88  * uses the specific property that the first listed MXG_ is the instance
89  * and the second listed MXG_ is the type of the entry.  The type must
90  * match with the self->args entry's indication of a type, of course.
91  */
92 static const syntax_keywordlist syntax_subtype = {
93         { "x509",      "x509 subtype",      { MXG_X509,
94                                               MXG_CREDTYPE,
95                                               MXG_NONE } },
96         { "openpgp",   "openpgp subtype",   { MXG_PGP,
97                                               MXG_CREDTYPE,
98                                               MXG_NONE } },
99         { "client",    "client subtype",    { MXG_CLIENT,
100                                               MXG_ROLE,
101                                               MXG_NONE } },
102         { "server",    "server subtype",    { MXG_SERVER,
103                                               MXG_ROLE,
104                                               MXG_NONE } },
105         { "peer",      "peer subtype",      { MXG_PEER,
106                                               MXG_ROLE,
107                                               MXG_CLIENT,
108                                               MXG_SERVER,
109                                               MXG_NONE } },
110         { "chained",   "chained subtype",   { MXG_CHAINED,
111                                               MXG_NONE } },
112         { "authority", "authority subtype", { MXG_AUTHORITY,
113                                               MXG_TRUSTKIND,
114                                               MXG_NONE } },
115         KEYWORD_LISTEND
116 };
117
118
119 /* The required resources for the various types
120  */
121 static const enum mutexgroup typereq_any [] = {
122         MXG_TYPE, MXG_ARGS,
123         MXG_NONE
124 };
125 static const enum mutexgroup typereq_disclose [] = {
126         MXG_TYPE, MXG_ARGS, MXG_LOCALID, MXG_REMOTEID,
127         MXG_NONE
128 };
129 static const enum mutexgroup typereq_localid [] = {
130         MXG_TYPE, MXG_ARGS, MXG_LOCALID, MXG_PKCS11, MXG_CRED, MXG_CREDTYPE, MXG_ROLE,
131         MXG_NONE
132 };
133 static const enum mutexgroup typereq_trust [] = {
134         MXG_TYPE, MXG_ARGS, MXG_CRED, MXG_VALEXP, MXG_CREDTYPE, MXG_ROLE,
135         MXG_TRUSTKIND,
136         MXG_NONE
137 };
138
139
140 /* Chase for a string's keyword_description.  The word is supposed to
141  * end in a given terminal character although, if this is a comma, the
142  * end of a string will also match (but comma lists may not be empty).
143  *
144  * The returned value indicates success with a non-negative value, or
145  * syntax error with -1.  The positive values returned is the index in
146  * the keyword_descriptor array passed in.
147  *
148  * Searching starts at the provided (pointed-at) offset in the string,
149  * and that offset will be incremented to point beyond the keyword,
150  * though never beyond the end-of-string NUL character.
151  *
152  * This routine updates the resource claims in the provided resource
153  * array, and reports on syslog() when it finds a conflict; in that
154  * case, it also returns an error.
155  *
156  * It is permitted to pass NULL for resources; no check on conflicts
157  * will then be made; this is useful during dynamic parsing, after
158  * the static analysis has already taken care of resource conflicts
159  * and all that is required to live up to it is recognising keywords
160  * of the proper type.
161  */
162 static int chase_keyword_descriptor (const struct keyword_descriptor *kd,
163                                         char *resources [MXG_COUNT],
164                                         const char *text, int *offset) {
165         int kdofs;
166         const enum mutexgroup *mtg;
167         if (text [*offset] == '\0') {
168                 syslog (LOG_ERR, "Unexpected end of string in %s", text);
169                 return -1;
170         }
171         for (kdofs = 0; kd->keyword != NULL; kdofs++, kd++) {
172                 int kwl = strlen (kd->keyword);
173                 if ((memcmp (kd->keyword, text + *offset, kwl) == 0) && (isalnum (text [*offset + kwl]) == 0)) {
174                         // We found a match -- check for resource clashes
175                         if (resources != NULL) {
176                                 for (mtg = kd->resources; *mtg != MXG_NONE; mtg++) {
177                                         if (resources [*mtg] != NULL) {
178                                                 syslog (LOG_ERR, "You cannot specify both %s and %s", resources [*mtg], kd [kdofs].claim);
179                                                 return -1;
180                                         }
181                                         resources [*mtg] = kd->claim;
182                                 }
183                         }
184                         // Return successfully
185                         *offset += kwl;
186                         return kdofs;
187                 }
188         }
189         syslog (LOG_ERR, "Unrecognised keyword at offset %d in %s", *offset, text);
190         return -1;
191 }
192
193
194 /* Parse a comma-separated list of entries and store them in-order in a
195  * sequence of the order numbers.  Return -1 on error, or otherwise the
196  * number of entries that were parsed successfully.
197  *
198  * The offset points to the position in the string, and this will be
199  * updated in case of success.  Also, the resources will be used to
200  * collect potential clashes between mutually exclusive resource claims.
201  */
202 static int parse_wordlist (const struct keyword_descriptor *kd,
203                                         int kdidx [MXG_COUNT],
204                                         char *resources [MXG_COUNT],
205                                         const char *text, int *offset) {
206         int rv;
207         int kdidx_count = 0;
208         char comma;
209         while (1) {
210                 rv = chase_keyword_descriptor (kd, resources, text, offset);
211                 if (rv < 0) {
212                         return -1;
213                 }
214                 if (kdidx_count >= MXG_COUNT) {
215                         syslog (LOG_ERR, "More than %d words in %s", kdidx_count, text);
216                         return -1;
217                 }
218                 // rv is the number of the kd entry;
219                 // Now place its first resource into kdidx
220                 kdidx [kdidx_count++] = kd [rv].resources [0];
221                 comma = text [*offset];
222                 if (comma != ',') {
223                         break;
224                 }
225                 (*offset)++;
226         }
227         if (comma != '\0') {
228                 syslog (LOG_ERR, "Unexpected character '%c' at offset %d in %s", comma, *offset, text);
229                 return -1;
230         }
231         return kdidx_count;
232 }
233
234
235 /* Parse all arguments to the backend function for the purpose of creating
236  * a new output driver instance.  All the words must be recognised and all
237  * the sublists ought to be as well, and in the end all the type-required
238  * resources must have been defined.
239  *
240  * This function returns -1 on error, or 0 for success, in the latter case
241  * it also configures the structure pointed at by data for later use.
242  * In the data structure, only the parameters from the Pulley Script are
243  * overwritten (and will fully be overwritten upon success).
244  */
245 int parse_arguments (int argc, char *argv [], int varc,
246                                 struct pulleyback_tlspool *self) {
247         int argi;
248         int parsed;
249         int num_args = 0;
250         int num_subtypes = 0;
251         int list_subtypes [MXG_COUNT + 1];
252         char *resources [MXG_COUNT];
253         int argofs;
254         const enum mutexgroup *minreq;
255         //
256         // Initialise parsing structures
257         self->config = NULL;
258         self->type = NULL;
259         self->subtypes = 0;
260         self->valexp = NULL;
261         assert (sizeof (self->args   ) == sizeof (int) * (MXG_COUNT + 1));
262         assert (sizeof (list_subtypes) == sizeof (int) * (MXG_COUNT + 1));
263         for (argi=0; argi < MXG_COUNT + 1; argi++) {
264                 self->args    [argi] =
265                 list_subtypes [argi] = MXG_NONE;
266         }
267         memset (resources, 0, sizeof (resources));  // all NULL strings
268         //
269         // Pick up all the words and word lists, while scoring resources
270         for (argi = 1; argi < argc; argi++) {
271                 argofs = 0;
272                 parsed = chase_keyword_descriptor (
273                                         syntax_parameters,
274                                         resources,
275                                         argv [argi], &argofs);
276                 if ((parsed >= 0) && (argv [argi] [argofs] != '=')) {
277                         syslog (LOG_ERR, "No equals sign in %s", argv [argi]);
278                         parsed = -1;
279                 }
280                 argofs++;  // Skip '=' sign
281                 if (parsed == -1) {
282                         return -1;
283                 }
284                 switch (syntax_parameters [parsed].resources [0]) {
285                 case MXG_CONFIG:
286                         self->config = argv [argi] + argofs;
287                         parsed = (*self->config) ? 0 : -1;;
288                         break;
289                 case MXG_TYPE:
290                         parsed = chase_keyword_descriptor (
291                                         exclusion_type,
292                                         resources,
293                                         argv [argi], &argofs);
294                         minreq = typereq_any;
295                         if (parsed >= 0) {
296                                 self->type = exclusion_type [parsed].keyword;
297                                 self->update =  update_type [parsed];
298                                 if (*exclusion_type [parsed].keyword == 'd') {
299                                         minreq = typereq_disclose;
300                                 } else if (*exclusion_type [parsed].keyword == 'l') {
301                                         minreq = typereq_localid;
302                                 } else if (*exclusion_type [parsed].keyword == 't') {
303                                         minreq = typereq_trust;
304                                 }
305                         }
306                         break;
307                 case MXG_ARGS:
308                         parsed = num_args = parse_wordlist (
309                                         syntax_args,
310                                         self->args,
311                                         resources,
312                                         argv [argi], &argofs);
313                         break;
314                 case MXG_SUBTYPE:
315                         parsed = num_subtypes = parse_wordlist (
316                                         syntax_subtype,
317                                         list_subtypes,
318                                         resources,
319                                         argv [argi], &argofs);
320                         break;
321                 case MXG_VALEXP:
322                         self->valexp = argv [argi] + argofs;
323                 default:
324                 case -1:
325                         parsed = -1;
326                         break;
327                 }
328                 if (parsed == -1) {
329                         return -1;
330                 }
331         }
332         //
333         // Compare the number of args entries to the number supplied in varc
334         if (num_args != varc) {
335                 syslog (LOG_ERR, "You listed %d args keywords, but provided %d variables", num_args, varc);
336                 parsed = -1;
337         }
338         //
339         // Ensure that minimum requirements are met
340         while (*minreq != MXG_NONE) {
341                 if (resources [*minreq] == NULL) {
342                         //TODO// Would be nice to say what is missing...
343                         syslog (LOG_ERR, "Missing resource in output handler");
344                         parsed = -1;
345                 }
346                 minreq++;
347         }
348         if (parsed == -1) {
349                 return -1;
350         }
351         //
352         // Harvest the flags for the subtypes found
353         assert (8 * sizeof (self->subtypes) <= 1 << MXG_COUNT);
354         for (argi = 0; argi < num_subtypes; argi++) {
355                 self->subtypes |= ( 1 << list_subtypes [argi] );
356                 assert ((self->subtypes & (1 << list_subtypes [argi])) != 0);
357         }
358         return 0;
359 }
360
361
362 /* Parse the dynamic instantiation value of a parameter, if its self->args [i]
363  * indicates that it is suitable for this.  This is currently only the case
364  * with MXG_ROLE and MXG_CREDTYPE.  Instantiations are taken from the
365  * syntax_subtype table, whose resources are ordered to accommodate that.
366  *
367  * On encountering an error, this function returns MXG_NONE; otherwise it
368  * returns the recognised word, which is of the indicated type.
369  *
370  * Resource management has all been taken care of at compile time, so
371  * that is trivially skipped during this run.
372  */
373 enum mutexgroup parse_dynamic_argument (char *arg, enum mutexgroup dyntype) {
374         int argofs = 0;
375         int ok = 1;
376         int rv;
377         assert ((dyntype == MXG_ROLE) || (dyntype == MXG_CREDTYPE));
378         rv = chase_keyword_descriptor ( syntax_subtype,
379                                         NULL  /* no resources */ ,
380                                         arg, &argofs);
381         ok = ok && (rv >= 0);
382         ok = ok && (arg [argofs] == '\0');
383         ok = ok && (syntax_subtype [rv].resources [1] == dyntype);
384         return  ok? syntax_subtype [rv].resources [0]: MXG_NONE;
385 }
386