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