xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/vbuf_print.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
1 /*	$NetBSD: vbuf_print.c,v 1.4 2022/10/08 16:12:50 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	vbuf_print 3
6 /* SUMMARY
7 /*	formatted print to generic buffer
8 /* SYNOPSIS
9 /*	#include <stdarg.h>
10 /*	#include <vbuf_print.h>
11 /*
12 /*	VBUF	*vbuf_print(bp, format, ap)
13 /*	VBUF	*bp;
14 /*	const char *format;
15 /*	va_list	ap;
16 /* DESCRIPTION
17 /*	vbuf_print() appends data to the named buffer according to its
18 /*	\fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e,
19 /*	f and g format types, the l modifier, field width and precision,
20 /*	sign, and padding with zeros or spaces.
21 /*
22 /*	In addition, vbuf_print() recognizes the %m format specifier
23 /*	and expands it to the error message corresponding to the current
24 /*	value of the global \fIerrno\fR variable.
25 /* REENTRANCY
26 /* .ad
27 /* .fi
28 /*	vbuf_print() allocates a static buffer. After completion
29 /*	of the first vbuf_print() call, this buffer is safe for
30 /*	reentrant vbuf_print() calls by (asynchronous) terminating
31 /*	signal handlers or by (synchronous) terminating error
32 /*	handlers. vbuf_print() initialization typically happens
33 /*	upon the first formatted output to a VSTRING or VSTREAM.
34 /*
35 /*	However, it is up to the caller to ensure that the destination
36 /*	VSTREAM or VSTRING buffer is protected against reentrant usage.
37 /* LICENSE
38 /* .ad
39 /* .fi
40 /*	The Secure Mailer license must be distributed with this software.
41 /* AUTHOR(S)
42 /*	Wietse Venema
43 /*	IBM T.J. Watson Research
44 /*	P.O. Box 704
45 /*	Yorktown Heights, NY 10598, USA
46 /*
47 /*	Wietse Venema
48 /*	Google, Inc.
49 /*	111 8th Avenue
50 /*	New York, NY 10011, USA
51 /*--*/
52 
53 /* System library. */
54 
55 #include "sys_defs.h"
56 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
57 #include <stdarg.h>
58 #include <string.h>
59 #include <ctype.h>
60 #include <stdlib.h>			/* 44bsd stdarg.h uses abort() */
61 #include <stdio.h>			/* sprintf() prototype */
62 #include <float.h>			/* range of doubles */
63 #include <errno.h>
64 #include <limits.h>			/* CHAR_BIT, INT_MAX */
65 
66 /* Application-specific. */
67 
68 #include "msg.h"
69 #include "mymalloc.h"
70 #include "vbuf.h"
71 #include "vstring.h"
72 #include "vbuf_print.h"
73 
74  /*
75   * What we need here is a *sprintf() routine that can ask for more room (as
76   * in 4.4 BSD). However, that functionality is not widely available, and I
77   * have no plans to maintain a complete 4.4 BSD *sprintf() alternative.
78   *
79   * Postfix vbuf_print() was implemented when many mainstream systems had no
80   * usable snprintf() implementation (usable means: return the length,
81   * excluding terminator, that the output would have if the buffer were large
82   * enough). For example, GLIBC before 2.1 (1999) snprintf() did not
83   * distinguish between formatting error and buffer size error, while SUN had
84   * no snprintf() implementation before Solaris 2.6 (1997).
85   *
86   * For the above reasons, vbuf_print() was implemented with sprintf() and a
87   * generously-sized output buffer. Current vbuf_print() implementations use
88   * snprintf(), and report an error if the output does not fit (in that case,
89   * the old sprintf()-based implementation would have had a buffer overflow
90   * vulnerability). The old implementation is still available for building
91   * Postfix on ancient systems.
92   *
93   * Guessing the output size of a string (%s) conversion is not hard. The
94   * problem is with numerical results. Instead of making an accurate guess we
95   * take a wide margin when reserving space.  The INT_SPACE margin should be
96   * large enough to hold the result from any (octal, hex, decimal) integer
97   * conversion that has no explicit width or precision specifiers. With
98   * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP
99   * just to be sure.
100   */
101 #define INT_SPACE	((CHAR_BIT * sizeof(long)) / 2)
102 #define DBL_SPACE	((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP)
103 #define PTR_SPACE	((CHAR_BIT * sizeof(char *)) / 2)
104 
105  /*
106   * Helper macros... Note that there is no need to check the result from
107   * VSTRING_SPACE() because that always succeeds or never returns.
108   */
109 #ifndef NO_SNPRINTF
110 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
111 	ssize_t _ret; \
112 	if (VBUF_SPACE((bp), (sz)) != 0) \
113 	    return (bp); \
114 	_ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
115 	if (_ret < 0) \
116 	    msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \
117 	if (_ret >= (bp)->cnt) \
118 	    msg_panic("%s: output for '%s' exceeds space %ld", \
119 		      myname, mystrdup(fmt), (long) (bp)->cnt); \
120 	VBUF_SKIP(bp); \
121     } while (0)
122 #else
123 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
124 	if (VBUF_SPACE((bp), (sz)) != 0) \
125 	    return (bp); \
126 	sprintf((char *) (bp)->ptr, (fmt), (arg)); \
127 	VBUF_SKIP(bp); \
128     } while (0)
129 #endif
130 
131 #define VBUF_SKIP(bp) do { \
132 	while ((bp)->cnt > 0 && *(bp)->ptr) \
133 	    (bp)->ptr++, (bp)->cnt--; \
134     } while (0)
135 
136 #define VSTRING_ADDNUM(vp, n) do { \
137 	VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
138     } while (0)
139 
140 #define VBUF_STRCAT(bp, s) do { \
141 	unsigned char *_cp = (unsigned char *) (s); \
142 	int _ch; \
143 	while ((_ch = *_cp++) != 0) \
144 	    VBUF_PUT((bp), _ch); \
145     } while (0)
146 
147 /* vbuf_print - format string, vsprintf-like interface */
148 
vbuf_print(VBUF * bp,const char * format,va_list ap)149 VBUF   *vbuf_print(VBUF *bp, const char *format, va_list ap)
150 {
151     const char *myname = "vbuf_print";
152     static VSTRING *fmt;		/* format specifier */
153     unsigned char *cp;
154     int     width;			/* width and numerical precision */
155     int     prec;			/* are signed for overflow defense */
156     unsigned long_flag;			/* long or plain integer */
157     int     ch;
158     char   *s;
159     int     saved_errno = errno;	/* VBUF_SPACE() may clobber it */
160 
161     /*
162      * Assume that format strings are short.
163      */
164     if (fmt == 0)
165 	fmt = vstring_alloc(INT_SPACE);
166 
167     /*
168      * Iterate over characters in the format string, picking up arguments
169      * when format specifiers are found.
170      */
171     for (cp = (unsigned char *) format; *cp; cp++) {
172 	if (*cp != '%') {
173 	    VBUF_PUT(bp, *cp);			/* ordinary character */
174 	} else if (cp[1] == '%') {
175 	    VBUF_PUT(bp, *cp++);		/* %% becomes % */
176 	} else {
177 
178 	    /*
179 	     * Handle format specifiers one at a time, since we can only deal
180 	     * with arguments one at a time. Try to determine the end of the
181 	     * format specifier. We do not attempt to fully parse format
182 	     * strings, since we are ging to let sprintf() do the hard work.
183 	     * In regular expression notation, we recognize:
184 	     *
185 	     * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z]
186 	     *
187 	     * which includes some combinations that do not make sense. Garbage
188 	     * in, garbage out.
189 	     */
190 	    VSTRING_RESET(fmt);			/* clear format string */
191 	    VSTRING_ADDCH(fmt, *cp++);
192 	    if (*cp == '-')			/* left-adjusted field? */
193 		VSTRING_ADDCH(fmt, *cp++);
194 	    if (*cp == '+')			/* signed field? */
195 		VSTRING_ADDCH(fmt, *cp++);
196 	    if (*cp == '0')			/* zero-padded field? */
197 		VSTRING_ADDCH(fmt, *cp++);
198 	    if (*cp == '*') {			/* dynamic field width */
199 		width = va_arg(ap, int);
200 		if (width < 0) {
201 		    msg_warn("%s: bad width %d in %.50s",
202 			     myname, width, format);
203 		    width = 0;
204 		} else
205 		    VSTRING_ADDNUM(fmt, width);
206 		cp++;
207 	    } else {				/* hard-coded field width */
208 		for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
209 		    int     digit = ch - '0';
210 
211 		    if (width > INT_MAX / 10
212 			|| (width *= 10) > INT_MAX - digit)
213 			msg_panic("%s: bad width %d... in %.50s",
214 				  myname, width, format);
215 		    width += digit;
216 		    VSTRING_ADDCH(fmt, ch);
217 		}
218 	    }
219 	    if (*cp == '.') {			/* width/precision separator */
220 		VSTRING_ADDCH(fmt, *cp++);
221 		if (*cp == '*') {		/* dynamic precision */
222 		    prec = va_arg(ap, int);
223 		    if (prec < 0) {
224 			msg_warn("%s: bad precision %d in %.50s",
225 				 myname, prec, format);
226 			prec = -1;
227 		    } else
228 			VSTRING_ADDNUM(fmt, prec);
229 		    cp++;
230 		} else {			/* hard-coded precision */
231 		    for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
232 			int     digit = ch - '0';
233 
234 			if (prec > INT_MAX / 10
235 			    || (prec *= 10) > INT_MAX - digit)
236 			    msg_panic("%s: bad precision %d... in %.50s",
237 				      myname, prec, format);
238 			prec += digit;
239 			VSTRING_ADDCH(fmt, ch);
240 		    }
241 		}
242 	    } else {
243 		prec = -1;
244 	    }
245 	    if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
246 		VSTRING_ADDCH(fmt, *cp++);
247 	    if (*cp == 0)			/* premature end, punt */
248 		break;
249 	    VSTRING_ADDCH(fmt, *cp);		/* type (checked below) */
250 	    VSTRING_TERMINATE(fmt);		/* null terminate */
251 
252 	    /*
253 	     * Execute the format string - let sprintf() do the hard work for
254 	     * non-trivial cases only. For simple string conversions and for
255 	     * long string conversions, do a direct copy to the output
256 	     * buffer.
257 	     */
258 	    switch (*cp) {
259 	    case 's':				/* string-valued argument */
260 		if (long_flag)
261 		    msg_panic("%s: %%l%c is not supported", myname, *cp);
262 		s = va_arg(ap, char *);
263 		if (prec >= 0 || (width > 0 && width > strlen(s))) {
264 		    VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
265 				  vstring_str(fmt), s);
266 		} else {
267 		    VBUF_STRCAT(bp, s);
268 		}
269 		break;
270 	    case 'c':				/* integral-valued argument */
271 		if (long_flag)
272 		    msg_panic("%s: %%l%c is not supported", myname, *cp);
273 		/* FALLTHROUGH */
274 	    case 'd':
275 	    case 'u':
276 	    case 'o':
277 	    case 'x':
278 	    case 'X':
279 		if (long_flag)
280 		    VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
281 				  vstring_str(fmt), va_arg(ap, long));
282 		else
283 		    VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
284 				  vstring_str(fmt), va_arg(ap, int));
285 		break;
286 	    case 'e':				/* float-valued argument */
287 	    case 'f':
288 	    case 'g':
289 		/* C99 *printf ignore the 'l' modifier. */
290 		VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
291 			      vstring_str(fmt), va_arg(ap, double));
292 		break;
293 	    case 'm':
294 		/* Ignore the 'l' modifier, width and precision. */
295 		VBUF_STRCAT(bp, saved_errno ?
296 			    strerror(saved_errno) : "Application error");
297 		break;
298 	    case 'p':
299 		if (long_flag)
300 		    msg_panic("%s: %%l%c is not supported", myname, *cp);
301 		VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE,
302 			      vstring_str(fmt), va_arg(ap, char *));
303 		break;
304 	    default:				/* anything else is bad */
305 		msg_panic("vbuf_print: unknown format type: %c", *cp);
306 		/* NOTREACHED */
307 		break;
308 	    }
309 	}
310     }
311     return (bp);
312 }
313 
314 #ifdef TEST
315 #include <argv.h>
316 #include <msg_vstream.h>
317 #include <vstring.h>
318 #include <vstring_vstream.h>
319 
main(int argc,char ** argv)320 int     main(int argc, char **argv)
321 {
322     VSTRING *ibuf = vstring_alloc(100);
323 
324     msg_vstream_init(argv[0], VSTREAM_ERR);
325 
326     while (vstring_fgets_nonl(ibuf, VSTREAM_IN)) {
327 	ARGV   *args = argv_split(vstring_str(ibuf), CHARS_SPACE);
328 	char   *cp;
329 
330 	if (args->argc == 0 || *(cp = args->argv[0]) == '#') {
331 	     /* void */ ;
332 	} else if (args->argc != 2 || *cp != '%') {
333 	    msg_warn("usage: format number");
334 	} else {
335 	    char   *fmt = cp++;
336 	    int     lflag;
337 
338 	    /* Determine the vstring_sprintf() argument type. */
339 	    cp += strspn(cp, "+-*0123456789.");
340 	    if ((lflag = (*cp == 'l')) != 0)
341 		cp++;
342 	    if (cp[1] != 0) {
343 		msg_warn("bad format: \"%s\"", fmt);
344 	    } else {
345 		VSTRING *obuf = vstring_alloc(1);
346 		char   *val = args->argv[1];
347 
348 		/* Test the worst-case memory allocation. */
349 #ifdef CA_VSTRING_CTL_EXACT
350 		vstring_ctl(obuf, CA_VSTRING_CTL_EXACT, CA_VSTRING_CTL_END);
351 #endif
352 		switch (*cp) {
353 		case 'c':
354 		case 'd':
355 		case 'o':
356 		case 'u':
357 		case 'x':
358 		case 'X':
359 		    if (lflag)
360 			vstring_sprintf(obuf, fmt, atol(val));
361 		    else
362 			vstring_sprintf(obuf, fmt, atoi(val));
363 		    msg_info("\"%s\"", vstring_str(obuf));
364 		    break;
365 		case 's':
366 		    vstring_sprintf(obuf, fmt, val);
367 		    msg_info("\"%s\"", vstring_str(obuf));
368 		    break;
369 		case 'f':
370 		case 'g':
371 		    vstring_sprintf(obuf, fmt, atof(val));
372 		    msg_info("\"%s\"", vstring_str(obuf));
373 		    break;
374 		default:
375 		    msg_warn("bad format: \"%s\"", fmt);
376 		    break;
377 		}
378 		vstring_free(obuf);
379 	    }
380 	}
381 	argv_free(args);
382     }
383     vstring_free(ibuf);
384     return (0);
385 }
386 
387 #endif
388