summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
authorJoshua M. Clulow <jmc@joyent.com>2014-12-19 14:03:07 -0800
committerRobert Mustacchi <rm@joyent.com>2014-12-23 14:52:48 -0800
commit196c7f05d2deba7404e90ad67f3861185c78ca2d (patch)
tree29ff7b2bd21041c7331063485095b6008b58ee10 /usr/src
parentaa64fa16549f13233681f4b40e50fb5b1c18a97c (diff)
downloadillumos-joyent-196c7f05d2deba7404e90ad67f3861185c78ca2d.tar.gz
5480 CVE-2012-3165 mailx(1) buffer overflow vulnerability
Reviewed by: Dan McDonald <danmcd@omniti.com> Reviewed by: Robert Mustacchi <rm@joyent.com> Reviewed by: Richard Lowe <richlowe@richlowe.net> Approved by: Dan McDonald <danmcd@omniti.com>
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/cmd/Makefile1
-rw-r--r--usr/src/cmd/fmt/Makefile1
-rw-r--r--usr/src/cmd/fmt/fmt.c6
-rw-r--r--usr/src/cmd/mailx/Makefile4
-rw-r--r--usr/src/cmd/mailx/cmd1.c34
-rw-r--r--usr/src/cmd/mailx/fio.c8
-rw-r--r--usr/src/cmd/mailx/hdr/def.h22
-rw-r--r--usr/src/cmd/mailx/head.c242
-rw-r--r--usr/src/cmd/mailx/receipt.c26
-rw-r--r--usr/src/lib/libcmdutils/Makefile.com3
-rw-r--r--usr/src/lib/libcmdutils/common/custr.c135
-rw-r--r--usr/src/lib/libcmdutils/common/mapfile-vers7
-rw-r--r--usr/src/lib/libcmdutils/libcmdutils.h43
13 files changed, 421 insertions, 111 deletions
diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile
index c59e370d47..6f5a277510 100644
--- a/usr/src/cmd/Makefile
+++ b/usr/src/cmd/Makefile
@@ -829,6 +829,7 @@ fs.d: fstyp
ksh: shcomp isaexec
mdb: terminfo
print: lp
+fmt: mailx
$(FIRST_SUBDIRS) $(BWOSDIRS) $(SUBDIRS) $(AUDITSUBDIRS): FRC
@if [ -f $@/Makefile ]; then \
diff --git a/usr/src/cmd/fmt/Makefile b/usr/src/cmd/fmt/Makefile
index 21938536d6..cecd4e3c2a 100644
--- a/usr/src/cmd/fmt/Makefile
+++ b/usr/src/cmd/fmt/Makefile
@@ -33,6 +33,7 @@ SRCS= $(OBJS:%.o=%.c)
include ../Makefile.cmd
+LDLIBS += -lcmdutils
CERRWARN += -_gcc=-Wno-switch
CERRWARN += -_gcc=-Wno-parentheses
diff --git a/usr/src/cmd/fmt/fmt.c b/usr/src/cmd/fmt/fmt.c
index 52d4676172..b5fbee6e13 100644
--- a/usr/src/cmd/fmt/fmt.c
+++ b/usr/src/cmd/fmt/fmt.c
@@ -86,7 +86,7 @@ int h_lines; /* index into lines of hdrbuf */
void (*(split))(wchar_t []);
extern int scrwidth(wchar_t);
-extern int ishead(char []);
+extern boolean_t is_headline(const char *);
static void fill_hdrbuf(wchar_t []);
@@ -240,13 +240,13 @@ fmt(FILE *fi)
}
/*
* Need to convert string from wchar_t to char,
- * since this is what ishead() expects. Since we
+ * since this is what is_headline() expects. Since we
* only want to make sure cp points to a "From" line
* of the email, we don't have to alloc
* BUFSIZ * MB_LEN_MAX to cbuf.
*/
wcstombs(cbuf, cp, (BUFSIZ - 1));
- if (ishead(cbuf)) {
+ if (is_headline(cbuf) == B_TRUE) {
hdr_state = in_hdr;
fill_hdrbuf(canonb);
} else {
diff --git a/usr/src/cmd/mailx/Makefile b/usr/src/cmd/mailx/Makefile
index 93b527b72a..4fb19334f2 100644
--- a/usr/src/cmd/mailx/Makefile
+++ b/usr/src/cmd/mailx/Makefile
@@ -22,6 +22,8 @@
# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
+# Copyright 2014 Joyent, Inc.
+#
# cmd/mailx/Makefile
PROG= mailx
@@ -67,7 +69,7 @@ CERRWARN += -_gcc=-Wno-uninitialized
CERRWARN += -_gcc=-Wno-unused-variable
CERRWARN += -_gcc=-Wno-clobbered
LINTFLAGS= -hb
-LDLIBS += -lmail
+LDLIBS += -lmail -lcmdutils
LDFLAGS += $(MAPFILE.NGB:%=-M%)
CLOBBERFILES += $(MAILXHELP)
diff --git a/usr/src/cmd/mailx/cmd1.c b/usr/src/cmd/mailx/cmd1.c
index 5cdb7ae465..38c9b7ab9b 100644
--- a/usr/src/cmd/mailx/cmd1.c
+++ b/usr/src/cmd/mailx/cmd1.c
@@ -19,6 +19,11 @@
*
* CDDL HEADER END
*/
+
+/*
+ * Copyright 2014 Joyent, Inc.
+ */
+
/*
* Copyright 2001 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
@@ -37,8 +42,7 @@
* contributors.
*/
-#pragma ident "%Z%%M% %I% %E% SMI"
-
+#include <err.h>
#include "rcv.h"
#include <locale.h>
@@ -213,10 +217,14 @@ printhead(int mesg)
char *fromline;
char pbuf[LINESIZE];
char name[LINESIZE];
- struct headline hl;
+ headline_t *hl;
register char *cp;
int showto;
+ if (headline_alloc(&hl) != 0) {
+ err(1, "could not allocate memory");
+ }
+
mp = &message[mesg-1];
ibuf = setinput(mp);
readline(ibuf, headline);
@@ -248,9 +256,14 @@ printhead(int mesg)
dispc = 'H';
if (mp->m_flag & MBOX)
dispc = 'M';
- parse(headline, &hl, pbuf);
- if (hl.l_date == NOSTR)
- hl.l_date = "<Unknown date>";
+ if (parse_headline(headline, hl) == -1) {
+ headline_reset(hl);
+ }
+ if (custr_len(hl->hl_date) == 0) {
+ if (custr_append(hl->hl_date, "<Unknown date>") != 0) {
+ err(1, "could not print header");
+ }
+ }
/*
* Netnews interface?
@@ -299,11 +312,14 @@ printhead(int mesg)
}
if (mp->m_text) {
printf("%16.16s %4ld/%-5ld %-.25s\n",
- hl.l_date, mp->m_lines, mp->m_size, subjline);
- } else {
- printf("%16.16s binary/%-5ld %-.25s\n", hl.l_date, mp->m_size,
+ custr_cstr(hl->hl_date), mp->m_lines, mp->m_size,
subjline);
+ } else {
+ printf("%16.16s binary/%-5ld %-.25s\n", custr_cstr(hl->hl_date),
+ mp->m_size, subjline);
}
+
+ headline_free(hl);
}
/*
diff --git a/usr/src/cmd/mailx/fio.c b/usr/src/cmd/mailx/fio.c
index 818860d7ae..59eceb519f 100644
--- a/usr/src/cmd/mailx/fio.c
+++ b/usr/src/cmd/mailx/fio.c
@@ -21,6 +21,10 @@
*/
/*
+ * Copyright 2014 Joyent, Inc.
+ */
+
+/*
* Copyright 1999 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -28,8 +32,6 @@
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
-#pragma ident "%Z%%M% %I% %E% SMI"
-
#include "rcv.h"
#include <locale.h>
#include <wordexp.h>
@@ -174,7 +176,7 @@ setptr(register FILE *ibuf)
}
/* Look for a From line that starts a new message */
- if (blankline && linebuf[0] == 'F' && ishead(linebuf)) {
+ if (blankline && linebuf[0] == 'F' && is_headline(linebuf)) {
if (msgCount > 0 && !newmail) {
message[msgCount-1].m_size = s;
message[msgCount-1].m_lines = l;
diff --git a/usr/src/cmd/mailx/hdr/def.h b/usr/src/cmd/mailx/hdr/def.h
index d0e48820ea..7453b24f97 100644
--- a/usr/src/cmd/mailx/hdr/def.h
+++ b/usr/src/cmd/mailx/hdr/def.h
@@ -20,6 +20,10 @@
*/
/*
+ * Copyright 2014 Joyent, Inc.
+ */
+
+/*
* Copyright (c) 1985, 2010, Oracle and/or its affiliates. All rights reserved.
*/
@@ -60,6 +64,7 @@ extern "C" {
#include <stdlib.h>
#include <ulimit.h>
#include <wait.h>
+#include <libcmdutils.h>
#endif
#ifdef VMUNIX
#include <sys/wait.h>
@@ -204,11 +209,11 @@ struct cmd {
* line
*/
-struct headline {
- char *l_from; /* The name of the sender */
- char *l_tty; /* His tty string (if any) */
- char *l_date; /* The entire date string */
-};
+typedef struct headline {
+ custr_t *hl_from; /* The name of the sender */
+ custr_t *hl_tty; /* His tty string (if any) */
+ custr_t *hl_date; /* The entire date string */
+} headline_t;
#define GTO 1 /* Grab To: line */
#define GSUBJECT 2 /* Likewise, Subject: line */
@@ -486,6 +491,9 @@ extern int hash(char name[]);
extern char *hcontents(char hfield[]);
extern int headerp(register char *line);
extern int headers(int *msgvec);
+extern int headline_alloc(headline_t **);
+extern void headline_free(headline_t *);
+extern void headline_reset(headline_t *);
extern int help(void);
extern char *helppath(char *file);
extern char *hfield(char field[], struct message *mp,
@@ -497,7 +505,7 @@ extern int igfield(char *list[]);
extern int inc(void);
extern void inithost(void);
extern int isdir(char name[]);
-extern int ishead(char linebuf[]);
+extern boolean_t is_headline(const char *);
extern int ishfield(char linebuf[], char field[]);
extern int ishost(char *sys, char *rest);
extern int isign(char *field, int saving);
@@ -533,7 +541,7 @@ extern int null(char *e);
extern int outof(struct name *names, FILE *fo);
extern struct name *outpre(struct name *to);
extern void panic(char *str);
-extern void parse(char line[], struct headline *hl, char pbuf[]);
+extern int parse_headline(const char *, headline_t *);
extern int pcmdlist(void);
extern int pdot(void);
extern int preserve(int *msgvec);
diff --git a/usr/src/cmd/mailx/head.c b/usr/src/cmd/mailx/head.c
index 3f833ab14e..b6d09f0767 100644
--- a/usr/src/cmd/mailx/head.c
+++ b/usr/src/cmd/mailx/head.c
@@ -19,6 +19,11 @@
*
* CDDL HEADER END
*/
+
+/*
+ * Copyright 2014 Joyent, Inc.
+ */
+
/*
* Copyright 1995 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
@@ -38,7 +43,7 @@
* contributors.
*/
-#pragma ident "%Z%%M% %I% %E% SMI"
+#include <err.h>
#include "rcv.h"
@@ -49,115 +54,188 @@
* Routines for processing and detecting headlines.
*/
-static char *copyin(char src[], char **space);
-static char *nextword(char wp[], char wbuf[]);
+static int nextword(const char *, custr_t *, const char **);
/*
* See if the passed line buffer is a mail header.
* Return true if yes.
*/
-
-int
-ishead(char linebuf[])
+boolean_t
+is_headline(const char *linebuf)
{
- register char *cp;
- struct headline hl;
- char parbuf[BUFSIZ];
-
- cp = linebuf;
- if (strncmp("From ", cp, 5) != 0)
- return(0);
- parse(cp, &hl, parbuf);
- if (hl.l_from == NOSTR) {
- return(0);
+ headline_t *hl;
+ boolean_t ret;
+
+ if (strncmp("From ", linebuf, 5) != 0) {
+ return (B_FALSE);
+ }
+
+ if (headline_alloc(&hl) != 0 || parse_headline(linebuf, hl) != 0) {
+ err(1, "could not parse headline");
}
- return(1);
+
+ ret = custr_len(hl->hl_from) > 0 ? B_TRUE : B_FALSE;
+
+ headline_free(hl);
+ return (ret);
}
/*
- * Split a headline into its useful components.
- * Copy the line into dynamic string space, then set
- * pointers into the copied line in the passed headline
- * structure. Actually, it scans.
+ * Manage headline_t objects:
*/
-void
-parse(char line[], struct headline *hl, char pbuf[])
+void
+headline_free(headline_t *hl)
+{
+ custr_free(hl->hl_from);
+ custr_free(hl->hl_tty);
+ custr_free(hl->hl_date);
+ free(hl);
+}
+
+int
+headline_alloc(headline_t **hl)
{
- register char *cp, *dp;
- char *sp;
- char word[LINESIZE];
+ int en;
+ headline_t *t;
+
+ if ((t = calloc(1, sizeof (*t))) == NULL) {
+ return (-1);
+ }
- hl->l_from = NOSTR;
- hl->l_date = NOSTR;
- cp = line;
- sp = pbuf;
+ if (custr_alloc(&t->hl_from) != 0 || custr_alloc(&t->hl_tty) != 0 ||
+ custr_alloc(&t->hl_date) != 0) {
+ en = errno;
- /*
- * Skip the first "word" of the line, which should be "From"
- * anyway.
- */
+ headline_free(t);
- cp = nextword(cp, word);
- dp = nextword(cp, word);
- if (!equal(word, ""))
- hl->l_from = copyin(word, &sp);
- if (dp != NOSTR)
- hl->l_date = copyin(dp, &sp);
+ errno = en;
+ return (-1);
+ }
+
+ *hl = t;
+ return (0);
}
/*
- * Copy the string on the left into the string on the right
- * and bump the right (reference) string pointer by the length.
- * Thus, dynamically allocate space in the right string, copying
- * the left string into it.
+ * Clear all of the strings in a headline_t:
*/
+void
+headline_reset(headline_t *hl)
+{
+ custr_reset(hl->hl_from);
+ custr_reset(hl->hl_tty);
+ custr_reset(hl->hl_date);
+}
-static char *
-copyin(char src[], char **space)
+int
+parse_headline(const char *line, headline_t *hl)
{
- register char *cp, *top;
- register int s;
-
- s = strlen(src);
- cp = *space;
- top = cp;
- strcpy(cp, src);
- cp += s + 1;
- *space = cp;
- return(top);
+ const char *c = line;
+
+ headline_reset(hl);
+
+ /*
+ * Load the first word from the line and ensure that it is "From".
+ */
+ if (nextword(c, hl->hl_from, &c) != 0) {
+ return (-1);
+ }
+ if (strcmp(custr_cstr(hl->hl_from), "From") != 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+ custr_reset(hl->hl_from);
+
+ /*
+ * The next word will be the From address.
+ */
+ if (nextword(c, hl->hl_from, &c) != 0) {
+ return (-1);
+ }
+
+ /*
+ * If there is a next word, the rest of the string is the Date.
+ */
+ if (c != NULL) {
+ if (custr_append(hl->hl_date, c) != 0) {
+ return (-1);
+ }
+ }
+
+ errno = 0;
+ return (0);
}
/*
- * Collect a liberal (space, tab delimited) word into the word buffer
- * passed. Also, return a pointer to the next word following that,
- * or NOSTR if none follow.
+ * Collect a space- or tab-delimited word into the word buffer, if one is
+ * passed. The double quote character (") can be used to include whitespace
+ * within a word. Set "nextword" to the location of the first character of the
+ * _next_ word, or NULL if there were no more words. Returns 0 on success or
+ * -1 otherwise.
*/
-
-static char *
-nextword(char wp[], char wbuf[])
+static int
+nextword(const char *input, custr_t *word, const char **nextword)
{
- register char *cp, *cp2;
+ boolean_t in_quotes = B_FALSE;
+ const char *c = input != NULL ? input : "";
+
+ /*
+ * Collect the first word into the word buffer, if one is provided.
+ */
+ for (;;) {
+ if (*c == '\0') {
+ /*
+ * We have reached the end of the string.
+ */
+ *nextword = NULL;
+ return (0);
+ }
+
+ if (*c == '"') {
+ /*
+ * Either beginning or ending a quoted string.
+ */
+ in_quotes = in_quotes ? B_FALSE : B_TRUE;
+ }
+
+ if (!in_quotes && (*c == ' ' || *c == '\t')) {
+ /*
+ * We have reached a whitespace region.
+ */
+ break;
+ }
+
+ /*
+ * Copy this character into the word buffer.
+ */
+ if (word != NULL) {
+ if (custr_appendc(word, *c) != 0) {
+ return (-1);
+ }
+ }
+ c++;
+ }
+
+ /*
+ * Find the beginning of the next word, if there is one.
+ */
+ for (;;) {
+ if (*c == '\0') {
+ /*
+ * We have reached the end of the string.
+ */
+ *nextword = NULL;
+ return (0);
- if ((cp = wp) == NOSTR) {
- copy("", wbuf);
- return(NOSTR);
+ } else if (*c != ' ' && *c != '\t') {
+ /*
+ * We have located the next word.
+ */
+ *nextword = c;
+ return (0);
+ }
+ c++;
}
- cp2 = wbuf;
- while (!any(*cp, " \t") && *cp != '\0')
- if (*cp == '"') {
- *cp2++ = *cp++;
- while (*cp != '\0' && *cp != '"')
- *cp2++ = *cp++;
- if (*cp == '"')
- *cp2++ = *cp++;
- } else
- *cp2++ = *cp++;
- *cp2 = '\0';
- while (any(*cp, " \t"))
- cp++;
- if (*cp == '\0')
- return(NOSTR);
- return(cp);
}
/*
diff --git a/usr/src/cmd/mailx/receipt.c b/usr/src/cmd/mailx/receipt.c
index 2a128d6a91..0b6b9296fc 100644
--- a/usr/src/cmd/mailx/receipt.c
+++ b/usr/src/cmd/mailx/receipt.c
@@ -19,6 +19,11 @@
*
* CDDL HEADER END
*/
+
+/*
+ * Copyright 2014 Joyent, Inc.
+ */
+
/*
* Copyright 1998 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
@@ -38,7 +43,7 @@
* contributors.
*/
-#pragma ident "%Z%%M% %I% %E% SMI"
+#include <err.h>
#include "rcv.h"
@@ -47,12 +52,12 @@ static int icsubstr(char *s1, char *s2);
void
receipt(struct message *mp)
{
- struct headline hl;
char head[LINESIZE];
char buf[BUFSIZ];
FILE *pp, *fp;
char *mail, *s;
+
if ((mail = value("sendmail")) == 0)
#ifdef SENDMAIL
mail = SENDMAIL;
@@ -63,16 +68,27 @@ receipt(struct message *mp)
|| icsubstr(hfield(">to", mp, addto), "/receipt")) {
snprintf(buf, sizeof (buf), "%s %s", mail, skin(nameof(mp)));
if (pp = npopen(buf, "w")) {
+ headline_t *hl;
+
+ if (headline_alloc(&hl) != 0) {
+ err(1, "could not allocate memory");
+ }
+
fp = setinput(mp);
readline(fp, head);
- parse(head, &hl, buf);
- if (hl.l_date != NOSTR)
- fprintf(pp, "Original-Date: %s\n", hl.l_date);
+ if (parse_headline(head, hl) != 0) {
+ headline_reset(hl);
+ }
+ if (custr_len(hl->hl_date) > 0) {
+ fprintf(pp, "Original-Date: %s\n",
+ custr_cstr(hl->hl_date));
+ }
if (s = hfield("message-id", mp, addone))
fprintf(pp, "Original-Message-ID: %s\n", s);
s = hfield("subject", mp, addone);
fprintf(pp, "Subject: RR: %s\n", s ? s : "(none)");
npclose(pp);
+ headline_free(hl);
}
}
}
diff --git a/usr/src/lib/libcmdutils/Makefile.com b/usr/src/lib/libcmdutils/Makefile.com
index 2f98fe5f25..ffe3c9cba5 100644
--- a/usr/src/lib/libcmdutils/Makefile.com
+++ b/usr/src/lib/libcmdutils/Makefile.com
@@ -25,7 +25,8 @@
LIBRARY= libcmdutils.a
VERS= .1
-CMD_OBJS= avltree.o sysattrs.o writefile.o process_xattrs.o uid.o gid.o
+CMD_OBJS= avltree.o sysattrs.o writefile.o process_xattrs.o uid.o gid.o \
+ custr.o
COM_OBJS= list.o
OBJECTS= $(CMD_OBJS) $(COM_OBJS)
diff --git a/usr/src/lib/libcmdutils/common/custr.c b/usr/src/lib/libcmdutils/common/custr.c
new file mode 100644
index 0000000000..1ec72de9dd
--- /dev/null
+++ b/usr/src/lib/libcmdutils/common/custr.c
@@ -0,0 +1,135 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * String utility functions with dynamic memory management.
+ */
+
+/*
+ * Copyright 2014, Joyent, Inc.
+ */
+
+#include <stdlib.h>
+#include <err.h>
+#include <string.h>
+
+#include "libcmdutils.h"
+
+struct custr {
+ size_t cus_strlen;
+ size_t cus_datalen;
+ char *cus_data;
+};
+
+#define STRING_CHUNK_SIZE 64
+
+void
+custr_reset(custr_t *cus)
+{
+ if (cus->cus_data == NULL)
+ return;
+
+ cus->cus_strlen = 0;
+ cus->cus_data[0] = '\0';
+}
+
+size_t
+custr_len(custr_t *cus)
+{
+ return (cus->cus_strlen);
+}
+
+const char *
+custr_cstr(custr_t *cus)
+{
+ return (cus->cus_data);
+}
+
+int
+custr_appendc(custr_t *cus, char newc)
+{
+ char news[2];
+
+ news[0] = newc;
+ news[1] = '\0';
+
+ return (custr_append(cus, news));
+}
+
+int
+custr_append(custr_t *cus, const char *news)
+{
+ size_t len = strlen(news);
+ size_t chunksz = STRING_CHUNK_SIZE;
+
+ while (chunksz < len) {
+ chunksz *= 2;
+ }
+
+ if (len + cus->cus_strlen + 1 >= cus->cus_datalen) {
+ char *new_data;
+ size_t new_datalen = cus->cus_datalen + chunksz;
+
+ /*
+ * Allocate replacement memory:
+ */
+ if ((new_data = malloc(new_datalen)) == NULL) {
+ return (-1);
+ }
+
+ /*
+ * Copy existing data into replacement memory and free
+ * the old memory.
+ */
+ if (cus->cus_data != NULL) {
+ (void) memcpy(new_data, cus->cus_data,
+ cus->cus_strlen + 1);
+ free(cus->cus_data);
+ }
+
+ /*
+ * Swap in the replacement buffer:
+ */
+ cus->cus_data = new_data;
+ cus->cus_datalen = new_datalen;
+ }
+ /*
+ * Append new string to existing string:
+ */
+ (void) memcpy(cus->cus_data + cus->cus_strlen, news, len + 1);
+ cus->cus_strlen += len;
+
+ return (0);
+}
+
+int
+custr_alloc(custr_t **cus)
+{
+ custr_t *t;
+
+ if ((t = calloc(1, sizeof (*t))) == NULL) {
+ *cus = NULL;
+ return (-1);
+ }
+
+ *cus = t;
+ return (0);
+}
+
+void
+custr_free(custr_t *cus)
+{
+ if (cus == NULL)
+ return;
+
+ free(cus->cus_data);
+ free(cus);
+}
diff --git a/usr/src/lib/libcmdutils/common/mapfile-vers b/usr/src/lib/libcmdutils/common/mapfile-vers
index e4c5940c31..640959e4b5 100644
--- a/usr/src/lib/libcmdutils/common/mapfile-vers
+++ b/usr/src/lib/libcmdutils/common/mapfile-vers
@@ -42,6 +42,13 @@ $mapfile_version 2
SYMBOL_VERSION SUNWprivate_1.1 {
global:
add_tnode;
+ custr_alloc;
+ custr_append;
+ custr_appendc;
+ custr_cstr;
+ custr_free;
+ custr_len;
+ custr_reset;
destroy_tree;
findnextgid;
findnextuid;
diff --git a/usr/src/lib/libcmdutils/libcmdutils.h b/usr/src/lib/libcmdutils/libcmdutils.h
index c315e0fbef..a280751c27 100644
--- a/usr/src/lib/libcmdutils/libcmdutils.h
+++ b/usr/src/lib/libcmdutils/libcmdutils.h
@@ -25,6 +25,9 @@
/*
* Copyright (c) 2013 RackTop Systems.
*/
+/*
+ * Copyright 2014 Joyent, Inc.
+ */
/*
* Declarations for the functions in libcmdutils.
@@ -140,6 +143,46 @@ extern int findnextuid(uid_t, uid_t, uid_t *);
*/
extern int findnextgid(gid_t, gid_t, gid_t *);
+
+
+ /* dynamic string utilities */
+
+typedef struct custr custr_t;
+
+/*
+ * Allocate and free a "custr_t" dynamic string object. Returns 0 on success
+ * and -1 otherwise.
+ */
+extern int custr_alloc(custr_t **);
+extern void custr_free(custr_t *);
+
+/*
+ * Append a single character, or a NUL-terminated string of characters, to a
+ * dynamic string. Returns 0 on success and -1 otherwise. The dynamic string
+ * will be unmodified if the function returns -1.
+ */
+extern int custr_appendc(custr_t *, char);
+extern int custr_append(custr_t *, const char *);
+
+/*
+ * Determine the length in bytes, not including the NUL terminator, of the
+ * dynamic string.
+ */
+extern size_t custr_len(custr_t *);
+
+/*
+ * Clear the contents of a dynamic string. Does not free the underlying
+ * memory.
+ */
+extern void custr_reset(custr_t *);
+
+/*
+ * Retrieve a const pointer to a NUL-terminated string version of the contents
+ * of the dynamic string. Storage for this string should not be freed, and
+ * the pointer will be invalidated by any mutations to the dynamic string.
+ */
+extern const char *custr_cstr(custr_t *str);
+
#ifdef __cplusplus
}
#endif