Remove string*_large() and add string_large().
[pwmd.git] / src / bulk.c
blob5e607d500b39073cf8477dd5cafdff4b532e2506
1 /*
2 Copyright (C) 2018-2021 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 version 2 as
8 published by the Free Software Foundation.
10 Pwmd 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.
15 You should have received a copy of the GNU General Public License
16 along with Pwmd. If not, see <http://www.gnu.org/licenses/>.
18 #include <ctype.h>
19 #include <string.h>
20 #include "bulk.h"
21 #include "mem.h"
23 void
24 bulk_free_list (struct sexp_s **list)
26 unsigned i;
28 for (i = 0; list && list[i]; i++)
30 struct bulk_cmd_s *c = list[i]->user;
32 if (c)
34 xfree (c->result);
35 xfree (c);
38 xfree (list[i]->tag);
39 xfree (list[i]->data);
40 bulk_free_list (list[i]->next);
41 xfree (list[i]);
44 xfree (list);
47 #define SKIP_WS(s, size) do { \
48 while (s && isspace (*s)) \
49 s++, (*size)--; \
50 } while (0)
52 #define NUMBER(s, size, buf, len, rc) do { \
53 const char *e = s; \
54 len = 0; \
55 while (e && isdigit (*e)) \
56 e++, len++; \
57 if (len >= sizeof (buf)-1) \
58 rc = GPG_ERR_INV_LENGTH; \
59 else if (len) { \
60 memcpy (buf, s, len); \
61 buf[len] = 0; \
62 if (*e != ':') \
63 rc = GPG_ERR_SYNTAX; \
64 else { \
65 s += len+1; \
66 *size -= len+1; \
67 e = NULL; \
68 len = strtoul (buf, (char **)&e, 10); \
69 if (e && *e) \
70 rc = GPG_ERR_INV_LENGTH; \
71 } \
72 } \
73 } while (0)
75 /* Like a canonical s-expression but without an internal representation. Any
76 * character is allowed. The tags and data are NULL terminated but not
77 * considered in the length structure members. */
78 static gpg_error_t
79 parse_sexp (struct sexp_s ***head, const char *str, size_t *size)
81 const char *p;
82 size_t t = 0;
83 gpg_error_t rc = 0;
85 for (p = str; p && *p;)
87 struct sexp_s **s, *cur;
88 char buf[16];
89 char *tag = NULL, *data = NULL;
90 size_t taglen, datalen;
92 if (*p == ')')
94 p++;
95 (*size)--;
96 SKIP_WS (p, size);
97 return 0;
99 else if (isspace (*p))
101 p++;
102 (*size)--;
103 continue;
105 else if (*p == '(')
107 size_t n;
109 if (!t)
110 return GPG_ERR_INV_SEXP;
112 p++;
113 (*size)--;
114 cur = (*head)[t-1];
115 n = *size;
116 rc = parse_sexp (&cur->next, p, size);
117 if (rc)
118 break;
120 p += n - *size;
121 continue;
124 SKIP_WS (p, size);
125 if (!isdigit (*p))
127 rc = GPG_ERR_SEXP_INV_LEN_SPEC;
128 break;
131 NUMBER (p, size, buf, taglen, rc);
132 if (rc)
133 break;
135 if (taglen > *size-1) // -1 for closing parentheses
137 rc = GPG_ERR_INV_LENGTH;
138 break;
141 if (!taglen)
142 continue;
144 tag = xmalloc ((taglen+1) * sizeof (char));
145 if (!tag)
147 rc = GPG_ERR_ENOMEM;
148 break;
151 memcpy (tag, p, taglen);
152 tag[taglen] = 0;
153 p += taglen;
154 *size -= taglen;
156 SKIP_WS (p, size);
157 NUMBER (p, size, buf, datalen, rc);
158 if (rc)
160 xfree (tag);
161 break;
164 if (datalen > *size-1)
166 xfree (tag);
167 rc = GPG_ERR_INV_LENGTH;
168 break;
171 if (datalen)
173 data = xmalloc ((datalen+1) * sizeof (char));
174 if (!data)
176 xfree (tag);
177 rc = GPG_ERR_ENOMEM;
178 break;
181 memcpy (data, p, datalen);
182 data[datalen] = 0;
183 p += datalen;
184 *size -= datalen;
187 cur = xcalloc (1, sizeof (struct sexp_s));
188 if (!cur)
190 xfree (tag);
191 xfree (data);
192 rc = GPG_ERR_ENOMEM;
193 break;
196 cur->tag = tag;
197 cur->taglen = taglen;
198 cur->data = data;
199 cur->datalen = datalen;
201 s = xrealloc (*head, (t+2) * sizeof (struct sexp_s *));
202 if (!s)
204 xfree (tag);
205 xfree (data);
206 xfree (cur);
207 rc = GPG_ERR_ENOMEM;
208 break;
211 s[t++] = cur;
212 s[t] = NULL;
213 *head = s;
216 if (!rc && (!*p || (*p && *p != ')')))
217 rc = GPG_ERR_SYNTAX;
219 return rc;
222 static gpg_error_t
223 validate_command (struct sexp_s *cur, struct command_table_s **commands)
225 unsigned i;
227 for (i = 0; commands[i]; i++)
229 if (!strcasecmp (cur->tag, "BULK"))
230 return GPG_ERR_ASS_NESTED_COMMANDS;
231 else if (!strcasecmp (cur->tag, "RESET")
232 || !strcasecmp (cur->tag, "BYE"))
233 return GPG_ERR_ASS_UNEXPECTED_CMD;
235 if (!strcasecmp (cur->tag, commands[i]->name))
237 struct bulk_cmd_s *p = xcalloc (1, sizeof (struct bulk_cmd_s));
239 if (!p)
240 return GPG_ERR_ENOMEM;
242 p->cmd = commands[i];
243 cur->user = p;
244 return 0;
248 return GPG_ERR_UNKNOWN_COMMAND;
251 /* Parse the s-expression using pwmd's syntax of:
253 * (2:id<I>:<id> <P>:<prot><D>:[<data>]
254 * [2:rc<R>:<code>[|<code>[...]](2:id...) | 2:id...])
256 * Where <I> is an integer specifying the length of the Id name <id>, <P> the
257 * length of the protocol command name <prot>, <D> is the length of <data>
258 * passed to <proto>, <R> is optional and the length of the numbered string
259 * <code> representing one or more pipe separated return codes of <prot>.
261 * When the return code of the command <prot> returns and matches <code>, the
262 * new command sequence in parentheses following <code> begins. You can specify
263 * multiple return codes and command branches for each command. Multiple return
264 * codes are separated with a pipe '|' to simulate an if-this-or-that
265 * expression for a command return value.
267 static gpg_error_t
268 parse_cmds (struct sexp_s **list, struct command_table_s **commands)
270 gpg_error_t rc = 0;
271 unsigned i;
273 for (i = 0; list && list[i]; i++)
275 struct sexp_s *cur = list[i];
277 if (!memcmp (cur->tag, "rc", cur->taglen))
278 goto validate_rc;
280 cur = list[i];
281 if (!memcmp (cur->tag, "id", cur->taglen))
283 if (cur->next)
284 return GPG_ERR_SYNTAX;
286 else
287 return GPG_ERR_SYNTAX;
289 cur = list[++i];
290 if (!cur)
291 return GPG_ERR_SYNTAX;
293 if (memcmp (cur->tag, "rc", cur->taglen))
295 rc = validate_command (cur, commands);
296 if (rc)
297 return rc;
300 validate_rc:
301 if (!memcmp (cur->tag, "rc", cur->taglen))
303 char *t;
305 if (!cur->data)
306 return GPG_ERR_SYNTAX;
308 if (*cur->data == '|' || cur->data[cur->datalen-1] == '|')
309 return GPG_ERR_SYNTAX;
311 for (t = cur->data; t && *t; t++)
313 if (!isdigit (*t) && *t != '|')
314 return GPG_ERR_SYNTAX;
317 rc = parse_cmds (list[i]->next, commands);
318 if (rc)
319 break;
320 continue;
324 return !list ? GPG_ERR_SYNTAX : rc;
327 gpg_error_t
328 bulk_parse_commands (struct sexp_s ***result, const char *str,
329 struct command_table_s **commands)
331 gpg_error_t rc;
332 size_t n = strlen (str)-1;
334 if (*str != '(')
335 return GPG_ERR_SYNTAX;
337 rc = parse_sexp (result, str+1, &n);
338 if (!rc && n)
339 rc = GPG_ERR_SYNTAX;
340 if (!rc)
341 rc = parse_cmds (*result, commands);
343 if (rc)
344 bulk_free_list (*result);
346 return rc;
349 static size_t
350 number_length (size_t n, char **result)
352 char buf[32];
354 snprintf (buf, sizeof (buf), "%zu", n);
355 if (result)
356 *result = str_dup (buf);
358 return strlen (buf);
361 static gpg_error_t
362 recurse_bulk_build_result (struct sexp_s **sexp, char **str, size_t *size)
364 unsigned i;
365 gpg_error_t rc = 0;
367 for (i = 0; sexp[i]; i++)
369 struct sexp_s *id = NULL;
370 struct bulk_cmd_s *cur;
371 size_t len, n;
372 char *p;
373 char *idlen = NULL, *rclen = NULL, *rcstr = NULL, *resultlen = NULL;
375 if (!memcmp (sexp[i]->tag, "id", sexp[i]->taglen))
376 id = sexp[i++];
377 else if (!memcmp (sexp[i]->tag, "rc", sexp[i]->taglen))
378 goto parse_rc;
380 cur = sexp[i]->user;
381 if (!cur || !cur->ran)
382 continue;
384 if (!id)
385 return GPG_ERR_SYNTAX;
387 len = n = *size;
388 n += number_length (id->datalen, &idlen);
389 n += id->datalen;
390 n += number_length (cur->rc, &rclen);
391 n += number_length (strlen (rclen), &rcstr);
392 n += number_length (cur->result_len, &resultlen);
393 n += cur->result_len;
394 n += 11;
396 p = xrealloc (*str, (n+1) * sizeof (char));
397 if (!p || !idlen || !rclen || !rcstr || !resultlen)
399 xfree (idlen);
400 xfree (rclen);
401 xfree (rcstr);
402 xfree (resultlen);
403 rc = GPG_ERR_ENOMEM;
404 break;
407 *size = n;
408 *str = p;
410 memcpy (&p[len], "2:id", 4);
411 len += 4;
412 memcpy (&p[len], idlen, strlen (idlen));
413 len += strlen (idlen);
414 xfree (idlen);
415 memcpy (&p[len++], ":", 1);
416 memcpy (&p[len], id->data, id->datalen);
417 len += id->datalen;
419 memcpy (&p[len], "2:rc", 4);
420 len += 4;
421 memcpy (&p[len], rcstr, strlen (rcstr));
422 len += strlen (rcstr);
423 xfree (rcstr);
424 memcpy (&p[len++], ":", 1);
425 memcpy (&p[len], rclen, strlen (rclen));
426 len += strlen (rclen);
427 xfree (rclen);
429 memcpy (&p[len], resultlen, strlen (resultlen));
430 len += strlen (resultlen);
431 xfree (resultlen);
432 memcpy (&p[len++], ":", 1);
433 if (cur->result)
434 memcpy (&p[len], cur->result, cur->result_len);
436 len += cur->result_len;
437 p[len] = 0;
439 parse_rc:
440 if (sexp[i+1] && sexp[i+1]->next)
442 rc = recurse_bulk_build_result (sexp[i+1]->next, str, size);
443 if (rc)
444 break;
448 return rc;
451 /* Creates a string representing the result of a bulk command list. Syntax:
453 * (11:bulk-result 2:id<I>:<id> 2:rc<R>:<code> <D>:[<data>] [2:id...])
455 * Where <I> is the length of <id>, <R> is the length of the command result
456 * <code> and <D> is the length of the command result <data> if any.
458 gpg_error_t
459 bulk_build_result (struct sexp_s **sexp, char **result)
461 gpg_error_t rc;
462 size_t size = 0;
464 *result = str_dup ("(11:bulk-result");
465 if (!(*result))
466 return GPG_ERR_ENOMEM;
468 size = strlen (*result);
469 rc = recurse_bulk_build_result (sexp, result, &size);
470 if (!rc)
472 char *p = xrealloc (*result, (size+2) * sizeof (char));
474 if (!p)
476 xfree (*result);
477 return GPG_ERR_ENOMEM;
480 p[size++] = ')';
481 p[size] = 0;
482 *result = p;
484 else
485 xfree (*result);
487 return rc;