Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/lib/util/lbuf.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2007-2015, 2023 Todd C. Miller <[email protected]>
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
#include <config.h>
20
21
#include <ctype.h>
22
#include <errno.h>
23
#include <limits.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include <sudo_compat.h>
28
#include <sudo_debug.h>
29
#include <sudo_lbuf.h>
30
#include <sudo_util.h>
31
32
void
33
sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output,
34
unsigned int indent, const char *continuation, int cols)
35
{
36
debug_decl(sudo_lbuf_init, SUDO_DEBUG_UTIL);
37
38
if (cols < 0)
39
cols = 0;
40
41
lbuf->output = output;
42
lbuf->continuation = continuation;
43
lbuf->indent = indent;
44
lbuf->cols = (unsigned short)cols;
45
lbuf->error = 0;
46
lbuf->len = 0;
47
lbuf->size = 0;
48
lbuf->buf = NULL;
49
50
debug_return;
51
}
52
53
void
54
sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf)
55
{
56
debug_decl(sudo_lbuf_destroy, SUDO_DEBUG_UTIL);
57
58
free(lbuf->buf);
59
lbuf->error = 0;
60
lbuf->len = 0;
61
lbuf->size = 0;
62
lbuf->buf = NULL;
63
64
debug_return;
65
}
66
67
static bool
68
sudo_lbuf_expand(struct sudo_lbuf *lbuf, unsigned int extra)
69
{
70
debug_decl(sudo_lbuf_expand, SUDO_DEBUG_UTIL);
71
72
if (lbuf->len + extra + 1 <= lbuf->len) {
73
errno = ENOMEM;
74
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
75
"integer overflow updating lbuf->len");
76
lbuf->error = 1;
77
debug_return_bool(false);
78
}
79
80
if (lbuf->len + extra + 1 > lbuf->size) {
81
const size_t size = lbuf->len + extra + 1;
82
size_t new_size = sudo_pow2_roundup(size);
83
char *new_buf;
84
85
if (new_size > UINT_MAX || new_size < lbuf->size) {
86
errno = ENOMEM;
87
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
88
"integer overflow updating lbuf->size");
89
lbuf->error = 1;
90
debug_return_bool(false);
91
}
92
if (new_size < 1024)
93
new_size = 1024;
94
if ((new_buf = realloc(lbuf->buf, new_size)) == NULL) {
95
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
96
"unable to allocate memory");
97
lbuf->error = 1;
98
debug_return_bool(false);
99
}
100
lbuf->buf = new_buf;
101
lbuf->size = (unsigned int)new_size;
102
}
103
debug_return_bool(true);
104
}
105
106
/*
107
* Escape a character in octal form (#0n) and store it as a string
108
* in buf, which must have at least 6 bytes available.
109
* Returns the length of buf, not counting the terminating NUL byte.
110
*/
111
static unsigned int
112
escape(char ch, char *buf)
113
{
114
unsigned char uch = (unsigned char)ch;
115
const unsigned int len = uch < 0100 ? (uch < 010 ? 3 : 4) : 5;
116
117
/* Work backwards from the least significant digit to most significant. */
118
switch (len) {
119
case 5:
120
buf[4] = (uch & 7) + '0';
121
uch >>= 3;
122
FALLTHROUGH;
123
case 4:
124
buf[3] = (uch & 7) + '0';
125
uch >>= 3;
126
FALLTHROUGH;
127
case 3:
128
buf[2] = (uch & 7) + '0';
129
buf[1] = '0';
130
buf[0] = '#';
131
break;
132
}
133
buf[len] = '\0';
134
135
return len;
136
}
137
138
/*
139
* Parse the format and append strings, only %s and %% escapes are supported.
140
* Any non-printable characters are escaped in octal as #0nn.
141
*/
142
bool
143
sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char * restrict fmt, ...)
144
{
145
unsigned int saved_len = lbuf->len;
146
bool ret = false;
147
const char *s;
148
va_list ap;
149
debug_decl(sudo_lbuf_append_esc, SUDO_DEBUG_UTIL);
150
151
if (sudo_lbuf_error(lbuf))
152
debug_return_bool(false);
153
154
#define should_escape(ch) \
155
((ISSET(flags, LBUF_ESC_CNTRL) && iscntrl((unsigned char)ch)) || \
156
(ISSET(flags, LBUF_ESC_BLANK) && isblank((unsigned char)ch)))
157
#define should_quote(ch) \
158
(ISSET(flags, LBUF_ESC_QUOTE) && (ch == '\'' || ch == '\\'))
159
160
va_start(ap, fmt);
161
while (*fmt != '\0') {
162
if (fmt[0] == '%' && fmt[1] == 's') {
163
if ((s = va_arg(ap, char *)) == NULL)
164
s = "(NULL)";
165
while (*s != '\0') {
166
if (should_escape(*s)) {
167
if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
168
goto done;
169
lbuf->len += escape(*s++, lbuf->buf + lbuf->len);
170
continue;
171
}
172
if (should_quote(*s)) {
173
if (!sudo_lbuf_expand(lbuf, 2))
174
goto done;
175
lbuf->buf[lbuf->len++] = '\\';
176
lbuf->buf[lbuf->len++] = *s++;
177
continue;
178
}
179
if (!sudo_lbuf_expand(lbuf, 1))
180
goto done;
181
lbuf->buf[lbuf->len++] = *s++;
182
}
183
fmt += 2;
184
continue;
185
}
186
if (should_escape(*fmt)) {
187
if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
188
goto done;
189
if (*fmt == '\'') {
190
lbuf->buf[lbuf->len++] = '\\';
191
lbuf->buf[lbuf->len++] = *fmt++;
192
} else {
193
lbuf->len += escape(*fmt++, lbuf->buf + lbuf->len);
194
}
195
continue;
196
}
197
if (!sudo_lbuf_expand(lbuf, 1))
198
goto done;
199
lbuf->buf[lbuf->len++] = *fmt++;
200
}
201
ret = true;
202
203
done:
204
if (!ret)
205
lbuf->len = saved_len;
206
if (lbuf->size != 0)
207
lbuf->buf[lbuf->len] = '\0';
208
va_end(ap);
209
210
debug_return_bool(ret);
211
}
212
213
/*
214
* Parse the format and append strings, only %s and %% escapes are supported.
215
* Any characters in set are quoted with a backslash.
216
*/
217
bool
218
sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char * restrict fmt, ...)
219
{
220
unsigned int saved_len = lbuf->len;
221
bool ret = false;
222
const char *cp, *s;
223
va_list ap;
224
unsigned int len;
225
debug_decl(sudo_lbuf_append_quoted, SUDO_DEBUG_UTIL);
226
227
if (sudo_lbuf_error(lbuf))
228
debug_return_bool(false);
229
230
va_start(ap, fmt);
231
while (*fmt != '\0') {
232
if (fmt[0] == '%' && fmt[1] == 's') {
233
if ((s = va_arg(ap, char *)) == NULL)
234
s = "(NULL)";
235
while ((cp = strpbrk(s, set)) != NULL) {
236
len = (unsigned int)(cp - s);
237
if (!sudo_lbuf_expand(lbuf, len + 2))
238
goto done;
239
memcpy(lbuf->buf + lbuf->len, s, len);
240
lbuf->len += len;
241
lbuf->buf[lbuf->len++] = '\\';
242
lbuf->buf[lbuf->len++] = *cp;
243
s = cp + 1;
244
}
245
if (*s != '\0') {
246
len = (unsigned int)strlen(s);
247
if (!sudo_lbuf_expand(lbuf, len))
248
goto done;
249
memcpy(lbuf->buf + lbuf->len, s, len);
250
lbuf->len += len;
251
}
252
fmt += 2;
253
continue;
254
}
255
if (!sudo_lbuf_expand(lbuf, 2))
256
goto done;
257
if (strchr(set, *fmt) != NULL)
258
lbuf->buf[lbuf->len++] = '\\';
259
lbuf->buf[lbuf->len++] = *fmt++;
260
}
261
ret = true;
262
263
done:
264
if (!ret)
265
lbuf->len = saved_len;
266
if (lbuf->size != 0)
267
lbuf->buf[lbuf->len] = '\0';
268
va_end(ap);
269
270
debug_return_bool(ret);
271
}
272
273
/*
274
* Parse the format and append strings, only %s, %n$s and %% escapes are supported.
275
*/
276
bool
277
sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char * restrict fmt, ...)
278
{
279
unsigned int saved_len = lbuf->len;
280
bool ret = false;
281
va_list ap;
282
const char *s;
283
unsigned int len;
284
debug_decl(sudo_lbuf_append, SUDO_DEBUG_UTIL);
285
286
if (sudo_lbuf_error(lbuf))
287
debug_return_bool(false);
288
289
va_start(ap, fmt);
290
while (*fmt != '\0') {
291
if (fmt[0] == '%' && isdigit((unsigned char)fmt[1])) {
292
const char *num_start = fmt + 1;
293
const char *num_end = num_start;
294
int arg_num;
295
/* Find the end of the numeric part */
296
while (isdigit((unsigned char)*num_end))
297
num_end++;
298
if (num_end[0] == '$' && num_end[1] == 's' && num_end > num_start) {
299
/* Convert the numeric part to an integer */
300
char numbuf[STRLEN_MAX_SIGNED(int) + 1];
301
len = (unsigned int)(num_end - num_start);
302
if (len >= sizeof(numbuf)) {
303
errno = EINVAL;
304
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
305
"integer overflow parsing $n");
306
lbuf->error = 1;
307
goto done;
308
}
309
memcpy(numbuf, num_start, len);
310
numbuf[len] = '\0';
311
arg_num = atoi(numbuf);
312
if (arg_num > 0) {
313
va_list arg_copy;
314
va_copy(arg_copy, ap);
315
for (int i = 1; i < arg_num; i++) {
316
(void)va_arg(arg_copy, char *);
317
}
318
if ((s = va_arg(arg_copy, char *)) == NULL)
319
s = "(NULL)";
320
len = (unsigned int)strlen(s);
321
if (!sudo_lbuf_expand(lbuf, len)) {
322
va_end(arg_copy);
323
goto done;
324
}
325
memcpy(lbuf->buf + lbuf->len, s, len);
326
lbuf->len += len;
327
fmt = num_end + 2;
328
va_end(arg_copy);
329
continue;
330
}
331
}
332
}
333
if (fmt[0] == '%' && fmt[1] == 's') {
334
if ((s = va_arg(ap, char *)) == NULL)
335
s = "(NULL)";
336
len = (unsigned int)strlen(s);
337
if (!sudo_lbuf_expand(lbuf, len))
338
goto done;
339
memcpy(lbuf->buf + lbuf->len, s, len);
340
lbuf->len += len;
341
fmt += 2;
342
continue;
343
}
344
if (!sudo_lbuf_expand(lbuf, 1))
345
goto done;
346
lbuf->buf[lbuf->len++] = *fmt++;
347
}
348
ret = true;
349
350
done:
351
if (!ret)
352
lbuf->len = saved_len;
353
if (lbuf->size != 0)
354
lbuf->buf[lbuf->len] = '\0';
355
va_end(ap);
356
357
debug_return_bool(ret);
358
}
359
360
/* XXX - check output function return value */
361
static void
362
sudo_lbuf_println(struct sudo_lbuf *lbuf, char *line, size_t len)
363
{
364
char *cp, save;
365
size_t i, have, contlen = 0;
366
unsigned int indent = lbuf->indent;
367
bool is_comment = false;
368
debug_decl(sudo_lbuf_println, SUDO_DEBUG_UTIL);
369
370
/* Comment lines don't use continuation and only indent is for "# " */
371
if (line[0] == '#' && isblank((unsigned char)line[1])) {
372
is_comment = true;
373
indent = 2;
374
}
375
if (lbuf->continuation != NULL && !is_comment)
376
contlen = strlen(lbuf->continuation);
377
378
/*
379
* Print the buffer, splitting the line as needed on a word
380
* boundary.
381
*/
382
cp = line;
383
have = lbuf->cols;
384
while (cp != NULL && *cp != '\0') {
385
char *ep = NULL;
386
size_t need = len - (size_t)(cp - line);
387
388
if (need > have) {
389
have -= contlen; /* subtract for continuation char */
390
if ((ep = memrchr(cp, ' ', have)) == NULL)
391
ep = memchr(cp + have, ' ', need - have);
392
if (ep != NULL)
393
need = (size_t)(ep - cp);
394
}
395
if (cp != line) {
396
if (is_comment) {
397
lbuf->output("# ");
398
} else {
399
/* indent continued lines */
400
/* XXX - build up string instead? */
401
for (i = 0; i < indent; i++)
402
lbuf->output(" ");
403
}
404
}
405
/* NUL-terminate cp for the output function and restore afterwards */
406
save = cp[need];
407
cp[need] = '\0';
408
lbuf->output(cp);
409
cp[need] = save;
410
cp = ep;
411
412
/*
413
* If there is more to print, reset have, increment cp past
414
* the whitespace, and print a line continuation char if needed.
415
*/
416
if (cp != NULL) {
417
have = lbuf->cols - indent;
418
ep = line + len;
419
while (cp < ep && isblank((unsigned char)*cp)) {
420
cp++;
421
}
422
if (contlen)
423
lbuf->output(lbuf->continuation);
424
}
425
lbuf->output("\n");
426
}
427
428
debug_return;
429
}
430
431
/*
432
* Print the buffer with word wrap based on the tty width.
433
* The lbuf is reset on return.
434
* XXX - check output function return value
435
*/
436
void
437
sudo_lbuf_print_v1(struct sudo_lbuf *lbuf)
438
{
439
char *cp, *ep;
440
size_t len;
441
debug_decl(sudo_lbuf_print, SUDO_DEBUG_UTIL);
442
443
if (lbuf->buf == NULL || lbuf->len == 0)
444
goto done;
445
446
/* For very small widths just give up... */
447
len = lbuf->continuation ? strlen(lbuf->continuation) : 0;
448
if (lbuf->cols <= lbuf->indent + len + 20) {
449
lbuf->buf[lbuf->len] = '\0';
450
lbuf->output(lbuf->buf);
451
if (lbuf->buf[lbuf->len - 1] != '\n')
452
lbuf->output("\n");
453
goto done;
454
}
455
456
/* Print each line in the buffer */
457
for (cp = lbuf->buf; cp != NULL && *cp != '\0'; ) {
458
if (*cp == '\n') {
459
lbuf->output("\n");
460
cp++;
461
} else {
462
len = lbuf->len - (size_t)(cp - lbuf->buf);
463
if ((ep = memchr(cp, '\n', len)) != NULL)
464
len = (size_t)(ep - cp);
465
if (len)
466
sudo_lbuf_println(lbuf, cp, len);
467
cp = ep ? ep + 1 : NULL;
468
}
469
}
470
471
done:
472
lbuf->len = 0; /* reset the buffer for reuse. */
473
lbuf->error = 0;
474
475
debug_return;
476
}
477
478
bool
479
sudo_lbuf_error_v1(struct sudo_lbuf *lbuf)
480
{
481
if (lbuf != NULL && lbuf->error != 0)
482
return true;
483
return false;
484
}
485
486
void
487
sudo_lbuf_clearerr_v1(struct sudo_lbuf *lbuf)
488
{
489
if (lbuf != NULL)
490
lbuf->error = 0;
491
}
492
493