nmsg  0.9.0
ncap.c
Go to the documentation of this file.
1 /* ncap nmsg message module */
2 
3 /*
4  * Copyright (c) 2009-2013 by Farsight Security, Inc.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 /* Import. */
20 
21 #include <wdns.h>
22 
23 #include "ncap.pb-c.h"
24 
25 /* Exported via module context. */
26 
27 static nmsg_res
28 ncap_msg_load(nmsg_message_t m, void **msg_clos);
29 
30 static nmsg_res
31 ncap_msg_fini(nmsg_message_t m, void *msg_clos);
32 
33 static nmsg_res
34 ncap_ipdg_to_payload(void *, const struct nmsg_ipdg *, uint8_t **pay, size_t *);
35 
36 static NMSG_MSGMOD_FIELD_PRINTER(ncap_print_payload);
37 
38 static NMSG_MSGMOD_FIELD_GETTER(ncap_get_srcip);
39 static NMSG_MSGMOD_FIELD_GETTER(ncap_get_dstip);
40 static NMSG_MSGMOD_FIELD_GETTER(ncap_get_srcport);
41 static NMSG_MSGMOD_FIELD_GETTER(ncap_get_dstport);
42 static NMSG_MSGMOD_FIELD_GETTER(ncap_get_proto);
43 static NMSG_MSGMOD_FIELD_GETTER(ncap_get_dns);
44 
45 /* Data. */
46 
47 struct nmsg_msgmod_field ncap_fields[] = {
48  {
50  .name = "type",
52  },
53  {
54  .type = nmsg_msgmod_ft_enum,
55  .name = "ltype",
56  .flags = NMSG_MSGMOD_FIELD_HIDDEN,
57  },
58  {
59  .type = nmsg_msgmod_ft_ip,
60  .name = "srcip",
61  .get = ncap_get_srcip,
62  },
63  {
64  .type = nmsg_msgmod_ft_ip,
65  .name = "dstip",
66  .get = ncap_get_dstip,
67  },
68  {
69  .type = nmsg_msgmod_ft_uint32,
70  .name = "lint0",
71  .flags = NMSG_MSGMOD_FIELD_HIDDEN,
72  },
73  {
74  .type = nmsg_msgmod_ft_uint32,
75  .name = "lint1",
76  .flags = NMSG_MSGMOD_FIELD_HIDDEN,
77  },
78  {
79  .type = nmsg_msgmod_ft_uint16,
80  .name = "srcport",
81  .get = ncap_get_srcport,
82  },
83  {
84  .type = nmsg_msgmod_ft_uint16,
85  .name = "dstport",
86  .get = ncap_get_dstport,
87  },
88  {
89  .type = nmsg_msgmod_ft_uint16,
90  .name = "proto",
91  .get = ncap_get_proto,
92  },
93  {
94  .type = nmsg_msgmod_ft_bytes,
95  .name = "payload",
97  .print = ncap_print_payload
98  },
99  {
100  .type = nmsg_msgmod_ft_bytes,
101  .name = "dns",
102  .flags = NMSG_MSGMOD_FIELD_NOPRINT,
103  .get = ncap_get_dns
104  },
105  NMSG_MSGMOD_FIELD_END
106 };
107 
108 /* Export. */
109 
110 struct nmsg_msgmod_plugin nmsg_msgmod_ctx = {
111  NMSG_MSGMOD_REQUIRED_INIT,
112  .vendor = NMSG_VENDOR_BASE,
113  .msgtype = { NMSG_VENDOR_BASE_NCAP_ID, NMSG_VENDOR_BASE_NCAP_NAME },
114 
115  .msg_load = ncap_msg_load,
116  .msg_fini = ncap_msg_fini,
117  .pbdescr = &nmsg__base__ncap__descriptor,
118  .fields = ncap_fields,
119  .ipdg_to_payload = ncap_ipdg_to_payload
120 };
121 
122 /* Forward. */
123 
124 static nmsg_res ncap_pbuf_inet_ntop(ProtobufCBinaryData *bdata, char *str);
125 
126 static nmsg_res
127 ncap_print_udp(nmsg_strbuf_t, const char *srcip, const char *dstip,
128  uint16_t srcport, uint16_t dstport,
129  const u_char *payload, size_t paylen, const char *el);
130 
131 /* Private. */
132 
133 struct ncap_priv {
134  bool has_srcip;
135  bool has_dstip;
136  bool has_srcport;
137  bool has_dstport;
138  uint32_t srcport;
139  uint32_t dstport;
140  uint32_t proto;
141  ProtobufCBinaryData srcip;
142  ProtobufCBinaryData dstip;
143  struct nmsg_ipdg dg;
144 };
145 
146 static nmsg_res
147 ncap_msg_load(nmsg_message_t m, void **msg_clos) {
148  Nmsg__Base__Ncap *ncap;
149  const struct nmsg_iphdr *ip;
150  const struct ip6_hdr *ip6;
151  const struct nmsg_udphdr *udp;
152  struct ncap_priv *p;
153  unsigned etype;
154 
155  ncap = (Nmsg__Base__Ncap *) nmsg_message_get_payload(m);
156  if (ncap == NULL || ncap->payload.data == NULL || ncap->payload.len == 0)
157  return (nmsg_res_failure);
158 
159  *msg_clos = p = calloc(1, sizeof(struct ncap_priv));
160  if (p == NULL)
161  return (nmsg_res_memfail);
162 
163  /* source and destination IPs */
164  switch (ncap->type) {
165  case NMSG__BASE__NCAP_TYPE__IPV4:
166  etype = ETHERTYPE_IP;
167  nmsg_ipdg_parse(&p->dg, etype, ncap->payload.len, ncap->payload.data);
168  ip = (const struct nmsg_iphdr *) p->dg.network;
169  p->has_srcip = true;
170  p->has_dstip = true;
171  p->srcip.len = 4;
172  p->dstip.len = 4;
173  p->srcip.data = (uint8_t *) &ip->ip_src;
174  p->dstip.data = (uint8_t *) &ip->ip_dst;
175  p->proto = ip->ip_p;
176  break;
177  case NMSG__BASE__NCAP_TYPE__IPV6:
178  etype = ETHERTYPE_IPV6;
179  nmsg_ipdg_parse(&p->dg, etype, ncap->payload.len, ncap->payload.data);
180  ip6 = (const struct ip6_hdr *) p->dg.network;
181  p->has_srcip = true;
182  p->has_dstip = true;
183  p->srcip.len = 16;
184  p->dstip.len = 16;
185  p->srcip.data = (uint8_t *) &ip6->ip6_src;
186  p->dstip.data = (uint8_t *) &ip6->ip6_dst;
187  p->proto = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt;
188  break;
189  case NMSG__BASE__NCAP_TYPE__Legacy:
190  break;
191  default:
192  assert(0); /* unreached */
193  }
194 
195  /* source and destination ports */
196  switch (ncap->type) {
197  case NMSG__BASE__NCAP_TYPE__IPV4:
198  case NMSG__BASE__NCAP_TYPE__IPV6:
199  switch (p->dg.proto_transport) {
200  case IPPROTO_UDP:
201  udp = (const struct nmsg_udphdr *) p->dg.transport;
202  p->has_srcport = true;
203  p->has_dstport = true;
204  p->srcport = ntohs(udp->uh_sport);
205  p->dstport = ntohs(udp->uh_dport);
206  break;
207  }
208  break;
209  case NMSG__BASE__NCAP_TYPE__Legacy:
210  switch (ncap->ltype) {
211  case NMSG__BASE__NCAP_LEGACY_TYPE__UDP:
212  case NMSG__BASE__NCAP_LEGACY_TYPE__TCP:
213  if (ncap->has_lint0) {
214  p->has_srcport = true;
215  p->srcport = ncap->lint0;
216  }
217  if (ncap->has_lint1) {
218  p->has_dstport = true;
219  p->dstport = ncap->lint1;
220  }
221  case NMSG__BASE__NCAP_LEGACY_TYPE__ICMP:
222  break;
223  default:
224  assert(0); /* unreached */
225  }
226  switch (ncap->ltype) {
227  case NMSG__BASE__NCAP_LEGACY_TYPE__UDP:
228  p->proto = IPPROTO_UDP;
229  break;
230  case NMSG__BASE__NCAP_LEGACY_TYPE__TCP:
231  p->proto = IPPROTO_TCP;
232  break;
233  case NMSG__BASE__NCAP_LEGACY_TYPE__ICMP:
234  p->proto = IPPROTO_ICMP;
235  break;
236  default:
237  assert(0); /* unreached */
238  }
239  break;
240  default:
241  assert(0); /* unreached */
242  }
243 
244  return (nmsg_res_success);
245 }
246 
247 static nmsg_res
248 ncap_msg_fini(nmsg_message_t m, void *msg_clos) {
249  free(msg_clos);
250  return (nmsg_res_success);
251 }
252 
253 static nmsg_res
254 ncap_get_srcip(nmsg_message_t m,
255  struct nmsg_msgmod_field *field,
256  unsigned val_idx,
257  void **data,
258  size_t *len,
259  void *msg_clos)
260 {
261  Nmsg__Base__Ncap *ncap = (Nmsg__Base__Ncap *) nmsg_message_get_payload(m);
262  struct ncap_priv *p = msg_clos;
263 
264  if (ncap == NULL || p == NULL)
265  return (nmsg_res_failure);
266 
267  if (val_idx == 0) {
268  switch (ncap->type) {
269  case NMSG__BASE__NCAP_TYPE__IPV4:
270  case NMSG__BASE__NCAP_TYPE__IPV6:
271  *data = p->srcip.data;
272  if (len)
273  *len = p->srcip.len;
274  break;
275  case NMSG__BASE__NCAP_TYPE__Legacy:
276  if (ncap->has_srcip) {
277  *data = ncap->srcip.data;
278  if (len)
279  *len = ncap->srcip.len;
280  }
281  break;
282  default:
283  assert(0); /* unreached */
284  }
285 
286  return (nmsg_res_success);
287  }
288  return (nmsg_res_failure);
289 }
290 
291 static nmsg_res
292 ncap_get_dstip(nmsg_message_t m,
293  struct nmsg_msgmod_field *field,
294  unsigned val_idx,
295  void **data,
296  size_t *len,
297  void *msg_clos)
298 {
299  Nmsg__Base__Ncap *ncap = (Nmsg__Base__Ncap *) nmsg_message_get_payload(m);
300  struct ncap_priv *p = msg_clos;
301 
302  if (ncap == NULL || p == NULL)
303  return (nmsg_res_failure);
304 
305  if (val_idx == 0) {
306  switch (ncap->type) {
307  case NMSG__BASE__NCAP_TYPE__IPV4:
308  case NMSG__BASE__NCAP_TYPE__IPV6:
309  *data = p->dstip.data;
310  if (len)
311  *len = p->dstip.len;
312  break;
313  case NMSG__BASE__NCAP_TYPE__Legacy:
314  if (ncap->has_dstip) {
315  *data = ncap->dstip.data;
316  if (len)
317  *len = ncap->dstip.len;
318  }
319  break;
320  default:
321  assert(0); /* unreached */
322  }
323 
324  return (nmsg_res_success);
325  }
326  return (nmsg_res_failure);
327 }
328 
329 static nmsg_res
330 ncap_get_srcport(nmsg_message_t m,
331  struct nmsg_msgmod_field *field,
332  unsigned val_idx,
333  void **data,
334  size_t *len,
335  void *msg_clos)
336 {
337  struct ncap_priv *p = msg_clos;
338 
339  if (p != NULL && val_idx == 0 && p->has_srcport) {
340  *data = &p->srcport;
341  if (len)
342  *len = sizeof(p->srcport);
343 
344  return (nmsg_res_success);
345  }
346  return (nmsg_res_failure);
347 }
348 
349 static nmsg_res
350 ncap_get_dstport(nmsg_message_t m,
351  struct nmsg_msgmod_field *field,
352  unsigned val_idx,
353  void **data,
354  size_t *len,
355  void *msg_clos)
356 {
357  struct ncap_priv *p = msg_clos;
358 
359  if (p != NULL && val_idx == 0 && p->has_dstport) {
360  *data = &p->dstport;
361  if (len)
362  *len = sizeof(p->dstport);
363 
364  return (nmsg_res_success);
365  }
366  return (nmsg_res_failure);
367 }
368 
369 static nmsg_res
370 ncap_get_proto(nmsg_message_t m,
371  struct nmsg_msgmod_field *field,
372  unsigned val_idx,
373  void **data,
374  size_t *len,
375  void *msg_clos)
376 {
377  struct ncap_priv *p = msg_clos;
378 
379  if (p != NULL && val_idx == 0) {
380  *data = &p->proto;
381  if (len)
382  *len = sizeof(p->proto);
383 
384  return (nmsg_res_success);
385  }
386  return (nmsg_res_failure);
387 }
388 
389 static nmsg_res
390 ncap_get_dns(nmsg_message_t m,
391  struct nmsg_msgmod_field *field,
392  unsigned val_idx,
393  void **data,
394  size_t *len,
395  void *msg_clos)
396 {
397  Nmsg__Base__Ncap *ncap = (Nmsg__Base__Ncap *) nmsg_message_get_payload(m);
398  struct ncap_priv *p = msg_clos;
399 
400  if (ncap == NULL || p == NULL)
401  return (nmsg_res_failure);
402 
403  if (val_idx != 0)
404  return (nmsg_res_failure);
405 
406  if (p->srcport == 53 || p->srcport == 5353 ||
407  p->dstport == 53 || p->dstport == 5353)
408  {
409  switch (ncap->type) {
410  case NMSG__BASE__NCAP_TYPE__IPV4:
411  case NMSG__BASE__NCAP_TYPE__IPV6:
412  *data = (void *) p->dg.payload;
413  if (len)
414  *len = p->dg.len_payload;
415  return (nmsg_res_success);
416  break;
417  case NMSG__BASE__NCAP_TYPE__Legacy:
418  *data = (void *) ncap->payload.data;
419  if (len)
420  *len = ncap->payload.len;
421  return (nmsg_res_success);
422  break;
423  default:
424  assert(0); /* unreached */
425  }
426  }
427  return (nmsg_res_failure);
428 }
429 
430 static nmsg_res
431 ncap_pbuf_inet_ntop(ProtobufCBinaryData *bdata, char *str) {
432  socklen_t strsz = INET6_ADDRSTRLEN;
433 
434  if (bdata->len == 4) {
435  if (inet_ntop(AF_INET, bdata->data, str, strsz) == NULL)
436  return (nmsg_res_failure);
437  } else if (bdata->len == 16) {
438  if (inet_ntop(AF_INET6, bdata->data, str, strsz) == NULL)
439  return (nmsg_res_failure);
440  }
441 
442  return (nmsg_res_success);
443 }
444 
445 static nmsg_res
446 ncap_print_udp(nmsg_strbuf_t sb, const char *srcip, const char *dstip,
447  uint16_t srcport, uint16_t dstport,
448  const u_char *payload, size_t paylen, const char *el)
449 {
450  nmsg_res res;
451 
452  if (payload == NULL)
453  return (nmsg_res_failure);
454  res = nmsg_strbuf_append(sb, "[%s].%hu [%s].%hu udp [%u]%s",
455  srcip, srcport, dstip, dstport, paylen, el);
456  if (res != nmsg_res_success)
457  return (nmsg_res_failure);
458 
459  if (srcport == 53 || srcport == 5353 ||
460  dstport == 53 || dstport == 5353)
461  {
462  char *s;
463  wdns_message_t m;
464  wdns_res status;
465 
466  status = wdns_parse_message(&m, payload, paylen);
467  if (status != wdns_res_success)
468  return (nmsg_res_failure);
469  s = wdns_message_to_str(&m);
470  if (s == NULL)
471  return (nmsg_res_memfail);
472  nmsg_strbuf_append(sb, "%s", s);
473  free(s);
474 
475  wdns_clear_message(&m);
476  }
477  if (res != nmsg_res_success)
478  return (nmsg_res_failure);
479  nmsg_strbuf_append(sb, "\n");
480 
481  return (nmsg_res_success);
482 }
483 
484 static nmsg_res
485 ncap_print_payload(nmsg_message_t msg,
486  struct nmsg_msgmod_field *field __attribute__((unused)),
487  void *ptr __attribute__((unused)),
488  nmsg_strbuf_t sb,
489  const char *endline)
490 {
491  Nmsg__Base__Ncap *ncap;
492  char dstip[INET6_ADDRSTRLEN];
493  char srcip[INET6_ADDRSTRLEN];
494  const char *err_str = "unknown";
495  const struct nmsg_iphdr *ip;
496  const struct ip6_hdr *ip6;
497  const struct nmsg_udphdr *udp;
498  nmsg_res res;
499  struct nmsg_ipdg dg;
500  unsigned etype;
501 
502  ncap = (Nmsg__Base__Ncap *) nmsg_message_get_payload(msg);
503  if (ncap == NULL || ncap->payload.data == NULL || ncap->payload.len == 0) {
504  res = nmsg_strbuf_append(sb, "payload: <DECODING ERROR>%s", endline);
505  return (res);
506  }
507 
508  dstip[0] = '\0';
509  srcip[0] = '\0';
510 
511  res = nmsg_strbuf_append(sb, "payload:%s", endline);
512  if (res != nmsg_res_success)
513  return (res);
514 
515  /* parse header fields */
516  switch (ncap->type) {
517  case NMSG__BASE__NCAP_TYPE__IPV4:
518  etype = ETHERTYPE_IP;
519  nmsg_ipdg_parse(&dg, etype, ncap->payload.len, ncap->payload.data);
520  ip = (const struct nmsg_iphdr *) dg.network;
521  inet_ntop(AF_INET, &ip->ip_src, srcip, sizeof(srcip));
522  inet_ntop(AF_INET, &ip->ip_dst, dstip, sizeof(dstip));
523  break;
524  case NMSG__BASE__NCAP_TYPE__IPV6:
525  etype = ETHERTYPE_IPV6;
526  nmsg_ipdg_parse(&dg, etype, ncap->payload.len, ncap->payload.data);
527  ip6 = (const struct ip6_hdr *) dg.network;
528  inet_ntop(AF_INET6, ip6->ip6_src.s6_addr, srcip, sizeof(srcip));
529  inet_ntop(AF_INET6, ip6->ip6_dst.s6_addr, dstip, sizeof(dstip));
530  break;
531  case NMSG__BASE__NCAP_TYPE__Legacy:
532  if (ncap->has_srcip == 0) {
533  err_str = "legacy ncap payload missing srcip field";
534  goto err;
535  }
536  if (ncap->has_dstip == 0) {
537  err_str = "legacy ncap payload missing dstip field";
538  goto err;
539  }
540  if (ncap_pbuf_inet_ntop(&ncap->srcip, srcip) == nmsg_res_failure)
541  {
542  err_str = "unable to decode legacy ncap srcip field";
543  goto err;
544  }
545  if (ncap_pbuf_inet_ntop(&ncap->dstip, dstip) == nmsg_res_failure)
546  {
547  err_str = "unable to decode legacy ncap dstip field";
548  goto err;
549  }
550  break;
551  default:
552  goto unknown_ncap_type;
553  break;
554  }
555 
556  /* parse payload */
557  switch (ncap->type) {
558  case NMSG__BASE__NCAP_TYPE__IPV4:
559  case NMSG__BASE__NCAP_TYPE__IPV6:
560  switch (dg.proto_transport) {
561  case IPPROTO_UDP:
562  udp = (const struct nmsg_udphdr *) dg.transport;
563  res = ncap_print_udp(sb, srcip, dstip,
564  ntohs(udp->uh_sport),
565  ntohs(udp->uh_dport),
566  dg.payload, dg.len_payload, endline);
567  if (res != nmsg_res_success) {
568  err_str = "payload parse failed";
569  goto err;
570  }
571  break;
572  }
573  break;
574  case NMSG__BASE__NCAP_TYPE__Legacy:
575  switch (ncap->ltype) {
576  case NMSG__BASE__NCAP_LEGACY_TYPE__UDP:
577  if (ncap->has_lint0 == 0) {
578  err_str = "legacy ncap payload missing lint0 field";
579  goto err;
580  }
581  if (ncap->has_lint1 == 0) {
582  err_str = "legacy ncap payload missing lint1 field";
583  goto err;
584  }
585  res = ncap_print_udp(sb, srcip, dstip,
586  ncap->lint0, ncap->lint1,
587  ncap->payload.data,
588  ncap->payload.len, endline);
589  if (res != nmsg_res_success) {
590  err_str = "legacy payload parse failed";
591  goto err;
592  }
593  break;
594  case NMSG__BASE__NCAP_LEGACY_TYPE__TCP:
595  case NMSG__BASE__NCAP_LEGACY_TYPE__ICMP:
596  res = nmsg_strbuf_append(sb, "<ERROR: unhandled legacy ncap type %u>%s",
597  ncap->ltype, endline);
598  return (res);
599  break;
600  default:
601  assert(0); /* unreached */
602  }
603  break;
604  default:
605  goto unknown_ncap_type;
606  break;
607  }
608 
609  return (nmsg_res_success);
610 
611 err:
612  res = nmsg_strbuf_append(sb, "<ERROR: %s>%s", err_str, endline);
613  return (res);
614 
615 unknown_ncap_type:
616  res = nmsg_strbuf_append(sb, "<ERROR: unknown ncap type %u>%s",
617  ncap->type, endline);
618  return (res);
619 }
620 
621 static nmsg_res
622 ncap_ipdg_to_payload(void *clos __attribute__((unused)),
623  const struct nmsg_ipdg *dg,
624  uint8_t **pbuf, size_t *sz)
625 {
626  Nmsg__Base__Ncap nc;
627  size_t estsz;
628 
629  /* initialize in-memory ncap message */
630  nmsg__base__ncap__init(&nc);
631 
632  /* set type */
633  switch (dg->proto_network) {
634  case PF_INET:
635  nc.type = NMSG__BASE__NCAP_TYPE__IPV4;
636  break;
637  case PF_INET6:
638  nc.type = NMSG__BASE__NCAP_TYPE__IPV6;
639  break;
640  default:
641  return (nmsg_res_parse_error);
642  }
643 
644  /* set payload */
645  nc.payload.data = (uint8_t *) dg->network;
646  nc.payload.len = dg->len_network;
647 
648  /* serialize ncap payload */
649  estsz = nc.payload.len + 64 /* ad hoc */;
650  *pbuf = malloc(estsz);
651  if (*pbuf == NULL)
652  return (nmsg_res_memfail);
653  *sz = nmsg__base__ncap__pack(&nc, *pbuf);
654 
655  return (nmsg_res_pbuf_ready);
656 }
657 
Structure exported by message modules to implement a new message type.
nmsg_res
nmsg result code
Definition: res.h:25
success
Definition: res.h:26
Protobuf enum.
Definition: msgmod.h:75
out of memory
Definition: res.h:29
Protobuf byte array.
Definition: msgmod.h:78
unsigned len_network
length starting from network
Definition: ipdg.h:37
Parsed IP datagram.
Definition: ipdg.h:34
Protobuf byte array.
Definition: msgmod.h:96
nmsg_res nmsg_ipdg_parse(struct nmsg_ipdg *dg, unsigned etype, size_t len, const u_char *pkt)
Parse IP packets from the network layer, discarding fragments.
Definition: ipdg.c:33
Structure mapping protocol buffer schema fields to nmsg_msgmod_field_type values for "transparent" mo...
Protobuf uint32.
Definition: msgmod.h:102
#define NMSG_MSGMOD_FIELD_HIDDEN
hide field from the message API
Definition: msgmod.h:125
generic failure
Definition: res.h:27
const u_char * network
pointer to network header
Definition: ipdg.h:40
a pbuf is ready to be written
Definition: res.h:32
Protobuf uint32.
Definition: msgmod.h:99
unable to parse input
Definition: res.h:36
nmsg_msgmod_field_type type
Intended (nmsg) type of this protobuf field.
void * nmsg_message_get_payload(nmsg_message_t msg)
WARNING: experts only.
#define NMSG_MSGMOD_FIELD_REQUIRED
field is required
Definition: msgmod.h:124
int proto_network
PF_* value.
Definition: ipdg.h:35
#define NMSG_MSGMOD_FIELD_NOPRINT
don't print the field
Definition: msgmod.h:126
nmsg_res nmsg_strbuf_append(struct nmsg_strbuf *sb, const char *fmt,...)
Append to a string buffer.
Definition: strbuf.c:44