BULK: Fix potential crash due to syntax error.
[pwmd.git] / src / bulk.c
blob14db5a0d59ae0a7c417382900a138e316681bf6d
1 /*
2 Copyright (C) 2018 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 s += len+1; \
64 *size -= len+1; \
65 e = NULL; \
66 len = strtoul (buf, (char **)&e, 10); \
67 if (e && *e) \
68 rc = GPG_ERR_INV_LENGTH; \
69 } \
70 } while (0)
72 /* Like a canonical s-expression but without an internal representation. Any
73 * character is allowed. The tags and data are NULL terminated but not
74 * considered in the length structure members. */
75 static gpg_error_t
76 parse_sexp (struct sexp_s ***head, const char *str, size_t *size)
78 const char *p;
79 size_t t = 0;
80 gpg_error_t rc = 0;
82 for (p = str; p && *p;)
84 struct sexp_s **s, *cur;
85 char buf[16];
86 char *tag = NULL, *data = NULL;
87 size_t taglen, datalen;
89 if (*p == ')')
91 (*size)--;
92 break;
94 else if (isspace (*p))
96 p++;
97 (*size)--;
98 continue;
100 else if (*p == '(')
102 size_t n;
104 if (!t)
105 return GPG_ERR_INV_SEXP;
107 p++;
108 (*size)--;
109 cur = (*head)[t-1];
110 n = *size;
111 rc = parse_sexp (&cur->next, p, size);
112 if (rc)
113 break;
115 p += n - *size;
116 continue;
119 SKIP_WS (p, size);
120 if (!isdigit (*p))
122 rc = GPG_ERR_INV_LENGTH;
123 break;
126 NUMBER (p, size, buf, taglen, rc);
127 if (rc)
128 break;
130 if (taglen > *size-1) // -1 for closing parentheses
132 rc = GPG_ERR_INV_LENGTH;
133 break;
136 if (!taglen)
137 continue;
139 tag = xmalloc ((taglen+1) * sizeof (char));
140 if (!tag)
142 rc = GPG_ERR_ENOMEM;
143 break;
146 memcpy (tag, p, taglen);
147 tag[taglen] = 0;
148 p += taglen;
149 *size -= taglen;
151 NUMBER (p, size, buf, datalen, rc);
152 if (rc)
154 xfree (tag);
155 break;
158 if (datalen > *size-1)
160 xfree (tag);
161 rc = GPG_ERR_INV_LENGTH;
162 break;
165 if (datalen)
167 data = xmalloc ((datalen+1) * sizeof (char));
168 if (!data)
170 xfree (tag);
171 rc = GPG_ERR_ENOMEM;
172 break;
175 memcpy (data, p, datalen);
176 data[datalen] = 0;
177 p += datalen;
178 *size -= datalen;
181 cur = xcalloc (1, sizeof (struct sexp_s));
182 if (!cur)
184 xfree (tag);
185 xfree (data);
186 rc = GPG_ERR_ENOMEM;
187 break;
190 cur->tag = tag;
191 cur->taglen = taglen;
192 cur->data = data;
193 cur->datalen = datalen;
195 s = xrealloc (*head, (t+2) * sizeof (struct sexp_s *));
196 if (!s)
198 xfree (tag);
199 xfree (data);
200 xfree (cur);
201 rc = GPG_ERR_ENOMEM;
202 break;
205 s[t++] = cur;
206 s[t] = NULL;
207 *head = s;
210 return rc;
213 static unsigned
214 count_c (const char *str, const char c)
216 const char *p;
217 unsigned n = 0;
219 for (p = str; p && *p; p++)
221 if (*p == c)
222 n++;
225 return n;
228 static gpg_error_t
229 validate_command (struct sexp_s *cur, struct command_table_s **commands)
231 unsigned i;
233 for (i = 0; commands[i]; i++)
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 * (7:command 2:id<I>:<id> <P>:<prot><D>:[<data>]
254 * [2:rc<R>:<code>(7:command ...)] [7:command ...])
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 a return code 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.
265 static gpg_error_t
266 parse_cmds (struct sexp_s **list, struct command_table_s **commands)
268 gpg_error_t rc = 0;
269 unsigned i;
271 for (i = 0; list && list[i]; i++)
273 struct sexp_s *cur = list[i];
275 if (!memcmp (cur->tag, "command", cur->taglen))
277 if (cur->datalen || cur->next)
278 return GPG_ERR_SYNTAX;
280 else if (!memcmp (cur->tag, "rc", cur->taglen))
281 goto validate_rc;
283 cur = list[++i];
284 if (cur && !memcmp (cur->tag, "id", cur->taglen))
286 if (cur->next)
287 return GPG_ERR_SYNTAX;
289 else
290 return GPG_ERR_SYNTAX;
292 cur = list[++i];
293 if (!cur)
294 return GPG_ERR_SYNTAX;
296 if (cur && memcmp (cur->tag, "rc", cur->taglen))
298 rc = validate_command (cur, commands);
299 if (rc)
300 return rc;
303 validate_rc:
304 if (cur && !memcmp (cur->tag, "rc", cur->taglen))
306 char *e = NULL;
308 if (!cur->data)
309 return GPG_ERR_SYNTAX;
311 (void)strtoul (cur->data, &e, 10);
312 if (e && *e)
313 return GPG_ERR_SYNTAX;
315 rc = parse_cmds (list[i]->next, commands);
316 if (rc)
317 break;
318 continue;
322 return !list ? GPG_ERR_SYNTAX : rc;
325 gpg_error_t
326 bulk_parse_commands (struct sexp_s ***result, const char *str,
327 struct command_table_s **commands)
329 gpg_error_t rc;
330 size_t n = strlen (str)-1;
332 if (count_c (str, '(') != count_c (str, ')'))
333 return GPG_ERR_SEXP_UNMATCHED_PAREN;
335 rc = parse_sexp (result, str+1, &n);
336 if (!rc)
337 rc = parse_cmds (*result, commands);
339 if (rc)
340 bulk_free_list (*result);
342 return rc;
345 static size_t
346 number_length (size_t n, char **result)
348 char buf[32];
350 snprintf (buf, sizeof (buf), "%zu", n);
351 if (result)
352 *result = str_dup (buf);
354 return strlen (buf);
357 static gpg_error_t
358 recurse_bulk_build_result (struct sexp_s **sexp, char **str, size_t *size)
360 unsigned i;
361 gpg_error_t rc = 0;
363 for (i = 0; sexp[i]; i++)
365 struct sexp_s *id = NULL;
366 struct bulk_cmd_s *cur;
367 size_t len, n;
368 char *p;
369 char *idlen = NULL, *rclen = NULL, *rcstr = NULL, *resultlen = NULL;
371 if (!memcmp (sexp[i]->tag, "id", sexp[i]->taglen))
372 id = sexp[i++];
374 cur = sexp[i]->user;
375 if (!cur || !cur->ran)
376 continue;
378 len = n = *size;
379 n += number_length (id->datalen, &idlen);
380 n += id->datalen;
381 n += number_length (cur->rc, &rclen);
382 n += number_length (strlen (rclen), &rcstr);
383 n += number_length (cur->result_len, &resultlen);
384 n += cur->result_len;
385 n += 11;
387 p = xrealloc (*str, (n+1) * sizeof (char));
388 if (!p || !idlen || !rclen || !rcstr || !resultlen)
390 xfree (idlen);
391 xfree (rclen);
392 xfree (rcstr);
393 xfree (resultlen);
394 rc = GPG_ERR_ENOMEM;
395 break;
398 *size = n;
399 *str = p;
401 memcpy (&p[len], "2:id", 4);
402 len += 4;
403 memcpy (&p[len], idlen, strlen (idlen));
404 len += strlen (idlen);
405 xfree (idlen);
406 memcpy (&p[len++], ":", 1);
407 memcpy (&p[len], id->data, id->datalen);
408 len += id->datalen;
410 memcpy (&p[len], "2:rc", 4);
411 len += 4;
412 memcpy (&p[len], rcstr, strlen (rcstr));
413 len += strlen (rcstr);
414 xfree (rcstr);
415 memcpy (&p[len++], ":", 1);
416 memcpy (&p[len], rclen, strlen (rclen));
417 len += strlen (rclen);
418 xfree (rclen);
420 memcpy (&p[len], resultlen, strlen (resultlen));
421 len += strlen (resultlen);
422 xfree (resultlen);
423 memcpy (&p[len++], ":", 1);
424 if (cur->result)
425 memcpy (&p[len], cur->result, cur->result_len);
427 len += cur->result_len;
428 p[len] = 0;
430 if (sexp[i+1] && sexp[i+1]->next)
432 rc = recurse_bulk_build_result (sexp[i+1]->next, str, size);
433 if (rc)
434 break;
438 return rc;
441 /* Creates a string representing the result of a bulk command list. Syntax:
443 * (11:bulk-result 2:id<I>:<id> 2:rc<R>:<code> <D>:[<data>] [2:id...])
445 * Where <I> is the length of <id>, <R> is the length of the command result
446 * <code> and <D> is the length of the command result <data> if any.
448 gpg_error_t
449 bulk_build_result (struct sexp_s **sexp, char **result)
451 gpg_error_t rc;
452 size_t size = 0;
454 *result = str_dup ("(11:bulk-result");
455 if (!*result)
456 return GPG_ERR_ENOMEM;
458 size = strlen (*result);
459 rc = recurse_bulk_build_result (sexp, result, &size);
460 if (!rc)
462 char *p = xrealloc (*result, (size+2) * sizeof (char));
464 if (!p)
466 xfree (*result);
467 return GPG_ERR_ENOMEM;
470 p[size++] = ')';
471 p[size] = 0;
472 *result = p;
474 else
475 xfree (*result);
477 return rc;