Coverage Report

Created: 2023-06-07 06:46

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