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