ACL: Treat an empty "_acl" attribute value as non-existent.
[pwmd.git] / src / bulk.c
blobcaf10978d62ac432fa51175d99bf2811918bd05c
1 /*
2 Copyright (C) 2018-2019 Ben Kibbey <bjk@luxsci.net>
4 This file is part of pwmd.
6 Pwmd is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 2 of the License, or
9 (at your option) any later version.
11 Pwmd is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with Pwmd. If not, see <http://www.gnu.org/licenses/>.
19 #include <ctype.h>
20 #include <string.h>
21 #include "bulk.h"
22 #include "mem.h"
24 void
25 bulk_free_list (struct sexp_s **list)
27 unsigned i;
29 for (i = 0; list && list[i]; i++)
31 struct bulk_cmd_s *c = list[i]->user;
33 if (c)
35 xfree (c->result);
36 xfree (c);
39 xfree (list[i]->tag);
40 xfree (list[i]->data);
41 bulk_free_list (list[i]->next);
42 xfree (list[i]);
45 xfree (list);
48 #define SKIP_WS(s, size) do { \
49 while (s && isspace (*s)) \
50 s++, (*size)--; \
51 } while (0)
53 #define NUMBER(s, size, buf, len, rc) do { \
54 const char *e = s; \
55 len = 0; \
56 while (e && isdigit (*e)) \
57 e++, len++; \
58 if (len >= sizeof (buf)-1) \
59 rc = GPG_ERR_INV_LENGTH; \
60 else if (len) { \
61 memcpy (buf, s, len); \
62 buf[len] = 0; \
63 if (*e != ':') \
64 rc = GPG_ERR_SYNTAX; \
65 else { \
66 s += len+1; \
67 *size -= len+1; \
68 e = NULL; \
69 len = strtoul (buf, (char **)&e, 10); \
70 if (e && *e) \
71 rc = GPG_ERR_INV_LENGTH; \
72 } \
73 } \
74 } while (0)
76 /* Like a canonical s-expression but without an internal representation. Any
77 * character is allowed. The tags and data are NULL terminated but not
78 * considered in the length structure members. */
79 static gpg_error_t
80 parse_sexp (struct sexp_s ***head, const char *str, size_t *size)
82 const char *p;
83 size_t t = 0;
84 gpg_error_t rc = 0;
86 for (p = str; p && *p;)
88 struct sexp_s **s, *cur;
89 char buf[16];
90 char *tag = NULL, *data = NULL;
91 size_t taglen, datalen;
93 if (*p == ')')
95 p++;
96 (*size)--;
97 SKIP_WS (p, size);
98 return 0;
100 else if (isspace (*p))
102 p++;
103 (*size)--;
104 continue;
106 else if (*p == '(')
108 size_t n;
110 if (!t)
111 return GPG_ERR_INV_SEXP;
113 p++;
114 (*size)--;
115 cur = (*head)[t-1];
116 n = *size;
117 rc = parse_sexp (&cur->next, p, size);
118 if (rc)
119 break;
121 p += n - *size;
122 continue;
125 SKIP_WS (p, size);
126 if (!isdigit (*p))
128 rc = GPG_ERR_SEXP_INV_LEN_SPEC;
129 break;
132 NUMBER (p, size, buf, taglen, rc);
133 if (rc)
134 break;
136 if (taglen > *size-1) // -1 for closing parentheses
138 rc = GPG_ERR_INV_LENGTH;
139 break;
142 if (!taglen)
143 continue;
145 tag = xmalloc ((taglen+1) * sizeof (char));
146 if (!tag)
148 rc = GPG_ERR_ENOMEM;
149 break;
152 memcpy (tag, p, taglen);
153 tag[taglen] = 0;
154 p += taglen;
155 *size -= taglen;
157 SKIP_WS (p, size);
158 NUMBER (p, size, buf, datalen, rc);
159 if (rc)
161 xfree (tag);
162 break;
165 if (datalen > *size-1)
167 xfree (tag);
168 rc = GPG_ERR_INV_LENGTH;
169 break;
172 if (datalen)
174 data = xmalloc ((datalen+1) * sizeof (char));
175 if (!data)
177 xfree (tag);
178 rc = GPG_ERR_ENOMEM;
179 break;
182 memcpy (data, p, datalen);
183 data[datalen] = 0;
184 p += datalen;
185 *size -= datalen;
188 cur = xcalloc (1, sizeof (struct sexp_s));
189 if (!cur)
191 xfree (tag);
192 xfree (data);
193 rc = GPG_ERR_ENOMEM;
194 break;
197 cur->tag = tag;
198 cur->taglen = taglen;
199 cur->data = data;
200 cur->datalen = datalen;
202 s = xrealloc (*head, (t+2) * sizeof (struct sexp_s *));
203 if (!s)
205 xfree (tag);
206 xfree (data);
207 xfree (cur);
208 rc = GPG_ERR_ENOMEM;
209 break;
212 s[t++] = cur;
213 s[t] = NULL;
214 *head = s;
217 if (!rc && (!*p || (*p && *p != ')')))
218 rc = GPG_ERR_SYNTAX;
220 return rc;
223 static gpg_error_t
224 validate_command (struct sexp_s *cur, struct command_table_s **commands)
226 unsigned i;
228 for (i = 0; commands[i]; i++)
230 if (!strcasecmp (cur->tag, "BULK"))
231 return GPG_ERR_ASS_NESTED_COMMANDS;
232 else if (!strcasecmp (cur->tag, "RESET")
233 || !strcasecmp (cur->tag, "BYE"))
234 return GPG_ERR_ASS_UNEXPECTED_CMD;
236 if (!strcasecmp (cur->tag, commands[i]->name))
238 struct bulk_cmd_s *p = xcalloc (1, sizeof (struct bulk_cmd_s));
240 if (!p)
241 return GPG_ERR_ENOMEM;
243 p->cmd = commands[i];
244 cur->user = p;
245 return 0;
249 return GPG_ERR_UNKNOWN_COMMAND;
252 /* Parse the s-expression using pwmd's syntax of:
254 * (2:id<I>:<id> <P>:<prot><D>:[<data>]
255 * [2:rc<R>:<code>[|<code>[...]](2:id...) | 2:id...])
257 * Where <I> is an integer specifying the length of the Id name <id>, <P> the
258 * length of the protocol command name <prot>, <D> is the length of <data>
259 * passed to <proto>, <R> is optional and the length of the numbered string
260 * <code> representing one or more pipe separated return codes of <prot>.
262 * When the return code of the command <prot> returns and matches <code>, the
263 * new command sequence in parentheses following <code> begins. You can specify
264 * multiple return codes and command branches for each command. Multiple return
265 * codes are separated with a pipe '|' to simulate an if-this-or-that
266 * expression for a command return value.
268 static gpg_error_t
269 parse_cmds (struct sexp_s **list, struct command_table_s **commands)
271 gpg_error_t rc = 0;
272 unsigned i;
274 for (i = 0; list && list[i]; i++)
276 struct sexp_s *cur = list[i];
278 if (!memcmp (cur->tag, "rc", cur->taglen))
279 goto validate_rc;
281 cur = list[i];
282 if (!memcmp (cur->tag, "id", cur->taglen))
284 if (cur->next)
285 return GPG_ERR_SYNTAX;
287 else
288 return GPG_ERR_SYNTAX;
290 cur = list[++i];
291 if (!cur)
292 return GPG_ERR_SYNTAX;
294 if (memcmp (cur->tag, "rc", cur->taglen))
296 rc = validate_command (cur, commands);
297 if (rc)
298 return rc;
301 validate_rc:
302 if (!memcmp (cur->tag, "rc", cur->taglen))
304 char *t;
306 if (!cur->data)
307 return GPG_ERR_SYNTAX;
309 if (*cur->data == '|' || cur->data[cur->datalen-1] == '|')
310 return GPG_ERR_SYNTAX;
312 for (t = cur->data; t && *t; t++)
314 if (!isdigit (*t) && *t != '|')
315 return GPG_ERR_SYNTAX;
318 rc = parse_cmds (list[i]->next, commands);
319 if (rc)
320 break;
321 continue;
325 return !list ? GPG_ERR_SYNTAX : rc;
328 gpg_error_t
329 bulk_parse_commands (struct sexp_s ***result, const char *str,
330 struct command_table_s **commands)
332 gpg_error_t rc;
333 size_t n = strlen (str)-1;
335 if (*str != '(')
336 return GPG_ERR_SYNTAX;
338 rc = parse_sexp (result, str+1, &n);
339 if (!rc && n)
340 rc = GPG_ERR_SYNTAX;
341 if (!rc)
342 rc = parse_cmds (*result, commands);
344 if (rc)
345 bulk_free_list (*result);
347 return rc;
350 static size_t
351 number_length (size_t n, char **result)
353 char buf[32];
355 snprintf (buf, sizeof (buf), "%zu", n);
356 if (result)
357 *result = str_dup (buf);
359 return strlen (buf);
362 static gpg_error_t
363 recurse_bulk_build_result (struct sexp_s **sexp, char **str, size_t *size)
365 unsigned i;
366 gpg_error_t rc = 0;
368 for (i = 0; sexp[i]; i++)
370 struct sexp_s *id = NULL;
371 struct bulk_cmd_s *cur;
372 size_t len, n;
373 char *p;
374 char *idlen = NULL, *rclen = NULL, *rcstr = NULL, *resultlen = NULL;
376 if (!memcmp (sexp[i]->tag, "id", sexp[i]->taglen))
377 id = sexp[i++];
378 else if (!memcmp (sexp[i]->tag, "rc", sexp[i]->taglen))
379 goto parse_rc;
381 cur = sexp[i]->user;
382 if (!cur || !cur->ran)
383 continue;
385 len = n = *size;
386 n += number_length (id->datalen, &idlen);
387 n += id->datalen;
388 n += number_length (cur->rc, &rclen);
389 n += number_length (strlen (rclen), &rcstr);
390 n += number_length (cur->result_len, &resultlen);
391 n += cur->result_len;
392 n += 11;
394 p = xrealloc (*str, (n+1) * sizeof (char));
395 if (!p || !idlen || !rclen || !rcstr || !resultlen)
397 xfree (idlen);
398 xfree (rclen);
399 xfree (rcstr);
400 xfree (resultlen);
401 rc = GPG_ERR_ENOMEM;
402 break;
405 *size = n;
406 *str = p;
408 memcpy (&p[len], "2:id", 4);
409 len += 4;
410 memcpy (&p[len], idlen, strlen (idlen));
411 len += strlen (idlen);
412 xfree (idlen);
413 memcpy (&p[len++], ":", 1);
414 memcpy (&p[len], id->data, id->datalen);
415 len += id->datalen;
417 memcpy (&p[len], "2:rc", 4);
418 len += 4;
419 memcpy (&p[len], rcstr, strlen (rcstr));
420 len += strlen (rcstr);
421 xfree (rcstr);
422 memcpy (&p[len++], ":", 1);
423 memcpy (&p[len], rclen, strlen (rclen));
424 len += strlen (rclen);
425 xfree (rclen);
427 memcpy (&p[len], resultlen, strlen (resultlen));
428 len += strlen (resultlen);
429 xfree (resultlen);
430 memcpy (&p[len++], ":", 1);
431 if (cur->result)
432 memcpy (&p[len], cur->result, cur->result_len);
434 len += cur->result_len;
435 p[len] = 0;
437 parse_rc:
438 if (sexp[i+1] && sexp[i+1]->next)
440 rc = recurse_bulk_build_result (sexp[i+1]->next, str, size);
441 if (rc)
442 break;
446 return rc;
449 /* Creates a string representing the result of a bulk command list. Syntax:
451 * (11:bulk-result 2:id<I>:<id> 2:rc<R>:<code> <D>:[<data>] [2:id...])
453 * Where <I> is the length of <id>, <R> is the length of the command result
454 * <code> and <D> is the length of the command result <data> if any.
456 gpg_error_t
457 bulk_build_result (struct sexp_s **sexp, char **result)
459 gpg_error_t rc;
460 size_t size = 0;
462 *result = str_dup ("(11:bulk-result");
463 if (!*result)
464 return GPG_ERR_ENOMEM;
466 size = strlen (*result);
467 rc = recurse_bulk_build_result (sexp, result, &size);
468 if (!rc)
470 char *p = xrealloc (*result, (size+2) * sizeof (char));
472 if (!p)
474 xfree (*result);
475 return GPG_ERR_ENOMEM;
478 p[size++] = ')';
479 p[size] = 0;
480 *result = p;
482 else
483 xfree (*result);
485 return rc;