>From 974a8a7ecb9a41da0f56dbfc847db53420f11d2b Mon Sep 17 00:00:00 2001
From: Stef Walter <stefw@redhat.com>
Date: Tue, 7 Jan 2014 13:24:27 +0100
Subject: [PATCH] NSS: Don't use printf(3) on user provided strings.

This also fixes several corner cases and crashers.

It's not prudent to pass user input to (even admin) input as a
format string to printf, and various distros now check for this.
This can cause accessing memory incorrectly, and various also
various libc abort()'s.

In addition various assumptions were made about full_name_format
that aren't necessarily the case if the user uses a more complex
format.

Use safe-printf.c implementation for formatting full_name_format.
This code came from realmd but only has a dependency on libc.

Adapt the NSS resolver so it doesn't barf on formatted strings that
are shorter than expected given a full_name_format.

Tests added and updated appropriately.
---
 .gitignore                      |   1 +
 Makefile.am                     |  16 +-
 src/responder/nss/nsssrv_cmd.c  | 132 ++++++---------
 src/tests/cmocka/test_fqnames.c |  21 ---
 src/tests/cmocka/test_nss_srv.c |  21 ++-
 src/tests/cmocka/test_utils.c   |   6 +-
 src/tests/safe-printf-tests.c   | 268 ++++++++++++++++++++++++++++++
 src/util/safe-printf.c          | 349 ++++++++++++++++++++++++++++++++++++++++
 src/util/safe-printf.h          |  46 ++++++
 src/util/usertools.c            | 112 ++++---------
 src/util/util.h                 |  12 +-
 11 files changed, 774 insertions(+), 210 deletions(-)
 create mode 100644 src/tests/safe-printf-tests.c
 create mode 100644 src/util/safe-printf.c
 create mode 100644 src/util/safe-printf.h

diff --git a/.gitignore b/.gitignore
index a9515ac0833a1f80da33c6394a634e898d1b0c20..74d5deeebe6285d35bc3853a0340b2bd438d7e3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,6 +95,7 @@ ad_common_tests
 ad_ldap_opt-tests
 dlopen-tests
 dyndns-tests
+safe-printf-tests
 fqnames-tests
 krb5-child-test
 nss-srv-tests
diff --git a/Makefile.am b/Makefile.am
index 4015dadd78e26ec285b7010c3e39b22458fa878b..f6635d6989a4170d4161302cb9ab2b8cc0d32a60 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -136,7 +136,8 @@ if HAVE_CHECK
         debug-tests \
         ipa_hbac-tests \
         sss_idmap-tests \
-        responder_socket_access-tests
+        responder_socket_access-tests \
+        safe-printf-tests
 
 if BUILD_SSH
     non_interactive_check_based_tests += sysdb_ssh-tests
@@ -400,6 +401,7 @@ dist_noinst_HEADERS = \
     src/util/util.h \
     src/util/io.h \
     src/util/util_errors.h \
+    src/util/safe-printf.h \
     src/util/strtonum.h \
     src/util/sss_endian.h \
     src/util/sss_nss.h \
@@ -572,6 +574,7 @@ libsss_util_la_SOURCES = \
     src/sbus/sssd_dbus_server.c \
     src/util/util.c \
     src/util/memory.c \
+    src/util/safe-printf.c \
     src/util/server.c \
     src/util/signal.c \
     src/util/usertools.c \
@@ -1179,6 +1182,17 @@ util_tests_LDADD = \
     $(SSSD_INTERNAL_LTLIBS) \
     libsss_test_common.la
 
