1 /*        $NetBSD: vbuf_print.c,v 1.5 2025/02/25 19:15:52 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 "stringops.h"
73 #include "vbuf_print.h"
74 
75  /*
76   * What we need here is a *sprintf() routine that can ask for more room (as
77   * in 4.4 BSD). However, that functionality is not widely available, and I
78   * have no plans to maintain a complete 4.4 BSD *sprintf() alternative.
79   *
80   * Postfix vbuf_print() was implemented when many mainstream systems had no
81   * usable snprintf() implementation (usable means: return the length,
82   * excluding terminator, that the output would have if the buffer were large
83   * enough). For example, GLIBC before 2.1 (1999) snprintf() did not
84   * distinguish between formatting error and buffer size error, while SUN had
85   * no snprintf() implementation before Solaris 2.6 (1997).
86   *
87   * For the above reasons, vbuf_print() was implemented with sprintf() and a
88   * generously-sized output buffer. Current vbuf_print() implementations use
89   * snprintf(), and report an error if the output does not fit (in that case,
90   * the old sprintf()-based implementation would have had a buffer overflow
91   * vulnerability). The old implementation is still available for building
92   * Postfix on ancient systems.
93   *
94   * Guessing the output size of a string (%s) conversion is not hard. The
95   * problem is with numerical results. Instead of making an accurate guess we
96   * take a wide margin when reserving space.  The INT_SPACE margin should be
97   * large enough to hold the result from any (octal, hex, decimal) integer
98   * conversion that has no explicit width or precision specifiers. With
99   * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP
100   * just to be sure.
101   */
102 #define INT_SPACE   ((CHAR_BIT * sizeof(long)) / 2)
103 #define DBL_SPACE   ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP)
104 #define PTR_SPACE   ((CHAR_BIT * sizeof(char *)) / 2)
105 
106  /*
107   * Helper macros... Note that there is no need to check the result from
108   * VSTRING_SPACE() because that always succeeds or never returns.
109   */
110 #ifndef NO_SNPRINTF
111 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
112           ssize_t _ret; \
113           if (VBUF_SPACE((bp), (sz)) != 0) \
114               return (bp); \
115           _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
116           if (_ret < 0) \
117               msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \
118           if (_ret >= (bp)->cnt) \
119               msg_panic("%s: output for '%s' exceeds space %ld", \
120                           myname, mystrdup(fmt), (long) (bp)->cnt); \
121           VBUF_SKIP(bp); \
122     } while (0)
123 #else
124 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
125           if (VBUF_SPACE((bp), (sz)) != 0) \
126               return (bp); \
127           sprintf((char *) (bp)->ptr, (fmt), (arg)); \
128           VBUF_SKIP(bp); \
129     } while (0)
130 #endif
131 
132 #define VBUF_SKIP(bp) do { \
133           while ((bp)->cnt > 0 && *(bp)->ptr) \
134               (bp)->ptr++, (bp)->cnt--; \
135     } while (0)
136 
137 #define VSTRING_ADDNUM(vp, n) do { \
138           VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
139     } while (0)
140 
141 #define VBUF_STRCAT(bp, s) do { \
142           unsigned char *_cp = (unsigned char *) (s); \
143           int _ch; \
144           while ((_ch = *_cp++) != 0) \
145               VBUF_PUT((bp), _ch); \
146     } while (0)
147 
148 /* vbuf_print - format string, vsprintf-like interface */
149 
vbuf_print(VBUF * bp,const char * format,va_list ap)150 VBUF   *vbuf_print(VBUF *bp, const char *format, va_list ap)
151 {
152     const char *myname = "vbuf_print";
153     static VSTRING *fmt;                /* format specifier */
154     unsigned char *cp;
155     int     width;                      /* width and numerical precision */
156     int     prec;                       /* are signed for overflow defense */
157     unsigned long_flag;                           /* long or plain integer */
158     int     ch;
159     char   *s;
160     int     saved_errno = errno;        /* VBUF_SPACE() may clobber it */
161 
162     /*
163      * Assume that format strings are short.
164      */
165     if (fmt == 0)
166           fmt = vstring_alloc(INT_SPACE);
167 
168     /*
169      * Iterate over characters in the format string, picking up arguments
170      * when format specifiers are found.
171      */
172     for (cp = (unsigned char *) format; *cp; cp++) {
173           if (*cp != '%') {
174               VBUF_PUT(bp, *cp);                            /* ordinary character */
175           } else if (cp[1] == '%') {
176               VBUF_PUT(bp, *cp++);                /* %% becomes % */
177           } else {
178 
179               /*
180                * Handle format specifiers one at a time, since we can only deal
181                * with arguments one at a time. Try to determine the end of the
182                * format specifier. We do not attempt to fully parse format
183                * strings, since we are ging to let sprintf() do the hard work.
184                * In regular expression notation, we recognize:
185                *
186                * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z]
187                *
188                * which includes some combinations that do not make sense. Garbage
189                * in, garbage out.
190                */
191               VSTRING_RESET(fmt);                           /* clear format string */
192               VSTRING_ADDCH(fmt, *cp++);
193               if (*cp == '-')                     /* left-adjusted field? */
194                     VSTRING_ADDCH(fmt, *cp++);
195               if (*cp == '+')                     /* signed field? */
196                     VSTRING_ADDCH(fmt, *cp++);
197               if (*cp == '0')                     /* zero-padded field? */
198                     VSTRING_ADDCH(fmt, *cp++);
199               if (*cp == '*') {                             /* dynamic field width */
200                     width = va_arg(ap, int);
201                     if (width < 0) {
202                         msg_warn("%s: bad width %d in %.50s",
203                                    myname, width, format);
204                         width = 0;
205                     } else
206                         VSTRING_ADDNUM(fmt, width);
207                     cp++;
208               } else {                                      /* hard-coded field width */
209                     for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
210                         int     digit = ch - '0';
211 
212                         if (width > INT_MAX / 10
213                               || (width *= 10) > INT_MAX - digit)
214                               msg_panic("%s: bad width %d... in %.50s",
215                                           myname, width, format);
216                         width += digit;
217                         VSTRING_ADDCH(fmt, ch);
218                     }
219               }
220               if (*cp == '.') {                             /* width/precision separator */
221                     VSTRING_ADDCH(fmt, *cp++);
222                     if (*cp == '*') {             /* dynamic precision */
223                         prec = va_arg(ap, int);
224                         if (prec < 0) {
225                               msg_warn("%s: bad precision %d in %.50s",
226                                          myname, prec, format);
227                               prec = -1;
228                         } else
229                               VSTRING_ADDNUM(fmt, prec);
230                         cp++;
231                     } else {                      /* hard-coded precision */
232                         for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
233                               int     digit = ch - '0';
234 
235                               if (prec > INT_MAX / 10
236                                   || (prec *= 10) > INT_MAX - digit)
237                                   msg_panic("%s: bad precision %d... in %.50s",
238                                               myname, prec, format);
239                               prec += digit;
240                               VSTRING_ADDCH(fmt, ch);
241                         }
242                     }
243               } else {
244                     prec = -1;
245               }
246               if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
247                     VSTRING_ADDCH(fmt, *cp++);
248               if (*cp == 0)                       /* premature end, punt */
249                     break;
250               VSTRING_ADDCH(fmt, *cp);            /* type (checked below) */
251               VSTRING_TERMINATE(fmt);             /* null terminate */
252 
253               /*
254                * Execute the format string - let sprintf() do the hard work for
255                * non-trivial cases only. For simple string conversions and for
256                * long string conversions, do a direct copy to the output
257                * buffer.
258                */
259               switch (*cp) {
260               case 's':                                     /* string-valued argument */
261                     if (long_flag)
262                         msg_panic("%s: %%l%c is not supported", myname, *cp);
263                     s = va_arg(ap, char *);
264                     if (prec >= 0 || (width > 0 && width > strlen(s))) {
265                         VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
266                                           vstring_str(fmt), s);
267                     } else {
268                         VBUF_STRCAT(bp, s);
269                     }
270                     break;
271               case 'c':                                     /* integral-valued argument */
272                     if (long_flag)
273                         msg_panic("%s: %%l%c is not supported", myname, *cp);
274                     /* FALLTHROUGH */
275               case 'd':
276               case 'u':
277               case 'o':
278               case 'x':
279               case 'X':
280                     if (long_flag)
281                         VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
282                                           vstring_str(fmt), va_arg(ap, long));
283                     else
284                         VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
285                                           vstring_str(fmt), va_arg(ap, int));
286                     break;
287               case 'e':                                     /* float-valued argument */
288               case 'f':
289               case 'g':
290                     /* C99 *printf ignore the 'l' modifier. */
291                     VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
292                                     vstring_str(fmt), va_arg(ap, double));
293                     break;
294               case 'm':
295                     /* Ignore the 'l' modifier, width and precision. */
296                     VBUF_STRCAT(bp, mystrerror(saved_errno));
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