Merge from vendor branch BIND:
[dragonfly.git] / lib / libc / net / ns_print.c
1 /*
2  * Copyright (c) 1996, 1998 by Internet Software Consortium.
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
9  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
10  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
11  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
13  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15  * SOFTWARE.
16  *
17  * $FreeBSD: src/lib/libc/net/ns_print.c,v 1.2 1999/08/28 00:00:15 peter Exp $
18  * $DragonFly: src/lib/libc/net/ns_print.c,v 1.3 2005/11/13 02:04:47 swildner Exp $
19  */
20
21 /* Import. */
22
23 #include <sys/types.h>
24 #include <sys/socket.h>
25
26 #include <netinet/in.h>
27 #include <arpa/nameser.h>
28 #include <arpa/inet.h>
29
30 #include <assert.h>
31 #include <errno.h>
32 #include <resolv.h>
33 #include <string.h>
34 #include <ctype.h>
35
36 #define SPRINTF(x) ((size_t)sprintf x)
37
38 /* Forward. */
39
40 static size_t   prune_origin(const char *name, const char *origin);
41 static int      charstr(const u_char *rdata, const u_char *edata,
42                         char **buf, size_t *buflen);
43 static int      addname(const u_char *msg, size_t msglen,
44                         const u_char **p, const char *origin,
45                         char **buf, size_t *buflen);
46 static void     addlen(size_t len, char **buf, size_t *buflen);
47 static int      addstr(const char *src, size_t len,
48                        char **buf, size_t *buflen);
49 static int      addtab(size_t len, size_t target, int spaced,
50                        char **buf, size_t *buflen);
51
52 /* Macros. */
53
54 #define T(x) \
55         do { \
56                 if ((x) < 0) \
57                         return (-1); \
58         } while (0)
59
60 /* Public. */
61
62 /*
63  * int
64  * ns_sprintrr(handle, rr, name_ctx, origin, buf, buflen)
65  *      Convert an RR to presentation format.
66  * return:
67  *      Number of characters written to buf, or -1 (check errno).
68  */
69 int
70 ns_sprintrr(const ns_msg *handle, const ns_rr *rr,
71             const char *name_ctx, const char *origin,
72             char *buf, size_t buflen)
73 {
74         int n;
75
76         n = ns_sprintrrf(ns_msg_base(*handle), ns_msg_size(*handle),
77                          ns_rr_name(*rr), ns_rr_class(*rr), ns_rr_type(*rr),
78                          ns_rr_ttl(*rr), ns_rr_rdata(*rr), ns_rr_rdlen(*rr),
79                          name_ctx, origin, buf, buflen);
80         return (n);
81 }
82
83 /*
84  * int
85  * ns_sprintrrf(msg, msglen, name, class, type, ttl, rdata, rdlen,
86  *             name_ctx, origin, buf, buflen)
87  *      Convert the fields of an RR into presentation format.
88  * return:
89  *      Number of characters written to buf, or -1 (check errno).
90  */
91 int
92 ns_sprintrrf(const u_char *msg, size_t msglen,
93             const char *name, ns_class class, ns_type type,
94             u_long ttl, const u_char *rdata, size_t rdlen,
95             const char *name_ctx, const char *origin,
96             char *buf, size_t buflen)
97 {
98         const char *obuf = buf;
99         const u_char *edata = rdata + rdlen;
100         int spaced = 0;
101
102         const char *comment;
103         char tmp[100];
104         int len, x;
105
106         /*
107          * Owner.
108          */
109         if (name_ctx != NULL && strcasecmp(name_ctx, name) == 0) {
110                 T(addstr("\t\t\t", 3, &buf, &buflen));
111         } else {
112                 len = prune_origin(name, origin);
113                 if (len == 0) {
114                         T(addstr("@\t\t\t", 4, &buf, &buflen));
115                 } else {
116                         T(addstr(name, len, &buf, &buflen));
117                         /* Origin not used and no trailing dot? */
118                         if ((!origin || !origin[0] || name[len] == '\0') &&
119                             name[len - 1] != '.') {
120                                 T(addstr(".", 1, &buf, &buflen));
121                                 len++;
122                         }
123                         T(spaced = addtab(len, 24, spaced, &buf, &buflen));
124                 }
125         }
126
127         /*
128          * TTL, Class, Type.
129          */
130         T(x = ns_format_ttl(ttl, buf, buflen));
131         addlen(x, &buf, &buflen);
132         len = SPRINTF((tmp, " %s %s", p_class(class), p_type(type)));
133         T(addstr(tmp, len, &buf, &buflen));
134         T(spaced = addtab(x + len, 16, spaced, &buf, &buflen));
135
136         /*
137          * RData.
138          */
139         switch (type) {
140         case ns_t_a:
141                 if (rdlen != NS_INADDRSZ)
142                         goto formerr;
143                 inet_ntop(AF_INET, rdata, buf, buflen);
144                 addlen(strlen(buf), &buf, &buflen);
145                 break;
146
147         case ns_t_cname:
148         case ns_t_mb:
149         case ns_t_mg:
150         case ns_t_mr:
151         case ns_t_ns:
152         case ns_t_ptr:
153                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
154                 break;
155
156         case ns_t_hinfo:
157         case ns_t_isdn:
158                 /* First word. */
159                 T(len = charstr(rdata, edata, &buf, &buflen));
160                 if (len == 0)
161                         goto formerr;
162                 rdata += len;
163                 T(addstr(" ", 1, &buf, &buflen));
164
165                 /* Second word. */
166                 T(len = charstr(rdata, edata, &buf, &buflen));
167                 if (len == 0)
168                         goto formerr;
169                 rdata += len;
170                 break;
171
172         case ns_t_soa: {
173                 u_long t;
174
175                 /* Server name. */
176                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
177                 T(addstr(" ", 1, &buf, &buflen));
178
179                 /* Administrator name. */
180                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
181                 T(addstr(" (\n", 3, &buf, &buflen));
182                 spaced = 0;
183
184                 if ((edata - rdata) != 5*NS_INT32SZ)
185                         goto formerr;
186
187                 /* Serial number. */
188                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
189                 T(addstr("\t\t\t\t\t", 5, &buf, &buflen));
190                 len = SPRINTF((tmp, "%lu", t));
191                 T(addstr(tmp, len, &buf, &buflen));
192                 T(spaced = addtab(len, 16, spaced, &buf, &buflen));
193                 T(addstr("; serial\n", 9, &buf, &buflen));
194                 spaced = 0;
195
196                 /* Refresh interval. */
197                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
198                 T(addstr("\t\t\t\t\t", 5, &buf, &buflen));
199                 T(len = ns_format_ttl(t, buf, buflen));
200                 addlen(len, &buf, &buflen);
201                 T(spaced = addtab(len, 16, spaced, &buf, &buflen));
202                 T(addstr("; refresh\n", 10, &buf, &buflen));
203                 spaced = 0;
204
205                 /* Retry interval. */
206                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
207                 T(addstr("\t\t\t\t\t", 5, &buf, &buflen));
208                 T(len = ns_format_ttl(t, buf, buflen));
209                 addlen(len, &buf, &buflen);
210                 T(spaced = addtab(len, 16, spaced, &buf, &buflen));
211                 T(addstr("; retry\n", 8, &buf, &buflen));
212                 spaced = 0;
213
214                 /* Expiry. */
215                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
216                 T(addstr("\t\t\t\t\t", 5, &buf, &buflen));
217                 T(len = ns_format_ttl(t, buf, buflen));
218                 addlen(len, &buf, &buflen);
219                 T(spaced = addtab(len, 16, spaced, &buf, &buflen));
220                 T(addstr("; expiry\n", 9, &buf, &buflen));
221                 spaced = 0;
222
223                 /* Minimum TTL. */
224                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
225                 T(addstr("\t\t\t\t\t", 5, &buf, &buflen));
226                 T(len = ns_format_ttl(t, buf, buflen));
227                 addlen(len, &buf, &buflen);
228                 T(addstr(" )", 2, &buf, &buflen));
229                 T(spaced = addtab(len, 16, spaced, &buf, &buflen));
230                 T(addstr("; minimum\n", 10, &buf, &buflen));
231
232                 break;
233             }
234
235         case ns_t_mx:
236         case ns_t_afsdb:
237         case ns_t_rt: {
238                 u_int t;
239
240                 if (rdlen < NS_INT16SZ)
241                         goto formerr;
242
243                 /* Priority. */
244                 t = ns_get16(rdata);
245                 rdata += NS_INT16SZ;
246                 len = SPRINTF((tmp, "%u ", t));
247                 T(addstr(tmp, len, &buf, &buflen));
248
249                 /* Target. */
250                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
251
252                 break;
253             }
254
255         case ns_t_px: {
256                 u_int t;
257
258                 if (rdlen < NS_INT16SZ)
259                         goto formerr;
260
261                 /* Priority. */
262                 t = ns_get16(rdata);
263                 rdata += NS_INT16SZ;
264                 len = SPRINTF((tmp, "%u ", t));
265                 T(addstr(tmp, len, &buf, &buflen));
266
267                 /* Name1. */
268                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
269                 T(addstr(" ", 1, &buf, &buflen));
270
271                 /* Name2. */
272                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
273
274                 break;
275             }
276
277         case ns_t_x25:
278                 T(len = charstr(rdata, edata, &buf, &buflen));
279                 if (len == 0)
280                         goto formerr;
281                 rdata += len;
282                 break;
283
284         case ns_t_txt:
285                 while (rdata < edata) {
286                         T(len = charstr(rdata, edata, &buf, &buflen));
287                         if (len == 0)
288                                 goto formerr;
289                         rdata += len;
290                         if (rdata < edata)
291                                 T(addstr(" ", 1, &buf, &buflen));
292                 }
293                 break;
294
295         case ns_t_nsap: {
296                 char t[255*3];
297
298                 inet_nsap_ntoa(rdlen, rdata, t);
299                 T(addstr(t, strlen(t), &buf, &buflen));
300                 break;
301             }
302
303         case ns_t_aaaa:
304                 if (rdlen != NS_IN6ADDRSZ)
305                         goto formerr;
306                 inet_ntop(AF_INET6, rdata, buf, buflen);
307                 addlen(strlen(buf), &buf, &buflen);
308                 break;
309
310         case ns_t_loc: {
311                 char t[255];
312
313                 /* XXX protocol format checking? */
314                 loc_ntoa(rdata, t);
315                 T(addstr(t, strlen(t), &buf, &buflen));
316                 break;
317             }
318
319         case ns_t_naptr: {
320                 u_int order, preference;
321                 char t[50];
322
323                 if (rdlen < 2*NS_INT16SZ)
324                         goto formerr;
325
326                 /* Order, Precedence. */
327                 order = ns_get16(rdata);        rdata += NS_INT16SZ;
328                 preference = ns_get16(rdata);   rdata += NS_INT16SZ;
329                 len = SPRINTF((t, "%u %u ", order, preference));
330                 T(addstr(t, len, &buf, &buflen));
331
332                 /* Flags. */
333                 T(len = charstr(rdata, edata, &buf, &buflen));
334                 if (len == 0)
335                         goto formerr;
336                 rdata += len;
337                 T(addstr(" ", 1, &buf, &buflen));
338
339                 /* Service. */
340                 T(len = charstr(rdata, edata, &buf, &buflen));
341                 if (len == 0)
342                         goto formerr;
343                 rdata += len;
344                 T(addstr(" ", 1, &buf, &buflen));
345
346                 /* Regexp. */
347                 T(len = charstr(rdata, edata, &buf, &buflen));
348                 if (len < 0)
349                         return (-1);
350                 if (len == 0)
351                         goto formerr;
352                 rdata += len;
353                 T(addstr(" ", 1, &buf, &buflen));
354
355                 /* Server. */
356                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
357                 break;
358             }
359
360         case ns_t_srv: {
361                 u_int priority, weight, port;
362                 char t[50];
363
364                 if (rdlen < NS_INT16SZ*3)
365                         goto formerr;
366
367                 /* Priority, Weight, Port. */
368                 priority = ns_get16(rdata);  rdata += NS_INT16SZ;
369                 weight   = ns_get16(rdata);  rdata += NS_INT16SZ;
370                 port     = ns_get16(rdata);  rdata += NS_INT16SZ;
371                 len = SPRINTF((t, "%u %u %u ", priority, weight, port));
372                 T(addstr(t, len, &buf, &buflen));
373
374                 /* Server. */
375                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
376                 break;
377             }
378
379         case ns_t_minfo:
380         case ns_t_rp:
381                 /* Name1. */
382                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
383                 T(addstr(" ", 1, &buf, &buflen));
384
385                 /* Name2. */
386                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
387
388                 break;
389
390         case ns_t_wks: {
391                 int n, lcnt;
392
393                 if (rdlen < NS_INT32SZ + 1)
394                         goto formerr;
395
396                 /* Address. */
397                 inet_ntop(AF_INET, rdata, buf, buflen);
398                 addlen(strlen(buf), &buf, &buflen);
399                 rdata += NS_INADDRSZ;
400
401                 /* Protocol. */
402                 len = SPRINTF((tmp, " %u ( ", *rdata));
403                 T(addstr(tmp, len, &buf, &buflen));
404                 rdata += NS_INT8SZ;
405
406                 /* Bit map. */
407                 n = 0;
408                 lcnt = 0;
409                 while (rdata < edata) {
410                         u_int c = *rdata++;
411                         do {
412                                 if (c & 0200) {
413                                         if (lcnt == 0) {
414                                                 T(addstr("\n\t\t\t\t", 5,
415                                                          &buf, &buflen));
416                                                 lcnt = 10;
417                                                 spaced = 0;
418                                         }
419                                         len = SPRINTF((tmp, "%d ", n));
420                                         T(addstr(tmp, len, &buf, &buflen));
421                                         lcnt--;
422                                 }
423                                 c <<= 1;
424                         } while (++n & 07);
425                 }
426                 T(addstr(")", 1, &buf, &buflen));
427
428                 break;
429             }
430
431         case ns_t_key: {
432                 char base64_key[NS_MD5RSA_MAX_BASE64];
433                 u_int keyflags, protocol, algorithm;
434                 const char *leader;
435                 int n;
436
437                 if (rdlen < NS_INT16SZ + NS_INT8SZ + NS_INT8SZ)
438                         goto formerr;
439
440                 /* Key flags, Protocol, Algorithm. */
441                 keyflags = ns_get16(rdata);  rdata += NS_INT16SZ;
442                 protocol = *rdata++;
443                 algorithm = *rdata++;
444                 len = SPRINTF((tmp, "0x%04x %u %u",
445                                keyflags, protocol, algorithm));
446                 T(addstr(tmp, len, &buf, &buflen));
447
448                 /* Public key data. */
449                 len = b64_ntop(rdata, edata - rdata,
450                                base64_key, sizeof base64_key);
451                 if (len < 0)
452                         goto formerr;
453                 if (len > 15) {
454                         T(addstr(" (", 2, &buf, &buflen));
455                         leader = "\n\t\t";
456                         spaced = 0;
457                 } else
458                         leader = " ";
459                 for (n = 0; n < len; n += 48) {
460                         T(addstr(leader, strlen(leader), &buf, &buflen));
461                         T(addstr(base64_key + n, MIN(len - n, 48),
462                                  &buf, &buflen));
463                 }
464                 if (len > 15)
465                         T(addstr(" )", 2, &buf, &buflen));
466
467                 break;
468             }
469
470         case ns_t_sig: {
471                 char base64_key[NS_MD5RSA_MAX_BASE64];
472                 u_int type, algorithm, labels, footprint;
473                 const char *leader;
474                 u_long t;
475                 int n;
476
477                 if (rdlen < 22)
478                         goto formerr;
479
480                 /* Type covered, Algorithm, Label count, Original TTL. */
481                 type = ns_get16(rdata);  rdata += NS_INT16SZ;
482                 algorithm = *rdata++;
483                 labels = *rdata++;
484                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
485                 len = SPRINTF((tmp, " %s %d %lu ",
486                                p_type(type), algorithm, t));
487                 T(addstr(tmp, len, &buf, &buflen));
488                 if (labels != (u_int)dn_count_labels(name))
489                         goto formerr;
490
491                 /* Signature expiry. */
492                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
493                 len = SPRINTF((tmp, "%s ", p_secstodate(t)));
494                 T(addstr(tmp, len, &buf, &buflen));
495
496                 /* Time signed. */
497                 t = ns_get32(rdata);  rdata += NS_INT32SZ;
498                 len = SPRINTF((tmp, "%s ", p_secstodate(t)));
499                 T(addstr(tmp, len, &buf, &buflen));
500
501                 /* Signature Footprint. */
502                 footprint = ns_get16(rdata);  rdata += NS_INT16SZ;
503                 len = SPRINTF((tmp, "%u ", footprint));
504                 T(addstr(tmp, len, &buf, &buflen));
505
506                 /* Signer's name. */
507                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
508
509                 /* Signature. */
510                 len = b64_ntop(rdata, edata - rdata,
511                                base64_key, sizeof base64_key);
512                 if (len > 15) {
513                         T(addstr(" (", 2, &buf, &buflen));
514                         leader = "\n\t\t";
515                         spaced = 0;
516                 } else
517                         leader = " ";
518                 if (len < 0)
519                         goto formerr;
520                 for (n = 0; n < len; n += 48) {
521                         T(addstr(leader, strlen(leader), &buf, &buflen));
522                         T(addstr(base64_key + n, MIN(len - n, 48),
523                                  &buf, &buflen));
524                 }
525                 if (len > 15)
526                         T(addstr(" )", 2, &buf, &buflen));
527
528                 break;
529             }
530
531         case ns_t_nxt: {
532                 int n, c;
533
534                 /* Next domain name. */
535                 T(addname(msg, msglen, &rdata, origin, &buf, &buflen));
536
537                 /* Type bit map. */
538                 n = edata - rdata;
539                 for (c = 0; c < n*8; c++)
540                         if (NS_NXT_BIT_ISSET(c, rdata)) {
541                                 len = SPRINTF((tmp, " %s", p_type(c)));
542                                 T(addstr(tmp, len, &buf, &buflen));
543                         }
544                 break;
545             }
546
547         default:
548                 comment = "unknown RR type";
549                 goto hexify;
550         }
551         return (buf - obuf);
552  formerr:
553         comment = "RR format error";
554  hexify: {
555         int n, m;
556         char *p;
557
558         len = SPRINTF((tmp, "\\#(\t\t; %s", comment));
559         T(addstr(tmp, len, &buf, &buflen));
560         while (rdata < edata) {
561                 p = tmp;
562                 p += SPRINTF((p, "\n\t"));
563                 spaced = 0;
564                 n = MIN(16, edata - rdata);
565                 for (m = 0; m < n; m++)
566                         p += SPRINTF((p, "%02x ", rdata[m]));
567                 T(addstr(tmp, p - tmp, &buf, &buflen));
568                 if (n < 16) {
569                         T(addstr(")", 1, &buf, &buflen));
570                         T(addtab(p - tmp + 1, 48, spaced, &buf, &buflen));
571                 }
572                 p = tmp;
573                 p += SPRINTF((p, "; "));
574                 for (m = 0; m < n; m++)
575                         *p++ = (isascii(rdata[m]) && isprint(rdata[m]))
576                                 ? rdata[m]
577                                 : '.';
578                 T(addstr(tmp, p - tmp, &buf, &buflen));
579                 rdata += n;
580         }
581         return (buf - obuf);
582     }
583 }
584
585 /* Private. */
586
587 /*
588  * size_t
589  * prune_origin(name, origin)
590  *      Find out if the name is at or under the current origin.
591  * return:
592  *      Number of characters in name before start of origin,
593  *      or length of name if origin does not match.
594  * notes:
595  *      This function should share code with samedomain().
596  */
597 static size_t
598 prune_origin(const char *name, const char *origin) {
599         const char *oname = name;
600
601         while (*name != '\0') {
602                 if (origin != NULL && strcasecmp(name, origin) == 0)
603                         return (name - oname - (name > oname));
604                 while (*name != '\0') {
605                         if (*name == '\\') {
606                                 name++;
607                                 /* XXX need to handle \nnn form. */
608                                 if (*name == '\0')
609                                         break;
610                         } else if (*name == '.') {
611                                 name++;
612                                 break;
613                         }
614                         name++;
615                 }
616         }
617         return (name - oname);
618 }
619
620 /*
621  * int
622  * charstr(rdata, edata, buf, buflen)
623  *      Format a <character-string> into the presentation buffer.
624  * return:
625  *      Number of rdata octets consumed
626  *      0 for protocol format error
627  *      -1 for output buffer error
628  * side effects:
629  *      buffer is advanced on success.
630  */
631 static int
632 charstr(const u_char *rdata, const u_char *edata, char **buf, size_t *buflen) {
633         const u_char *odata = rdata;
634         size_t save_buflen = *buflen;
635         char *save_buf = *buf;
636
637         if (addstr("\"", 1, buf, buflen) < 0)
638                 goto enospc;
639         if (rdata < edata) {
640                 int n = *rdata;
641
642                 if (rdata + 1 + n <= edata) {
643                         rdata++;
644                         while (n-- > 0) {
645                                 if (strchr("\n\"\\", *rdata) != NULL)
646                                         if (addstr("\\", 1, buf, buflen) < 0)
647                                                 goto enospc;
648                                 if (addstr((const char *)rdata, 1,
649                                            buf, buflen) < 0)
650                                         goto enospc;
651                                 rdata++;
652                         }
653                 }
654         }
655         if (addstr("\"", 1, buf, buflen) < 0)
656                 goto enospc;
657         return (rdata - odata);
658  enospc:
659         errno = ENOSPC;
660         *buf = save_buf;
661         *buflen = save_buflen;
662         return (-1);
663 }
664
665 static int
666 addname(const u_char *msg, size_t msglen,
667         const u_char **pp, const char *origin,
668         char **buf, size_t *buflen)
669 {
670         size_t newlen, save_buflen = *buflen;
671         char *save_buf = *buf;
672         int n;
673
674         n = dn_expand(msg, msg + msglen, *pp, *buf, *buflen);
675         if (n < 0)
676                 goto enospc;    /* Guess. */
677         newlen = prune_origin(*buf, origin);
678         if ((origin == NULL || origin[0] == '\0' || (*buf)[newlen] == '\0') &&
679             (newlen == 0 || (*buf)[newlen - 1] != '.')) {
680                 /* No trailing dot. */
681                 if (newlen + 2 > *buflen)
682                         goto enospc;    /* No room for ".\0". */
683                 (*buf)[newlen++] = '.';
684                 (*buf)[newlen] = '\0';
685         }
686         if (newlen == 0) {
687                 /* Use "@" instead of name. */
688                 if (newlen + 2 > *buflen)
689                         goto enospc;        /* No room for "@\0". */
690                 (*buf)[newlen++] = '@';
691                 (*buf)[newlen] = '\0';
692         }
693         *pp += n;
694         addlen(newlen, buf, buflen);
695         **buf = '\0';
696         return (newlen);
697  enospc:
698         errno = ENOSPC;
699         *buf = save_buf;
700         *buflen = save_buflen;
701         return (-1);
702 }
703
704 static void
705 addlen(size_t len, char **buf, size_t *buflen) {
706         assert(len <= *buflen);
707         *buf += len;
708         *buflen -= len;
709 }
710
711 static int
712 addstr(const char *src, size_t len, char **buf, size_t *buflen) {
713         if (len > *buflen) {
714                 errno = ENOSPC;
715                 return (-1);
716         }
717         memcpy(*buf, src, len);
718         addlen(len, buf, buflen);
719         **buf = '\0';
720         return (0);
721 }
722
723 static int
724 addtab(size_t len, size_t target, int spaced, char **buf, size_t *buflen) {
725         size_t save_buflen = *buflen;
726         char *save_buf = *buf;
727         int t;
728
729         if (spaced || len >= target - 1) {
730                 T(addstr("  ", 2, buf, buflen));
731                 spaced = 1;
732         } else {
733                 for (t = (target - len - 1) / 8; t >= 0; t--)
734                         if (addstr("\t", 1, buf, buflen) < 0) {
735                                 *buflen = save_buflen;
736                                 *buf = save_buf;
737                                 return (-1);
738                         }
739                 spaced = 0;
740         }
741         return (spaced);
742 }