+safe_printf_tests_SOURCES = \
+    src/tests/safe-printf-tests.c
+safe_printf_tests_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(CHECK_CFLAGS)
+safe_printf_tests_LDADD = \
+    $(SSSD_LIBS) \
+    $(CHECK_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    libsss_test_common.la
+
 debug_tests_SOURCES = \
     src/tests/debug-tests.c \
     src/tests/common.c
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 715313f8953020d7a80f2878392c245eaef855aa..137f14ce0796b31f601087d4bad29cfb1b38bd53 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -298,20 +298,14 @@ static int fill_pwent(struct sss_packet *packet,
     uint32_t uid;
     uint32_t gid;
     size_t rsize, rp, blen;
-    size_t dom_len = 0;
-    int delim = 0;
-    int i, ret, num, t;
+    size_t fq_len = 0;
+    int i, ret, num;
     bool add_domain = (!IS_SUBDOMAIN(dom) && dom->fqnames);
     const char *domain = dom->name;
     bool packet_initialized = false;
     int ncret;
     TALLOC_CTX *tmp_ctx = NULL;
 
-    if (add_domain) {
-        delim = 1;
-        dom_len = sss_fqdom_len(dom->names, dom);
-    }
-
     to_sized_string(&pwfield, nctx->pwfield);
 
     rp = 2*sizeof(uint32_t);
@@ -381,7 +375,17 @@ static int fill_pwent(struct sss_packet *packet,
 
         rsize = 2 * sizeof(uint32_t) + name.len + gecos.len +
                                        homedir.len + shell.len + pwfield.len;
-        if (add_domain) rsize += delim + dom_len;
+
+        if (add_domain) {
+            fq_len = sss_fqname(NULL, 0, dom->names, dom, name.str);
+            if (fq_len >= 0) {
+                fq_len += 1;
+                rsize -= name.len;
+                rsize += fq_len;
+            } else {
+                fq_len = 0;
+            }
+        }
 
         ret = sss_packet_grow(packet, rsize);
         if (ret != EOK) {
@@ -394,25 +398,8 @@ static int fill_pwent(struct sss_packet *packet,
         SAFEALIGN_SET_UINT32(&body[rp], gid, &rp);
 
         if (add_domain) {
-            ret = sss_fqname((char *) &body[rp], name.len + delim + dom_len,
-                             dom->names, dom, name.str);
-            if (ret >= (name.len + delim + dom_len)) {
-                /* need more space, got creative with the print format ? */
-                t = ret - (name.len + delim + dom_len) + 1;
-                ret = sss_packet_grow(packet, t);
-                if (ret != EOK) {
-                    num = 0;
-                    goto done;
-                }
-                delim += t;
-                sss_packet_get_body(packet, &body, &blen);
-
-                /* retry */
-                ret = sss_fqname((char *) &body[rp], name.len + delim + dom_len,
-                                 dom->names, dom, name.str);
-            }
-
-            if (ret != name.len + delim + dom_len - 1) {
+            ret = sss_fqname((char *) &body[rp], fq_len, dom->names, dom, name.str);
+            if (ret < 0 || ret != fq_len - 1) {
                 DEBUG(1, ("Failed to generate a fully qualified name for user "
                           "[%s] in [%s]! Skipping user.\n", name.str, domain));
                 continue;
@@ -2281,8 +2268,7 @@ static int fill_members(struct sss_packet *packet,
     struct sized_string name;
     TALLOC_CTX *tmp_ctx = NULL;
 
-    size_t delim = 0;
-    size_t dom_len = 0;
+    size_t nlen = 0;
 
     uint8_t *body;
     size_t blen;
@@ -2319,9 +2305,6 @@ static int fill_members(struct sss_packet *packet,
             }
         }
 
-        delim = 0;
-        dom_len = 0;
-
         ret = parse_member(tmp_ctx, dom, tmpstr, &member_dom, &name, &add_domain);
         if (ret != EOK) {
             DEBUG(SSSDBG_MINOR_FAILURE,
@@ -2330,44 +2313,33 @@ static int fill_members(struct sss_packet *packet,
         }
 
         if (add_domain) {
-            delim = 1;
-            dom_len = sss_fqdom_len(member_dom->names, member_dom);
+            nlen = sss_fqname(NULL, 0, dom->names, dom, name.str);
+            if (nlen >= 0) {
+                nlen += 1;
+            } else {
+                /* Other failures caught below */
+                nlen = 0;
+            }
+        } else {
+            nlen = name.len;
         }
 
-        ret = sss_packet_grow(packet, name.len + delim + dom_len);
+        ret = sss_packet_grow(packet, nlen);
         if (ret != EOK) {
             goto done;
         }
         sss_packet_get_body(packet, &body, &blen);
 
         if (add_domain) {
-            ret = sss_fqname((char *)&body[rzero + rsize],
-                             name.len + delim + dom_len,
+            ret = sss_fqname((char *)&body[rzero + rsize], nlen,
                              member_dom->names, member_dom, name.str);
-            if (ret >= (name.len + delim + dom_len)) {
-                /* need more space,
-                 * got creative with the print format ? */
-                int t = ret - (name.len + delim + dom_len) + 1;
-                ret = sss_packet_grow(packet, t);
-                if (ret != EOK) {
-                    goto done;
-                }
-                sss_packet_get_body(packet, &body, &blen);
-                delim += t;
-
-                /* retry */
-                ret = sss_fqname((char *)&body[rzero + rsize],
-                                name.len + delim + dom_len,
-                                member_dom->names, member_dom, name.str);
-            }
-
-            if (ret != name.len + delim + dom_len - 1) {
+            if (ret < 0 || ret != nlen - 1) {
                 DEBUG(SSSDBG_OP_FAILURE, ("Failed to generate a fully qualified name"
                                           " for member [%s@%s] of group [%s]!"
                                           " Skipping\n", name.str, domain,
                                           (char *)&body[rzero+STRS_ROFFSET]));
                 /* reclaim space */
-                ret = sss_packet_shrink(packet, name.len + delim + dom_len);
+                ret = sss_packet_shrink(packet, nlen);
                 if (ret != EOK) {
                     goto done;
                 }
@@ -2378,7 +2350,7 @@ static int fill_members(struct sss_packet *packet,
             memcpy(&body[rzero + rsize], name.str, name.len);
         }
 
-        rsize += name.len + delim + dom_len;
+        rsize += nlen;
         memnum++;
     }
 
@@ -2409,8 +2381,7 @@ static int fill_grent(struct sss_packet *packet,
     struct sized_string name;
     struct sized_string pwfield;
     struct sized_string fullname;
-    size_t delim = 0;
-    size_t dom_len = 0;
+    size_t fq_len = 0;
     int i = 0;
     int ret, num, memnum;
     size_t rzero, rsize;
@@ -2418,11 +2389,6 @@ static int fill_grent(struct sss_packet *packet,
     const char *domain = dom->name;
     TALLOC_CTX *tmp_ctx = NULL;
 
-    if (add_domain) {
-        delim = 1;
-        dom_len = sss_fqdom_len(dom->names, dom);
-    }
-
     to_sized_string(&pwfield, nctx->pwfield);
 
     num = 0;
@@ -2483,7 +2449,18 @@ static int fill_grent(struct sss_packet *packet,
 
         /* fill in gid and name and set pointer for number of members */
         rsize = STRS_ROFFSET + name.len + pwfield.len; /* name\0x\0 */
-        if (add_domain) rsize += delim + dom_len;
+
+        if (add_domain) {
+            fq_len = sss_fqname(NULL, 0, dom->names, dom, name.str);
+            if (fq_len >= 0) {
+                fq_len += 1;
+                rsize -= name.len;
+                rsize += fq_len;
+            } else {
+                /* Other failures caught below */
+                fq_len = 0;
+            }
+        }
 
         ret = sss_packet_grow(packet, rsize);
         if (ret != EOK) {
@@ -2500,28 +2477,9 @@ static int fill_grent(struct sss_packet *packet,
 
         /*  8-X: sequence of strings (name, passwd, mem..) */
         if (add_domain) {
-            ret = sss_fqname((char *)&body[rzero+STRS_ROFFSET],
-                             name.len + delim + dom_len,
+            ret = sss_fqname((char *)&body[rzero+STRS_ROFFSET], fq_len,
                              dom->names, dom, name.str);
-            if (ret >= (name.len + delim + dom_len)) {
-                /* need more space, got creative with the print format ? */
-                int t = ret - (name.len + delim + dom_len) + 1;
-                ret = sss_packet_grow(packet, t);
-                if (ret != EOK) {
-                    num = 0;
-                    goto done;
-                }
-                sss_packet_get_body(packet, &body, &blen);
-                rsize += t;
-                delim += t;
-
-                /* retry */
-                ret = sss_fqname((char *)&body[rzero+STRS_ROFFSET],
-                                 name.len + delim + dom_len,
-                                 dom->names, dom, name.str);
-            }
-
-            if (ret != name.len + delim + dom_len - 1) {
+            if (ret < 0 || ret != fq_len - 1) {
                 DEBUG(1, ("Failed to generate a fully qualified name for"
                           " group [%s] in [%s]! Skipping\n", name.str, domain));
                 /* reclaim space */
diff --git a/src/tests/cmocka/test_fqnames.c b/src/tests/cmocka/test_fqnames.c
index e0735083a632a253afc9c676782968fc495b6305..90f60c5be89415a7f9686cabde3af8f4c8742885 100644
--- a/src/tests/cmocka/test_fqnames.c
+++ b/src/tests/cmocka/test_fqnames.c
@@ -76,7 +76,6 @@ void test_default(void **state)
     char *fqdn;
     const int fqdn_size = 255;
     char fqdn_s[fqdn_size];
-    size_t domsize;
 
     if (test_ctx == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, ("Type mismatch\n"));
@@ -87,7 +86,6 @@ void test_default(void **state)
                                    "(?P<name>[^@]+)@?(?P<domain>[^@]*$)",
                                    "%1$s@%2$s", &test_ctx->nctx);
     assert_int_equal(ret, EOK);
-    assert_int_equal(test_ctx->nctx->fq_flags, FQ_FMT_NAME | FQ_FMT_DOMAIN);
 
     fqdn = sss_tc_fqname(test_ctx, test_ctx->nctx, test_ctx->dom, NAME);
     assert_non_null(fqdn);
@@ -98,9 +96,6 @@ void test_default(void **state)
     assert_int_equal(ret + 1, sizeof(NAME"@"DOMNAME));
     assert_string_equal(fqdn_s, NAME"@"DOMNAME);
 
-    domsize = sss_fqdom_len(test_ctx->nctx, test_ctx->dom);
-    assert_int_equal(domsize, sizeof(DOMNAME)-1);
-
     talloc_free(test_ctx->nctx);
 }
 
@@ -113,7 +108,6 @@ void test_all(void **state)
     char *fqdn;
     const int fqdn_size = 255;
     char fqdn_s[fqdn_size];
-    size_t domsize;
 
     if (test_ctx == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, ("Type mismatch\n"));
@@ -124,8 +118,6 @@ void test_all(void **state)
                                    "(?P<name>[^@]+)@?(?P<domain>[^@]*$)",
                                    "%1$s@%2$s@%3$s", &test_ctx->nctx);
     assert_int_equal(ret, EOK);
-    assert_int_equal(test_ctx->nctx->fq_flags,
-                     FQ_FMT_NAME | FQ_FMT_DOMAIN | FQ_FMT_FLAT_NAME);
 
     fqdn = sss_tc_fqname(test_ctx, test_ctx->nctx, test_ctx->dom, NAME);
     assert_non_null(fqdn);
@@ -136,9 +128,6 @@ void test_all(void **state)
     assert_int_equal(ret + 1, sizeof(NAME"@"DOMNAME"@"FLATNAME));
     assert_string_equal(fqdn_s, NAME"@"DOMNAME"@"FLATNAME);
 
-    domsize = sss_fqdom_len(test_ctx->nctx, test_ctx->dom);
-    assert_int_equal(domsize, sizeof(DOMNAME)-1 + sizeof(FLATNAME)-1);
-
     talloc_free(test_ctx->nctx);
 }
 
@@ -151,7 +140,6 @@ void test_flat(void **state)
     char *fqdn;
     const int fqdn_size = 255;
     char fqdn_s[fqdn_size];
-    size_t domsize;
 
     if (test_ctx == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, ("Type mismatch\n"));
@@ -162,7 +150,6 @@ void test_flat(void **state)
                                    "(?P<name>[^@]+)@?(?P<domain>[^@]*$)",
                                    "%1$s@%3$s", &test_ctx->nctx);
     assert_int_equal(ret, EOK);
-    assert_int_equal(test_ctx->nctx->fq_flags, FQ_FMT_NAME | FQ_FMT_FLAT_NAME);
 
     fqdn = sss_tc_fqname(test_ctx, test_ctx->nctx, test_ctx->dom, NAME);
     assert_non_null(fqdn);
@@ -173,9 +160,6 @@ void test_flat(void **state)
     assert_int_equal(ret + 1, sizeof(NAME"@"FLATNAME));
     assert_string_equal(fqdn_s, NAME"@"FLATNAME);
 
-    domsize = sss_fqdom_len(test_ctx->nctx, test_ctx->dom);
-    assert_int_equal(domsize, sizeof(FLATNAME)-1);
-
     talloc_free(test_ctx->nctx);
 }
 
@@ -188,7 +172,6 @@ void test_flat_fallback(void **state)
     char *fqdn;
     const int fqdn_size = 255;
     char fqdn_s[fqdn_size];
-    size_t domsize;
 
     if (test_ctx == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, ("Type mismatch\n"));
@@ -199,7 +182,6 @@ void test_flat_fallback(void **state)
                                    "(?P<name>[^@]+)@?(?P<domain>[^@]*$)",
                                    "%1$s@%3$s", &test_ctx->nctx);
     assert_int_equal(ret, EOK);
-    assert_int_equal(test_ctx->nctx->fq_flags, FQ_FMT_NAME | FQ_FMT_FLAT_NAME);
 
     test_ctx->dom->flat_name = NULL;
 
@@ -215,9 +197,6 @@ void test_flat_fallback(void **state)
     assert_int_equal(ret + 1, sizeof(NAME"@"DOMNAME));
     assert_string_equal(fqdn_s, NAME"@"DOMNAME);
 
-    domsize = sss_fqdom_len(test_ctx->nctx, test_ctx->dom);
-    assert_int_equal(domsize, sizeof(DOMNAME)-1);
-
     talloc_free(test_ctx->nctx);
 }
 
diff --git a/src/tests/cmocka/test_nss_srv.c b/src/tests/cmocka/test_nss_srv.c
index d1a4d4bd8fd793a0c8da1d3c508092b345dc78bb..4db108a79df9d3ec5f6f51bb2356f6f36a022a65 100644
--- a/src/tests/cmocka/test_nss_srv.c
+++ b/src/tests/cmocka/test_nss_srv.c
@@ -540,8 +540,8 @@ void test_nss_getpwnam_fqdn(void **state)
  * Check that FQDN processing is able to handle arbitrarily sized
  * delimeter
  */
-static int test_nss_getpwnam_check_resize_fqdn(uint32_t status,
-                                               uint8_t *body, size_t blen)
+static int test_nss_getpwnam_check_fancy_fqdn(uint32_t status,
+                                              uint8_t *body, size_t blen)
 {
     struct passwd pwd;
     errno_t ret;
@@ -555,29 +555,28 @@ static int test_nss_getpwnam_check_resize_fqdn(uint32_t status,
 
     assert_int_equal(pwd.pw_uid, 125);
     assert_int_equal(pwd.pw_gid, 458);
-    assert_string_equal(pwd.pw_name, "testuser_fqdn_resize@@@@@"TEST_DOM_NAME);
+    assert_string_equal(pwd.pw_name, "testuser_fqdn_fancy@@@@@"TEST_DOM_NAME);
     assert_string_equal(pwd.pw_shell, "/bin/sh");
     return EOK;
 }
 
-void test_nss_getpwnam_fqdn_resize(void **state)
+void test_nss_getpwnam_fqdn_fancy(void **state)
 {
     errno_t ret;
 
     /* Prime the cache with a valid user */
     ret = sysdb_add_user(nss_test_ctx->tctx->dom,
-                         "testuser_fqdn_resize", 125, 458, "test user",
+                         "testuser_fqdn_fancy", 125, 458, "test user",
                          "/home/testuser", "/bin/sh", NULL,
                          NULL, 300, 0);
     assert_int_equal(ret, EOK);
 
-    mock_input_user_or_group("testuser_fqdn_resize@"TEST_DOM_NAME);
+    mock_input_user_or_group("testuser_fqdn_fancy@"TEST_DOM_NAME);
     will_return(__wrap_sss_packet_get_cmd, SSS_NSS_GETPWNAM);
     mock_fill_user();
-    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
 
     /* Query for that user, call a callback when command finishes */
-    set_cmd_cb(test_nss_getpwnam_check_resize_fqdn);
+    set_cmd_cb(test_nss_getpwnam_check_fancy_fqdn);
     nss_test_ctx->cctx->rctx->domains[0].fqnames = true;
     ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
                           nss_test_ctx->nss_cmds);
@@ -1298,7 +1297,7 @@ void nss_subdom_test_setup(void **state)
     nss_test_ctx->subdom = subdomain;
 }
 
-void nss_fqdn_resize_test_setup(void **state)
+void nss_fqdn_fancy_test_setup(void **state)
 {
     struct sss_test_conf_param params[] = {
         { "enumerate", "false" },
@@ -1339,8 +1338,8 @@ int main(int argc, const char *argv[])
                                  nss_test_setup, nss_test_teardown),
         unit_test_setup_teardown(test_nss_getpwnam_fqdn,
                                  nss_fqdn_test_setup, nss_test_teardown),
-        unit_test_setup_teardown(test_nss_getpwnam_fqdn_resize,
-                                 nss_fqdn_resize_test_setup, nss_test_teardown),
+        unit_test_setup_teardown(test_nss_getpwnam_fqdn_fancy,
+                                 nss_fqdn_fancy_test_setup, nss_test_teardown),
         unit_test_setup_teardown(test_nss_getgrnam_no_members,
                                  nss_test_setup, nss_test_teardown),
         unit_test_setup_teardown(test_nss_getgrnam_members,
diff --git a/src/tests/cmocka/test_utils.c b/src/tests/cmocka/test_utils.c
index eeef9ee0ca3bf1a8acea1d3c436a8816fae4fb96..13a67f7564f1126900f9fb15d4d9af45127df290 100644
--- a/src/tests/cmocka/test_utils.c
+++ b/src/tests/cmocka/test_utils.c
@@ -531,8 +531,7 @@ void test_sss_names_init(void **state)
     assert_int_equal(ret, EOK);
     assert_non_null(names_ctx);
     assert_string_equal(names_ctx->re_pattern, GLOBAL_RE_EXPRESSION);
-    assert_string_equal(names_ctx->fq_fmt, GLOBAL_FULL_NAME_FORMAT"%3$s");
-    assert_int_equal(names_ctx->fq_flags, FQ_FMT_NAME|FQ_FMT_DOMAIN);
+    assert_string_equal(names_ctx->fq_fmt, GLOBAL_FULL_NAME_FORMAT);
 
     talloc_free(names_ctx);
 
@@ -541,8 +540,7 @@ void test_sss_names_init(void **state)
     assert_int_equal(ret, EOK);
     assert_non_null(names_ctx);
     assert_string_equal(names_ctx->re_pattern, DOMAIN_RE_EXPRESSION);
-    assert_string_equal(names_ctx->fq_fmt, DOMAIN_FULL_NAME_FORMAT"%2$s");
-    assert_int_equal(names_ctx->fq_flags, FQ_FMT_NAME|FQ_FMT_FLAT_NAME);
+    assert_string_equal(names_ctx->fq_fmt, DOMAIN_FULL_NAME_FORMAT);
 
     talloc_free(names_ctx);
 }
diff --git a/src/tests/safe-printf-tests.c b/src/tests/safe-printf-tests.c
new file mode 100644
index 0000000000000000000000000000000000000000..d0d17c93bc6ca31b8aed0a5865f162dd223c942a
--- /dev/null
+++ b/src/tests/safe-printf-tests.c
@@ -0,0 +1,268 @@
+/*
+ * This file originated in realmd
+ *
+ * Copyright 2012 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@gnome.org>
+ */
+
+#include "config.h"
+
+#include "src/util/safe-printf.h"
+
+#include <check.h>
+#include <popt.h>
+#include <string.h>
+#include <talloc.h>
+
+typedef struct {
+    const char *format;
+    const char *args[8];
+    const char *result;
+} Fixture;
+
+static const Fixture fixtures[] = {
+    {
+      /* Just a bog standard string */
+      "%s", { "blah", NULL, },
+      "blah"
+    },
+    {
+      /* Empty to print */
+      "%s", { "", NULL, },
+      ""
+    },
+    {
+      /* Nothing to print */
+      "", { "blah", NULL, },
+      ""
+    },
+    {
+      /* Width right aligned */
+      "%8s", { "blah", NULL, },
+      "    blah"
+    },
+    {
+      /* Width left aligned */
+      "whoop %-8s doo", { "dee", NULL, },
+      "whoop dee      doo"
+    },
+    {
+      /* Width space aligned (ignored) */
+      "whoop % 8s doo", { "dee", NULL, },
+      "whoop      dee doo"
+    },
+    {
+      /* Width left space aligned (ignored) */
+      "whoop % -8s doo", { "dee", NULL, },
+      "whoop dee      doo"
+    },
+    {
+      /* Precision 1 digit */
+      "whoop %.3s doo", { "deedle-dee", NULL, },
+      "whoop dee doo"
+    },
+    {
+      /* Precision, N digits */
+      "whoop %.10s doo", { "deedle-dee-deedle-do-deedle-dum", NULL, },
+      "whoop deedle-dee doo"
+    },
+    {
+      /* Precision, zero digits */
+      "whoop %.s doo", { "deedle", NULL, },
+      "whoop  doo"
+    },
+    {
+      /* Multiple simple arguments */
+      "space %s %s", { "man", "dances", NULL, },
+      "space man dances"
+    },
+    {
+      /* Literal percent */
+      "100%% of space folk dance", { NULL, },
+      "100% of space folk dance"
+    },
+    {
+      /* Multiple simple arguments */
+      "space %2$s %1$s", { "dances", "man", NULL, },
+      "space man dances"
+    },
+    {
+      /* Skipping an argument (not supported by standard printf) */
+      "space %2$s dances", { "dances", "man", NULL, },
+      "space man dances"
+    },
+
+    /* Failures start here */
+
+    {
+      /* Unsupported conversion */
+      "%x", { "blah", NULL, },
+      NULL
+    },
+    {
+      /* Bad positional argument */
+      "space %55$s dances", { "dances", "man", NULL, },
+      NULL
+    },
+    {
+      /* Zero positional argument */
+      "space %0$s dances", { "dances", "man", NULL, },
+      NULL
+    },
+    {
+      /* Too many args used */
+      "%s %s dances", { "space", NULL, },
+      NULL
+    },
+};
+
+
+static void
+callback (void *data, const char *piece, size_t len)
+{
+    char **str = data;
+    *str = talloc_strndup_append (*str, piece, len);
+}
+
+START_TEST(test_safe_printf)
+{
+    const Fixture *fixture;
+    char *out;
+    int num_args;
+    int ret;
+    void *mem_ctx;
+
+    fixture = &fixtures[_i];
+    mem_ctx = talloc_init("safe-printf");
+
+    for (num_args = 0; fixture->args[num_args] != NULL; )
+        num_args++;
+
+    out = talloc_strdup (mem_ctx, "");
+    ret = safe_printf_cb(callback, &out, fixture->format, (const char * const*)fixture->args, num_args);
+    if (fixture->result) {
+        ck_assert_int_ge(ret, 0);
+        ck_assert_str_eq(out, fixture->result);
+        ck_assert_int_eq(ret, strlen(out));
+    } else {
+        ck_assert_int_lt(ret, 0);
+    }
+
+    talloc_free(mem_ctx);
+}
+END_TEST
+
+START_TEST(test_safe_snprintf)
+{
+    char buffer[8];
+    int ret;
+
+    ret = safe_snprintf(buffer, 8, "%s", "space", "man", NULL);
+    ck_assert_int_eq(ret, 5);
+    ck_assert_str_eq(buffer, "space");
+
+    ret = safe_snprintf(buffer, 8, "", "space", "man", NULL);
+    ck_assert_int_eq(ret, 0);
+    ck_assert_str_eq(buffer, "");
+
+    ret = safe_snprintf(buffer, 8, "the %s %s dances away", "space", "man", NULL);
+    ck_assert_int_eq(ret, 25);
+    ck_assert_str_eq(buffer, "the spa");
+
+    ret = safe_snprintf(NULL, 0, "the %s %s dances away", "space", "man", NULL);
+    ck_assert_int_eq(ret, 25);
+
+    ret = safe_snprintf(buffer, 8, "%5$s", NULL);
+    ck_assert_int_lt(ret, 0);
+}
+END_TEST
+
+START_TEST(test_safe_asprintf)
+{
+    char *buffer;
+    int ret;
+
+    ret = safe_asprintf(&buffer, "%s", "space", "man", NULL);
+    ck_assert_int_eq(ret, 5);
+    ck_assert_str_eq(buffer, "space");
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "", "space", "man", NULL);
+    ck_assert_int_eq(ret, 0);
+    ck_assert_str_eq(buffer, "");
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "the %s %s dances away", "space", "man", NULL);
+    ck_assert_int_eq(ret, 25);
+    ck_assert_str_eq(buffer, "the space man dances away");
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "%1$s %1$s %1$s %1$s %1$s %1$s", "space man", NULL);
+    ck_assert_int_eq(ret, 59);
+    ck_assert_str_eq(buffer, "space man space man space man space man space man space man");
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "%5$s", NULL);
+    ck_assert_int_lt(ret, 0);
+}
+END_TEST
+
+static Suite *
+create_safe_printf_suite(void)
+{
+    Suite *s = suite_create("safe-printf");
+    TCase *tc_printf = tcase_create("printf");
+
+    /* One for each fixture */
+    tcase_add_loop_test(tc_printf, test_safe_printf, 0, (sizeof (fixtures) / sizeof (fixtures[0])));
+
+    tcase_add_test(tc_printf, test_safe_snprintf);
+    tcase_add_test(tc_printf, test_safe_asprintf);
+
+    suite_add_tcase(s, tc_printf);
+
+    return s;
+}
+
+int
+main(int argc, const char *argv[])
+{
+    int opt;
+    poptContext pc;
+    int failure_count;
+    Suite *suite;
+    SRunner *sr;
+
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        POPT_TABLEEND
+    };
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    suite = create_safe_printf_suite();
+    sr = srunner_create(suite);
+    /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+    srunner_run_all(sr, CK_ENV);
+    failure_count = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/src/util/safe-printf.c b/src/util/safe-printf.c
new file mode 100644
index 0000000000000000000000000000000000000000..b98b2a4d7800c4aafc15c47b5b83f55375f6724c
--- /dev/null
+++ b/src/util/safe-printf.c
@@ -0,0 +1,349 @@
+/*
+ * This file originated in the realmd project
+ *
+ * Copyright 2013 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@redhat.com>
+ */
+
+#include "config.h"
+
+#include "safe-printf.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#ifndef MIN
+#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
+#endif
+
+static void
+safe_padding (int count,
+              int *total,
+              void (* callback) (void *, const char *, size_t),
+              void *data)
+{
+	char eight[] = "        ";
+	int num;
+
+	while (count > 0) {
+		num = MIN (count, 8);
+		callback (data, eight, num);
+		count -= num;
+		*total += num;
+	}
+}
+
+static void
+dummy_callback (void *data,
+                const char *piece,
+                size_t len)
+{
+
+}
+
+int
+safe_printf_cb (void (* callback) (void *, const char *, size_t),
+                void *data,
+                const char *format,
+                const char * const args[],
+                int num_args)
+{
+	int at_arg = 0;
+	const char *cp;
+	int precision;
+	int width;
+	int len;
+	const char *value;
+	int total;
+	int left;
+
+	if (!callback)
+		callback = dummy_callback;
+
+	total = 0;
+	cp = format;
+
+	while (*cp) {
+
+		/* Piece of raw string */
+		if (*cp != '%') {
+			len = strcspn (cp, "%");
+			callback (data, cp, len);
+			total += len;
+			cp += len;
+			continue;
+		}
+
+		cp++;
+
+		/* An literal percent sign? */
+		if (*cp == '%') {
+			callback (data, "%", 1);
+			total++;
+			cp++;
+			continue;
+		}
+
+		value = NULL;
+		left = 0;
+		precision = -1;
+		width = -1;
+
+		/* Test for positional argument.  */
+		if (*cp >= '0' && *cp <= '9') {
+			/* Look-ahead parsing, otherwise skipped */
+			if (cp[strspn (cp, "0123456789")] == '$') {
+				unsigned int n = 0;
+				for (; *cp >= '0' && *cp <= '9'; cp++)
+					n = 10 * n + (*cp - '0');
+				/* Positional argument 0 is invalid. */
+				if (n == 0)
+					return -1;
+				/* Positional argument N too high */
+				if (n > num_args)
+					return -1;
+				value = args[n - 1];
+				cp++; /* $ */
+			}
+		}
+
+		/* Read the supported flags. */
+		for (; ; cp++) {
+			if (*cp == '-')
+				left = 1;
+			/* Supported but ignored */
+			else if (*cp != ' ')
+				break;
+		}
+
+		/* Parse the width. */
+		if (*cp >= '0' && *cp <= '9') {
+			width = 0;
+			for (; *cp >= '0' && *cp <= '9'; cp++)
+				width = 10 * width + (*cp - '0');
+		}
+
+		/* Parse the precision. */
+		if (*cp == '.') {
+			precision = 0;
+			for (cp++; *cp >= '0' && *cp <= '9'; cp++)
+				precision = 10 * precision + (*cp - '0');
+		}
+
+		/* Read the conversion character.  */
+		switch (*cp++) {
+		case 's':
+			/* Non-positional argument */
+			if (value == NULL) {
+				/* Too many arguments used */
+				if (at_arg == num_args)
+					return -1;
+				value = args[at_arg++];
+			}
+			break;
+
+		/* No other conversion characters are supported */
+		default:
+			return -1;
+		}
+
+		/* How many characters are we printing? */
+		len = strlen (value);
+		if (precision >= 0)
+			len = MIN (precision, len);
+
+		/* Do we need padding? */
+		safe_padding (left ? 0 : width - len, &total, callback, data);
+
+		/* The actual data */;
+		callback (data, value, len);
+		total += len;
+
+		/* Do we need padding? */
+		safe_padding (left ? width - len : 0, &total, callback, data);
+	}
+
+	return total;
+}
+
+struct sprintf_ctx {
+	char *data;
+	size_t length;
+	size_t alloc;
+};
+
+static void
+asprintf_callback (void *data,
+                   const char *piece,
+                   size_t length)
+{
+	struct sprintf_ctx *cx = data;
+	void *mem;
+
+	if (!cx->data)
+		return;
+
+	/* Reallocate if necessary */
+	if (cx->length + length + 1 > cx->alloc) {
+		cx->alloc += MAX (length + 1, 1024);
+		mem = realloc (cx->data, cx->alloc);
+		if (mem == NULL) {
+			free (cx->data);
+			cx->data = NULL;
+			return;
+		}
+		cx->data = mem;
+	}
+
+	memcpy (cx->data + cx->length, piece, length);
+	cx->length += length;
+	cx->data[cx->length] = 0;
+}
+
+static const char **
+valist_to_args (va_list va,
+                int *num_args)
+{
+	int alo_args;
+	const char **args;
+	const char *arg;
+	void *mem;
+
+	*num_args = alo_args = 0;
+	args = NULL;
+
+	for (;;) {
+		arg = va_arg (va, const char *);
+		if (arg == NULL)
+			break;
+		if (*num_args == alo_args) {
+			alo_args += 8;
+			mem = realloc (args, sizeof (const char *) * alo_args);
+			if (!mem) {
+				free (args);
+				return NULL;
+			}
+			args = mem;
+		}
+		args[(*num_args)++] = arg;
+	}
+
+	return args;
+}
+
+int
+safe_asprintf (char **strp,
+               const char *format,
+               ...)
+{
+	struct sprintf_ctx cx;
+	const char **args;
+	int num_args;
+	va_list va;
+	int ret;
+	int i;
+
+	va_start (va, format);
+	args = valist_to_args (va, &num_args);
+	va_end (va);
+
+	if (args == NULL)
+		return -1;
+
+	/* Preallocate a pretty good guess */
+	cx.alloc = strlen (format) + 1;
+	for (i = 0; i < num_args; i++)
+		cx.alloc += strlen (args[i]);
+
+	cx.data = malloc (cx.alloc);
+	if (!cx.data) {
+		free (args);
+		return -1;
+	}
+
+	cx.data[0] = '\0';
+	cx.length = 0;
+
+	ret = safe_printf_cb (asprintf_callback, &cx, format, args, num_args);
+	if (cx.data == NULL)
+		ret = -1;
+	if (ret < 0)
+		free (cx.data);
+	else
+		*strp = cx.data;
+
+	free (args);
+	return ret;
+}
+
+static void
+snprintf_callback (void *data,
+                   const char *piece,
+                   size_t length)
+{
+	struct sprintf_ctx *cx = data;
+
+	/* Don't copy if too much data */
+	if (cx->length > cx->alloc)
+		length = 0;
+	else if (cx->length + length > cx->alloc)
+		length = cx->alloc - cx->length;
+	else
+		length = length;
+
+	if (length > 0)
+		memcpy (cx->data + cx->length, piece, length);
+
+	/* Null termination happens later */
+	cx->length += length;
+}
+
+int
+safe_snprintf (char *str,
+               size_t len,
+               const char *format,
+               ...)
+{
+	struct sprintf_ctx cx;
+	int num_args;
+	va_list va;
+	const char **args;
+	int ret;
+
+	cx.data = str;
+	cx.length = 0;
+	cx.alloc = len;
+
+	va_start (va, format);
+	args = valist_to_args (va, &num_args);
+	va_end (va);
+
+	if (args == NULL)
+		return -1;
+
+	if (len)
+		cx.data[0] = '\0';
+
+	ret = safe_printf_cb (snprintf_callback, &cx, format, args, num_args);
+	if (ret < 0)
+		return ret;
+
+	/* Null terminate appropriately */
+	if (len > 0)
+		cx.data[MIN(cx.length, len - 1)] = '\0';
+
+	free (args);
+	return ret;
+}
diff --git a/src/util/safe-printf.h b/src/util/safe-printf.h
new file mode 100644
index 0000000000000000000000000000000000000000..dee137d4269c3f875e1b227626b8e3ee3ec61421
--- /dev/null
+++ b/src/util/safe-printf.h
@@ -0,0 +1,46 @@
+/*
+ * This file originated in the realmd project
+ *
+ * Copyright 2013 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@redhat.com>
+ */
+
+#include "config.h"
+
+#ifndef __SAFE_PRINTF_H__
+#define __SAFE_PRINTF_H__
+
+#include <stdlib.h>
+
+#ifndef GNUC_NULL_TERMINATED
+#if __GNUC__ >= 4
+#define GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+#else
+#define GNUC_NULL_TERMINATED
+#endif
+#endif
+
+int        safe_asprintf     (char **strp,
+                              const char *format,
+                              ...) GNUC_NULL_TERMINATED;
+
+int        safe_snprintf     (char *str,
+                              size_t len,
+                              const char *format,
+                              ...) GNUC_NULL_TERMINATED;
+
+int        safe_printf_cb    (void (* callback) (void *data, const char *piece, size_t len),
+                              void *data,
+                              const char *format,
+                              const char * const args[],
+                              int num_args);
+
+#endif /* __SAFE_PRINTF_H__ */
diff --git a/src/util/usertools.c b/src/util/usertools.c
index cd57d0c574755bf04a9f090ba3e8bd568cb90136..e6d38bac19febef3f93970779fbe5140308d53bf 100644
--- a/src/util/usertools.c
+++ b/src/util/usertools.c
@@ -26,6 +26,7 @@
 
 #include "confdb/confdb.h"
 #include "util/util.h"
+#include "util/safe-printf.h"
 #include "responder/common/responder.h"
 
 #ifdef HAVE_LIBPCRE_LESSER_THAN_7
@@ -138,18 +139,7 @@ done:
 
 static errno_t sss_fqnames_init(struct sss_names_ctx *nctx, const char *fq_fmt)
 {
-    struct pattern_desc {
-        const char *pattern;
-        const char *desc;
-        int flag;
-    };
-
-    struct pattern_desc fqname_patterns[] = {
-        { "%1$s", "user name", FQ_FMT_NAME },
-        { "%2$s", "domain name", FQ_FMT_DOMAIN },
-        { "%3$s", "domain flat name", FQ_FMT_FLAT_NAME },
-        { NULL, NULL, 0 }
-    };
+    char *fq;
 
     nctx->fq_fmt = talloc_strdup(nctx, fq_fmt);
     if (nctx->fq_fmt == NULL) {
@@ -158,34 +148,20 @@ static errno_t sss_fqnames_init(struct sss_names_ctx *nctx, const char *fq_fmt)
 
     DEBUG(SSSDBG_CONF_SETTINGS, ("Using fq format [%s].\n", nctx->fq_fmt));
 
-    /* Fail if the name specifier is missing and warn if the domain
-     * specifier is missing
-     */
-    if (strstr(fq_fmt, fqname_patterns[0].pattern) == NULL) {
+    /* Fail if the name specifier is missing, or if the format is
+     * invalid */
+    fq = sss_tc_fqname2 (nctx, nctx, "unused.example.com", "unused", "the-test-user");
+    if (fq == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              ("The fq format is invalid [%s]", nctx->fq_fmt));
+        return EINVAL;
+    } else if (strstr (fq, "the-test-user") == NULL) {
         DEBUG(SSSDBG_OP_FAILURE,
               ("Username pattern not found in [%s]\n", nctx->fq_fmt));
         return ENOENT;
     }
-    nctx->fq_flags = FQ_FMT_NAME;
-
-    for (int i = 1; fqname_patterns[i].pattern; i++) {
-        char *s;
-        s = strstr(fq_fmt, fqname_patterns[i].pattern);
-        if (s == NULL) {
-            /* Append the format specifier */
-            nctx->fq_fmt = talloc_strdup_append(nctx->fq_fmt,
-                                                fqname_patterns[i].pattern);
-            if (nctx->fq_fmt == NULL) {
-                return ENOMEM;
-            }
-            continue;
-        }
-
-        DEBUG(SSSDBG_CONF_SETTINGS,
-              ("Found the pattern for %s\n", fqname_patterns[i].desc));
-        nctx->fq_flags |= fqname_patterns[i].flag;
-    }
 
+    talloc_free (fq);
     return EOK;
 }
 
@@ -568,19 +544,13 @@ sss_get_cased_name_list(TALLOC_CTX *mem_ctx, const char * const *orig,
 }
 
 static inline const char *
-safe_fq_str(struct sss_names_ctx *nctx, uint8_t part, const char *str)
-{
-    return nctx->fq_flags & part ? str : "";
-}
-
-static inline const char *
-safe_flat_name(struct sss_names_ctx *nctx, struct sss_domain_info *domain)
+calc_flat_name(struct sss_domain_info *domain)
 {
     const char *s;
 
-    s = safe_fq_str(nctx, FQ_FMT_FLAT_NAME, domain->flat_name);
+    s = domain->flat_name;
     if (s == NULL) {
-        DEBUG(SSSDBG_CRIT_FAILURE, ("Flat name requested but domain has no"
+        DEBUG(SSSDBG_MINOR_FAILURE, ("Flat name requested but domain has no"
               "flat name set, falling back to domain name\n"));
         s = domain->name;
     }
@@ -588,28 +558,24 @@ safe_flat_name(struct sss_names_ctx *nctx, struct sss_domain_info *domain)
     return s;
 }
 
-static inline size_t
-fq_part_len(struct sss_names_ctx *nctx, struct sss_domain_info *dom,
-            uint8_t part, const char *str)
-{
-    const char *s = str;
-
-    if (part == FQ_FMT_FLAT_NAME) {
-        s = safe_flat_name(nctx, dom);
-    }
-    return nctx->fq_flags & part ? strlen(s) : 0;
-}
-
 char *
 sss_tc_fqname(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx,
               struct sss_domain_info *domain, const char *name)
 {
     if (domain == NULL || nctx == NULL) return NULL;
 
-    return talloc_asprintf(mem_ctx, nctx->fq_fmt,
-                           safe_fq_str(nctx, FQ_FMT_NAME, name),
-                           safe_fq_str(nctx, FQ_FMT_DOMAIN, domain->name),
-                           safe_flat_name(nctx, domain));
+    return sss_tc_fqname2 (mem_ctx, nctx, domain->name,
+                           calc_flat_name (domain), name);
+}
+
+static void
+safe_talloc_callback (void *data,
+                      const char *piece,
+                      size_t len)
+{
+    char **output = data;
+    if (*output != NULL)
+        *output = talloc_strndup_append(*output, piece, len);
 }
 
 char *
@@ -617,12 +583,15 @@ sss_tc_fqname2(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx,
                const char *domain_name, const char *flat_dom_name,
                const char *name)
 {
+    const char *args[] = { name, domain_name, flat_dom_name, NULL };
+    char *output;
+
     if (nctx == NULL) return NULL;
 
-    return talloc_asprintf(mem_ctx, nctx->fq_fmt,
-                           safe_fq_str(nctx, FQ_FMT_NAME, name),
-                           safe_fq_str(nctx, FQ_FMT_DOMAIN, domain_name),
-                           safe_fq_str(nctx, FQ_FMT_FLAT_NAME, flat_dom_name));
+    output = talloc_strdup(mem_ctx, "");
+    if (safe_printf_cb(safe_talloc_callback, &output, nctx->fq_fmt, args, 3) < 0)
+        output = NULL;
+    return output;
 }
 
 int
@@ -631,19 +600,8 @@ sss_fqname(char *str, size_t size, struct sss_names_ctx *nctx,
 {
     if (domain == NULL || nctx == NULL) return -EINVAL;
 
-    return snprintf(str, size, nctx->fq_fmt,
-                    safe_fq_str(nctx, FQ_FMT_NAME, name),
-                    safe_fq_str(nctx, FQ_FMT_DOMAIN, domain->name),
-                    safe_flat_name(nctx, domain));
-}
-
-size_t
-sss_fqdom_len(struct sss_names_ctx *nctx,
-              struct sss_domain_info *domain)
-{
-    size_t len = fq_part_len(nctx, domain, FQ_FMT_DOMAIN, domain->name);
-    len += fq_part_len(nctx, domain, FQ_FMT_FLAT_NAME, domain->flat_name);
-    return len;
+    return safe_snprintf(str, size, nctx->fq_fmt,
+                         name, domain->name, calc_flat_name (domain), NULL);
 }
 
 char *
diff --git a/src/util/util.h b/src/util/util.h
index 1012705715bafc55a9115b00fb39688140fc527e..4a9dbb75f2aa6f7fc24c9a549c65ede8c7464583 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -324,14 +324,9 @@ char *get_username_from_uid(TALLOC_CTX *mem_ctx, uid_t uid);
 
 char *get_uppercase_realm(TALLOC_CTX *memctx, const char *name);
 
-#define FQ_FMT_NAME         0x01
-#define FQ_FMT_DOMAIN       0x02
-#define FQ_FMT_FLAT_NAME    0x04
-
 struct sss_names_ctx {
     char *re_pattern;
     char *fq_fmt;
-    uint8_t fq_flags;
 
     pcre *re;
 };
@@ -384,15 +379,14 @@ sss_tc_fqname2(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx,
 /* Return fully-qualified name formatted according to the fq_fmt. The buffer in "str" is
  * "size" bytes long. Returns the number of bytes written on success or a negative
  * value of failure.
+ *
+ * Pass a zero size to calculate the length that would be needed by the fully-qualified
+ * name.
  */
 int
 sss_fqname(char *str, size_t size, struct sss_names_ctx *nctx,
            struct sss_domain_info *domain, const char *name);
 
-size_t
-sss_fqdom_len(struct sss_names_ctx *nctx,
-              struct sss_domain_info *domain);
-
 /* Subdomains use fully qualified names in the cache while primary domains use
  * just the name. Return either of these for a specified domain or subdomain
  */
-- 
1.8.4.2

