1 /*
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2013-2020 Todd C. Miller <Todd.Miller@sudo.ws>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22 */
23
24 #include <config.h>
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #ifdef HAVE_STDBOOL_H
29 # include <stdbool.h>
30 #else
31 # include "compat/stdbool.h"
32 #endif /* HAVE_STDBOOL_H */
33 #include <string.h>
34
35 #include "sudo_compat.h"
36 #include "sudo_debug.h"
37 #include "sudo_fatal.h"
38 #include "sudo_gettext.h"
39 #include "sudo_json.h"
40 #include "sudo_util.h"
41
42 /*
43 * Double the size of the json buffer.
44 * Returns true on success, false if out of memory.
45 */
46 static bool
json_expand_buf(struct json_container * json)47 json_expand_buf(struct json_container *json)
48 {
49 char *newbuf;
50 debug_decl(json_expand_buf, SUDO_DEBUG_UTIL);
51
52 if ((newbuf = reallocarray(json->buf, 2, json->bufsize)) == NULL) {
53 if (json->memfatal) {
54 sudo_fatalx(U_("%s: %s"),
55 __func__, U_("unable to allocate memory"));
56 }
57 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
58 "%s: %s", __func__, "unable to allocate memory");
59 debug_return_bool(false);
60 }
61 json->buf = newbuf;
62 json->bufsize *= 2;
63
64 debug_return_bool(true);
65 }
66
67 /*
68 * Start a new line and indent unless formatting as minimal JSON.
69 * Append "indent" number of blank characters.
70 */
71 static bool
json_new_line(struct json_container * json)72 json_new_line(struct json_container *json)
73 {
74 int indent = json->indent_level;
75 debug_decl(json_new_line, SUDO_DEBUG_UTIL);
76
77 /* No non-essential white space in minimal mode. */
78 if (json->minimal)
79 debug_return_bool(true);
80
81 while (json->buflen + 1 + indent >= json->bufsize) {
82 if (!json_expand_buf(json))
83 debug_return_bool(false);
84 }
85 json->buf[json->buflen++] = '\n';
86 while (indent--) {
87 json->buf[json->buflen++] = ' ';
88 }
89 json->buf[json->buflen] = '\0';
90
91 debug_return_bool(true);
92 }
93
94 /*
95 * Append a string to the JSON buffer, expanding as needed.
96 * Does not perform any quoting.
97 */
98 static bool
json_append_buf(struct json_container * json,const char * str)99 json_append_buf(struct json_container *json, const char *str)
100 {
101 size_t len;
102 debug_decl(json_append_buf, SUDO_DEBUG_UTIL);
103
104 len = strlen(str);
105 while (json->buflen + len >= json->bufsize) {
106 if (!json_expand_buf(json))
107 debug_return_bool(false);
108 }
109
110 memcpy(json->buf + json->buflen, str, len);
111 json->buflen += len;
112 json->buf[json->buflen] = '\0';
113
114 debug_return_bool(true);
115 }
116
117 /*
118 * Append a quoted JSON string, escaping special chars and expanding as needed.
119 * Does not support unicode escapes.
120 */
121 static bool
json_append_string(struct json_container * json,const char * str)122 json_append_string(struct json_container *json, const char *str)
123 {
124 char ch;
125 debug_decl(json_append_string, SUDO_DEBUG_UTIL);
126
127 if (!json_append_buf(json, "\""))
128 debug_return_bool(false);
129 while ((ch = *str++) != '\0') {
130 char buf[3], *cp = buf;
131
132 switch (ch) {
133 case '"':
134 case '\\':
135 *cp++ = '\\';
136 break;
137 case '\b':
138 *cp++ = '\\';
139 ch = 'b';
140 break;
141 case '\f':
142 *cp++ = '\\';
143 ch = 'f';
144 break;
145 case '\n':
146 *cp++ = '\\';
147 ch = 'n';
148 break;
149 case '\r':
150 *cp++ = '\\';
151 ch = 'r';
152 break;
153 case '\t':
154 *cp++ = '\\';
155 ch = 't';
156 break;
157 }
158 *cp++ = ch;
159 *cp++ = '\0';
160 if (!json_append_buf(json, buf))
161 debug_return_bool(false);
162 }
163 if (!json_append_buf(json, "\""))
164 debug_return_bool(false);
165
166 debug_return_bool(true);
167 }
168
169 bool
sudo_json_init_v1(struct json_container * json,int indent,bool minimal,bool memfatal)170 sudo_json_init_v1(struct json_container *json, int indent, bool minimal,
171 bool memfatal)
172 {
173 debug_decl(sudo_json_init, SUDO_DEBUG_UTIL);
174
175 memset(json, 0, sizeof(*json));
176 json->indent_level = indent;
177 json->indent_increment = indent;
178 json->minimal = minimal;
179 json->memfatal = memfatal;
180 json->buf = malloc(64 * 1024);
181 if (json->buf == NULL) {
182 if (json->memfatal) {
183 sudo_fatalx(U_("%s: %s"),
184 __func__, U_("unable to allocate memory"));
185 }
186 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
187 "%s: %s", __func__, "unable to allocate memory");
188 debug_return_bool(false);
189 }
190 *json->buf = '\0';
191 json->bufsize = 64 * 1024;
192
193 debug_return_bool(true);
194 }
195
196 void
sudo_json_free_v1(struct json_container * json)197 sudo_json_free_v1(struct json_container *json)
198 {
199 debug_decl(sudo_json_free, SUDO_DEBUG_UTIL);
200
201 free(json->buf);
202 memset(json, 0, sizeof(*json));
203
204 debug_return;
205 }
206
207 bool
sudo_json_open_object_v1(struct json_container * json,const char * name)208 sudo_json_open_object_v1(struct json_container *json, const char *name)
209 {
210 debug_decl(sudo_json_open_object, SUDO_DEBUG_UTIL);
211
212 /* Add comma if we are continuing an object/array. */
213 if (json->need_comma) {
214 if (!json_append_buf(json, ","))
215 debug_return_bool(false);
216 }
217 if (!json_new_line(json))
218 debug_return_bool(false);
219
220 if (name != NULL) {
221 json_append_string(json, name);
222 if (!json_append_buf(json, json->minimal ? ":{" : ": {"))
223 debug_return_bool(false);
224 } else {
225 if (!json_append_buf(json, "{"))
226 debug_return_bool(false);
227 }
228
229 json->indent_level += json->indent_increment;
230 json->need_comma = false;
231
232 debug_return_bool(true);
233 }
234
235 bool
sudo_json_close_object_v1(struct json_container * json)236 sudo_json_close_object_v1(struct json_container *json)
237 {
238 debug_decl(sudo_json_close_object, SUDO_DEBUG_UTIL);
239
240 if (!json->minimal) {
241 json->indent_level -= json->indent_increment;
242 if (!json_new_line(json))
243 debug_return_bool(false);
244 }
245 if (!json_append_buf(json, "}"))
246 debug_return_bool(false);
247
248 debug_return_bool(true);
249 }
250
251 bool
sudo_json_open_array_v1(struct json_container * json,const char * name)252 sudo_json_open_array_v1(struct json_container *json, const char *name)
253 {
254 debug_decl(sudo_json_open_array, SUDO_DEBUG_UTIL);
255
256 /* Add comma if we are continuing an object/array. */
257 if (json->need_comma) {
258 if (!json_append_buf(json, ","))
259 debug_return_bool(false);
260 }
261 if (!json_new_line(json))
262 debug_return_bool(false);
263
264 if (name != NULL) {
265 json_append_string(json, name);
266 if (!json_append_buf(json, json->minimal ? ":[" : ": ["))
267 debug_return_bool(false);
268 } else {
269 if (!json_append_buf(json, "["))
270 debug_return_bool(false);
271 }
272
273 json->indent_level += json->indent_increment;
274 json->need_comma = false;
275
276 debug_return_bool(true);
277 }
278
279 bool
sudo_json_close_array_v1(struct json_container * json)280 sudo_json_close_array_v1(struct json_container *json)
281 {
282 debug_decl(sudo_json_close_array, SUDO_DEBUG_UTIL);
283
284 if (!json->minimal) {
285 json->indent_level -= json->indent_increment;
286 if (!json_new_line(json))
287 debug_return_bool(false);
288 }
289 if (!json_append_buf(json, "]"))
290 debug_return_bool(false);
291
292 debug_return_bool(true);
293 }
294
295 static bool
sudo_json_add_value_int(struct json_container * json,const char * name,struct json_value * value,bool as_object)296 sudo_json_add_value_int(struct json_container *json, const char *name,
297 struct json_value *value, bool as_object)
298 {
299 char numbuf[(((sizeof(long long) * 8) + 2) / 3) + 2];
300 debug_decl(sudo_json_add_value, SUDO_DEBUG_UTIL);
301
302 /* Add comma if we are continuing an object/array. */
303 if (json->need_comma) {
304 if (!json_append_buf(json, ","))
305 debug_return_bool(false);
306 }
307 if (!json_new_line(json))
308 debug_return_bool(false);
309 json->need_comma = true;
310
311 if (as_object) {
312 if (!json_append_buf(json, json->minimal ? "{" : "{ "))
313 debug_return_bool(false);
314 }
315
316 /* name */
317 if (name != NULL) {
318 if (!json_append_string(json, name))
319 debug_return_bool(false);
320 if (!json_append_buf(json, json->minimal ? ":" : ": "))
321 debug_return_bool(false);
322 }
323
324 /* value */
325 switch (value->type) {
326 case JSON_STRING:
327 if (!json_append_string(json, value->u.string))
328 debug_return_bool(false);
329 break;
330 case JSON_ID:
331 snprintf(numbuf, sizeof(numbuf), "%u", (unsigned int)value->u.id);
332 if (!json_append_buf(json, numbuf))
333 debug_return_bool(false);
334 break;
335 case JSON_NUMBER:
336 snprintf(numbuf, sizeof(numbuf), "%lld", value->u.number);
337 if (!json_append_buf(json, numbuf))
338 debug_return_bool(false);
339 break;
340 case JSON_NULL:
341 if (!json_append_buf(json, "null"))
342 debug_return_bool(false);
343 break;
344 case JSON_BOOL:
345 if (!json_append_buf(json, value->u.boolean ? "true" : "false"))
346 debug_return_bool(false);
347 break;
348 case JSON_ARRAY:
349 sudo_fatalx("internal error: can't print JSON_ARRAY");
350 break;
351 case JSON_OBJECT:
352 sudo_fatalx("internal error: can't print JSON_OBJECT");
353 break;
354 }
355
356 if (as_object) {
357 if (!json_append_buf(json, json->minimal ? "}" : " }"))
358 debug_return_bool(false);
359 }
360
361 debug_return_bool(true);
362 }
363
364 bool
sudo_json_add_value_v1(struct json_container * json,const char * name,struct json_value * value)365 sudo_json_add_value_v1(struct json_container *json, const char *name,
366 struct json_value *value)
367 {
368 return sudo_json_add_value_int(json, name, value, false);
369 }
370
371 bool
sudo_json_add_value_as_object_v1(struct json_container * json,const char * name,struct json_value * value)372 sudo_json_add_value_as_object_v1(struct json_container *json, const char *name,
373 struct json_value *value)
374 {
375 return sudo_json_add_value_int(json, name, value, true);
376 }
377
378 char *
sudo_json_get_buf_v1(struct json_container * json)379 sudo_json_get_buf_v1(struct json_container *json)
380 {
381 return json->buf;
382 }
383
384 unsigned int
sudo_json_get_len_v1(struct json_container * json)385 sudo_json_get_len_v1(struct json_container *json)
386 {
387 return json->buflen;
388 }
389