1 /* pulleyback/parse.c -- Backend from SteamWorks Pulley to TLS Pool.
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.
8 * From: Rick van Rein <rick@openfortress.nl>
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"
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 } },
39 /* The exclusions due to the "type" values is formulated as a syntax
41 static const syntax_keywordlist exclusion_type = {
42 { "disclose", "disclose driver type", { MXG_SUBTYPE,
50 { "localid", "localid driver type", { MXG_REMOTEID,
53 { "trust", "trust driver type", { MXG_REMOTEID,
60 /* In the same order as the exclusion_type string, the updated functions
61 * for the recognised database types.
63 static update_fun *update_type [] = {
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.
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 } },
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.
92 static const syntax_keywordlist syntax_subtype = {
93 { "x509", "x509 subtype", { MXG_X509,
96 { "openpgp", "openpgp subtype", { MXG_PGP,
99 { "client", "client subtype", { MXG_CLIENT,
102 { "server", "server subtype", { MXG_SERVER,
105 { "peer", "peer subtype", { MXG_PEER,
110 { "chained", "chained subtype", { MXG_CHAINED,
112 { "authority", "authority subtype", { MXG_AUTHORITY,
119 /* The required resources for the various types
121 static const enum mutexgroup typereq_any [] = {
125 static const enum mutexgroup typereq_disclose [] = {
126 MXG_TYPE, MXG_ARGS, MXG_LOCALID, MXG_REMOTEID,
129 static const enum mutexgroup typereq_localid [] = {
130 MXG_TYPE, MXG_ARGS, MXG_LOCALID, MXG_PKCS11, MXG_CRED, MXG_CREDTYPE, MXG_ROLE,
133 static const enum mutexgroup typereq_trust [] = {
134 MXG_TYPE, MXG_ARGS, MXG_CRED, MXG_VALEXP, MXG_CREDTYPE, MXG_ROLE,
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).
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.
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.
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.
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.
162 static int chase_keyword_descriptor (const struct keyword_descriptor *kd,
163 char *resources [MXG_COUNT],
164 const char *text, int *offset) {
166 const enum mutexgroup *mtg;
167 if (text [*offset] == '\0') {
168 syslog (LOG_ERR, "Unexpected end of string in %s", text);
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);
181 resources [*mtg] = kd->claim;
184 // Return successfully
189 syslog (LOG_ERR, "Unrecognised keyword at offset %d in %s", *offset, text);
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.
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.
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) {
210 rv = chase_keyword_descriptor (kd, resources, text, offset);
214 if (kdidx_count >= MXG_COUNT) {
215 syslog (LOG_ERR, "More than %d words in %s", kdidx_count, text);
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];
228 syslog (LOG_ERR, "Unexpected character '%c' at offset %d in %s", comma, *offset, text);
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.
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).
245 int parse_arguments (int argc, char *argv [], int varc,
246 struct pulleyback_tlspool *self) {
250 int num_subtypes = 0;
251 int list_subtypes [MXG_COUNT + 1];
252 char *resources [MXG_COUNT];
254 const enum mutexgroup *minreq;
256 // Initialise parsing structures
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++) {
265 list_subtypes [argi] = MXG_NONE;
267 memset (resources, 0, sizeof (resources)); // all NULL strings
269 // Pick up all the words and word lists, while scoring resources
270 for (argi = 1; argi < argc; argi++) {
272 parsed = chase_keyword_descriptor (
275 argv [argi], &argofs);
276 if ((parsed >= 0) && (argv [argi] [argofs] != '=')) {
277 syslog (LOG_ERR, "No equals sign in %s", argv [argi]);
280 argofs++; // Skip '=' sign
284 switch (syntax_parameters [parsed].resources [0]) {
286 self->config = argv [argi] + argofs;
287 parsed = (*self->config) ? 0 : -1;;
290 parsed = chase_keyword_descriptor (
293 argv [argi], &argofs);
294 minreq = typereq_any;
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;
308 parsed = num_args = parse_wordlist (
312 argv [argi], &argofs);
315 parsed = num_subtypes = parse_wordlist (
319 argv [argi], &argofs);
322 self->valexp = argv [argi] + argofs;
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);
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");
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);
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.
367 * On encountering an error, this function returns MXG_NONE; otherwise it
368 * returns the recognised word, which is of the indicated type.
370 * Resource management has all been taken care of at compile time, so
371 * that is trivially skipped during this run.
373 enum mutexgroup parse_dynamic_argument (char *arg, enum mutexgroup dyntype) {
377 assert ((dyntype == MXG_ROLE) || (dyntype == MXG_CREDTYPE));
378 rv = chase_keyword_descriptor ( syntax_subtype,
379 NULL /* no resources */ ,
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;