1 /*
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2007-2015 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
30 #include "sudo_compat.h"
31 #include "sudo_debug.h"
32 #include "sudo_lbuf.h"
33
34 void
sudo_lbuf_init_v1(struct sudo_lbuf * lbuf,sudo_lbuf_output_t output,int indent,const char * continuation,int cols)35 sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output,
36 int indent, const char *continuation, int cols)
37 {
38 debug_decl(sudo_lbuf_init, SUDO_DEBUG_UTIL);
39
40 lbuf->output = output;
41 lbuf->continuation = continuation;
42 lbuf->indent = indent;
43 lbuf->cols = cols;
44 lbuf->error = 0;
45 lbuf->len = 0;
46 lbuf->size = 0;
47 lbuf->buf = NULL;
48
49 debug_return;
50 }
51
52 void
sudo_lbuf_destroy_v1(struct sudo_lbuf * lbuf)53 sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf)
54 {
55 debug_decl(sudo_lbuf_destroy, SUDO_DEBUG_UTIL);
56
57 free(lbuf->buf);
58 lbuf->error = 0;
59 lbuf->len = 0;
60 lbuf->size = 0;
61 lbuf->buf = NULL;
62
63 debug_return;
64 }
65
66 static bool
sudo_lbuf_expand(struct sudo_lbuf * lbuf,int extra)67 sudo_lbuf_expand(struct sudo_lbuf *lbuf, int extra)
68 {
69 debug_decl(sudo_lbuf_expand, SUDO_DEBUG_UTIL);
70
71 if (lbuf->len + extra + 1 >= lbuf->size) {
72 char *new_buf;
73 int new_size = lbuf->size;
74
75 do {
76 new_size += 256;
77 } while (lbuf->len + extra + 1 >= new_size);
78 if ((new_buf = realloc(lbuf->buf, new_size)) == NULL) {
79 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
80 "unable to allocate memory");
81 lbuf->error = 1;
82 debug_return_bool(false);
83 }
84 lbuf->buf = new_buf;
85 lbuf->size = new_size;
86 }
87 debug_return_bool(true);
88 }
89
90 /*
91 * Parse the format and append strings, only %s and %% escapes are supported.
92 * Any characters in set are quoted with a backslash.
93 */
94 bool
sudo_lbuf_append_quoted_v1(struct sudo_lbuf * lbuf,const char * set,const char * fmt,...)95 sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char *fmt, ...)
96 {
97 int len, saved_len = lbuf->len;
98 bool ret = false;
99 char *cp, *s;
100 va_list ap;
101 debug_decl(sudo_lbuf_append_quoted, SUDO_DEBUG_UTIL);
102
103 if (sudo_lbuf_error(lbuf))
104 debug_return_bool(false);
105
106 va_start(ap, fmt);
107 while (*fmt != '\0') {
108 if (fmt[0] == '%' && fmt[1] == 's') {
109 if ((s = va_arg(ap, char *)) == NULL)
110 s = "(NULL)";
111 while ((cp = strpbrk(s, set)) != NULL) {
112 len = (int)(cp - s);
113 if (!sudo_lbuf_expand(lbuf, len + 2))
114 goto done;
115 memcpy(lbuf->buf + lbuf->len, s, len);
116 lbuf->len += len;
117 lbuf->buf[lbuf->len++] = '\\';
118 lbuf->buf[lbuf->len++] = *cp;
119 s = cp + 1;
120 }
121 if (*s != '\0') {
122 len = strlen(s);
123 if (!sudo_lbuf_expand(lbuf, len))
124 goto done;
125 memcpy(lbuf->buf + lbuf->len, s, len);
126 lbuf->len += len;
127 }
128 fmt += 2;
129 continue;
130 }
131 if (!sudo_lbuf_expand(lbuf, 2))
132 goto done;
133 if (strchr(set, *fmt) != NULL)
134 lbuf->buf[lbuf->len++] = '\\';
135 lbuf->buf[lbuf->len++] = *fmt++;
136 }
137 ret = true;
138
139 done:
140 if (!ret)
141 lbuf->len = saved_len;
142 if (lbuf->size != 0)
143 lbuf->buf[lbuf->len] = '\0';
144 va_end(ap);
145
146 debug_return_bool(ret);
147 }
148
149 /*
150 * Parse the format and append strings, only %s and %% escapes are supported.
151 */
152 bool
sudo_lbuf_append_v1(struct sudo_lbuf * lbuf,const char * fmt,...)153 sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char *fmt, ...)
154 {
155 int len, saved_len = lbuf->len;
156 bool ret = false;
157 va_list ap;
158 char *s;
159 debug_decl(sudo_lbuf_append, SUDO_DEBUG_UTIL);
160
161 if (sudo_lbuf_error(lbuf))
162 debug_return_bool(false);
163
164 va_start(ap, fmt);
165 while (*fmt != '\0') {
166 if (fmt[0] == '%' && fmt[1] == 's') {
167 if ((s = va_arg(ap, char *)) == NULL)
168 s = "(NULL)";
169 len = strlen(s);
170 if (!sudo_lbuf_expand(lbuf, len))
171 goto done;
172 memcpy(lbuf->buf + lbuf->len, s, len);
173 lbuf->len += len;
174 fmt += 2;
175 continue;
176 }
177 if (!sudo_lbuf_expand(lbuf, 1))
178 goto done;
179 lbuf->buf[lbuf->len++] = *fmt++;
180 }
181 ret = true;
182
183 done:
184 if (!ret)
185 lbuf->len = saved_len;
186 if (lbuf->size != 0)
187 lbuf->buf[lbuf->len] = '\0';
188 va_end(ap);
189
190 debug_return_bool(ret);
191 }
192
193 /* XXX - check output function return value */
194 static void
sudo_lbuf_println(struct sudo_lbuf * lbuf,char * line,int len)195 sudo_lbuf_println(struct sudo_lbuf *lbuf, char *line, int len)
196 {
197 char *cp, save;
198 int i, have, contlen = 0;
199 int indent = lbuf->indent;
200 bool is_comment = false;
201 debug_decl(sudo_lbuf_println, SUDO_DEBUG_UTIL);
202
203 /* Comment lines don't use continuation and only indent is for "# " */
204 if (line[0] == '#' && isblank((unsigned char)line[1])) {
205 is_comment = true;
206 indent = 2;
207 }
208 if (lbuf->continuation != NULL && !is_comment)
209 contlen = strlen(lbuf->continuation);
210
211 /*
212 * Print the buffer, splitting the line as needed on a word
213 * boundary.
214 */
215 cp = line;
216 have = lbuf->cols;
217 while (cp != NULL && *cp != '\0') {
218 char *ep = NULL;
219 int need = len - (int)(cp - line);
220
221 if (need > have) {
222 have -= contlen; /* subtract for continuation char */
223 if ((ep = memrchr(cp, ' ', have)) == NULL)
224 ep = memchr(cp + have, ' ', need - have);
225 if (ep != NULL)
226 need = (int)(ep - cp);
227 }
228 if (cp != line) {
229 if (is_comment) {
230 lbuf->output("# ");
231 } else {
232 /* indent continued lines */
233 /* XXX - build up string instead? */
234 for (i = 0; i < indent; i++)
235 lbuf->output(" ");
236 }
237 }
238 /* NUL-terminate cp for the output function and restore afterwards */
239 save = cp[need];
240 cp[need] = '\0';
241 lbuf->output(cp);
242 cp[need] = save;
243 cp = ep;
244
245 /*
246 * If there is more to print, reset have, incremement cp past
247 * the whitespace, and print a line continuaton char if needed.
248 */
249 if (cp != NULL) {
250 have = lbuf->cols - indent;
251 ep = line + len;
252 while (cp < ep && isblank((unsigned char)*cp)) {
253 cp++;
254 }
255 if (contlen)
256 lbuf->output(lbuf->continuation);
257 }
258 lbuf->output("\n");
259 }
260
261 debug_return;
262 }
263
264 /*
265 * Print the buffer with word wrap based on the tty width.
266 * The lbuf is reset on return.
267 * XXX - check output function return value
268 */
269 void
sudo_lbuf_print_v1(struct sudo_lbuf * lbuf)270 sudo_lbuf_print_v1(struct sudo_lbuf *lbuf)
271 {
272 char *cp, *ep;
273 int len;
274 debug_decl(sudo_lbuf_print, SUDO_DEBUG_UTIL);
275
276 if (lbuf->buf == NULL || lbuf->len == 0)
277 goto done;
278
279 /* For very small widths just give up... */
280 len = lbuf->continuation ? strlen(lbuf->continuation) : 0;
281 if (lbuf->cols <= lbuf->indent + len + 20) {
282 if (lbuf->len > 0) {
283 lbuf->buf[lbuf->len] = '\0';
284 lbuf->output(lbuf->buf);
285 if (lbuf->buf[lbuf->len - 1] != '\n')
286 lbuf->output("\n");
287 }
288 goto done;
289 }
290
291 /* Print each line in the buffer */
292 for (cp = lbuf->buf; cp != NULL && *cp != '\0'; ) {
293 if (*cp == '\n') {
294 lbuf->output("\n");
295 cp++;
296 } else {
297 len = lbuf->len - (cp - lbuf->buf);
298 if ((ep = memchr(cp, '\n', len)) != NULL)
299 len = (int)(ep - cp);
300 if (len)
301 sudo_lbuf_println(lbuf, cp, len);
302 cp = ep ? ep + 1 : NULL;
303 }
304 }
305
306 done:
307 lbuf->len = 0; /* reset the buffer for re-use. */
308 lbuf->error = 0;
309
310 debug_return;
311 }
312
313 bool
sudo_lbuf_error_v1(struct sudo_lbuf * lbuf)314 sudo_lbuf_error_v1(struct sudo_lbuf *lbuf)
315 {
316 if (lbuf != NULL && lbuf->error != 0)
317 return true;
318 return false;
319 }
320
321 void
sudo_lbuf_clearerr_v1(struct sudo_lbuf * lbuf)322 sudo_lbuf_clearerr_v1(struct sudo_lbuf *lbuf)
323 {
324 if (lbuf != NULL)
325 lbuf->error = 0;
326 }
327