summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE6
-rw-r--r--Makefile.am11
-rw-r--r--NEWS5
-rw-r--r--configure.ac2
-rw-r--r--kasetkey/kasetkey.c221
-rw-r--r--portable/strlcat.c42
-rw-r--r--portable/strlcpy.c40
-rw-r--r--system.h6
-rw-r--r--tests/TESTS2
-rw-r--r--tests/portable/strlcat-t.c77
-rw-r--r--tests/portable/strlcat.c2
-rw-r--r--tests/portable/strlcpy-t.c72
-rw-r--r--tests/portable/strlcpy.c2
13 files changed, 426 insertions, 62 deletions
diff --git a/LICENSE b/LICENSE
index 24be219..a04578d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -50,10 +50,12 @@ The file portable/snprintf.c is released under the following license:
It may be used for any purpose as long as this notice remains intact
on all source code distributions
-The files portable/asprintf.c, portable/dummy.c and util/concat.c have
-been placed in the public domain by their author.
+The files portable/asprintf.c, portable/dummy.c, portable/strlcat.c,
+portable/strlcpy.c, and util/concat.c have been placed in the public
+domain by their author.
The files tests/libtest.c, tests/libtest.h, tests/portable/snprintf-t.c,
+tests/portable/strlcat-t.c, tests/portable/strlcpy-t.c,
tests/util/concat-t.c, tests/util/messages-t.c, tests/util/xmalloc-t, and
tests/util/xmalloc.c are released under the following copyright and
license:
diff --git a/Makefile.am b/Makefile.am
index ba1ff21..fe0a812 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -121,7 +121,8 @@ clean-local:
# The bits below are for the test suite, not for the main package.
check_PROGRAMS = tests/runtests tests/portable/asprintf-t \
- tests/portable/snprintf-t tests/util/concat-t tests/util/messages-t \
+ tests/portable/snprintf-t tests/portable/strlcat-t \
+ tests/portable/strlcpy-t tests/util/concat-t tests/util/messages-t \
tests/util/xmalloc
check_LIBRARIES = tests/libtest.a
tests_libtest_a_SOURCES = tests/libtest.c tests/libtest.h
@@ -135,6 +136,14 @@ tests_portable_snprintf_t_SOURCES = tests/portable/snprintf-t.c \
tests/portable/snprintf.c
tests_portable_snprintf_t_LDADD = tests/libtest.a util/libutil.a \
portable/libportable.a
+tests_portable_strlcat_t_SOURCES = tests/portable/strlcat-t.c \
+ tests/portable/strlcat.c
+tests_portable_strlcat_t_LDADD = tests/libtest.a util/libutil.a \
+ portable/libportable.a
+tests_portable_strlcpy_t_SOURCES = tests/portable/strlcpy-t.c \
+ tests/portable/strlcpy.c
+tests_portable_strlcpy_t_LDADD = tests/libtest.a util/libutil.a \
+ portable/libportable.a
tests_util_concat_t_LDADD = tests/libtest.a util/libutil.a \
portable/libportable.a
tests_util_messages_t_LDADD = tests/libtest.a util/libutil.a \
diff --git a/NEWS b/NEWS
index 8be1343..d56a472 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,11 @@
wallet 0.9 (unreleased)
+ Add support for enabling and disabling principals (clearing or setting
+ the NOTGS flag) and examining principals to kasetkey. This
+ functionality isn't used by wallet (and probably won't be) but is
+ convenient for other users of kasetkey such as kadmin-remctl.
+
Report the correct error message when addprinc fails while creating a
keytab object.
diff --git a/configure.ac b/configure.ac
index 7530691..4f722fc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,7 +26,7 @@ RRA_C_GNU_VAMACROS
AC_CHECK_TYPES([long long])
RRA_FUNC_SNPRINTF
AC_CHECK_FUNCS([setrlimit])
-AC_REPLACE_FUNCS([asprintf])
+AC_REPLACE_FUNCS([asprintf strlcat strlcpy])
AC_ARG_WITH([wallet-server],
[AC_HELP_STRING([--with-wallet-server=HOST], [Default wallet server])],
diff --git a/kasetkey/kasetkey.c b/kasetkey/kasetkey.c
index 19c1ffd..8bcc950 100644
--- a/kasetkey/kasetkey.c
+++ b/kasetkey/kasetkey.c
@@ -1,19 +1,19 @@
-/* $Id$
-**
-** Create or change a principal and/or generate a srvtab.
-**
-** Written by Roland Schemers <schemers@stanford.edu>
-** Updated by Russ Allbery <rra@stanford.edu>
-** Updated again by AAU, Anton Ushakov <antonu@stanford.edu>
-** Copyright 1994, 1998, 1999, 2000, 2006, 2007
-** Board of Trustees, Leland Stanford Jr. University
-**
-** See LICENSE for licensing terms.
-**
-** Sets the key of a principal in the AFS kaserver given a srvtab. This
-** program is now used for synchronization of K5 and K4 and nothing else.
-** It will no longer be used once K4 is retired.
-*/
+/* $Id$
+ *
+ * Create or change a principal and/or generate a srvtab.
+ *
+ * Sets the key of a principal in the AFS kaserver given a srvtab, enables or
+ * disables a principal, or displays information about a principal in an AFS
+ * kaserver.
+ *
+ * Written by Roland Schemers <schemers@stanford.edu>
+ * Updated by Russ Allbery <rra@stanford.edu>
+ * Updated again by Anton Ushakov <antonu@stanford.edu>
+ * Copyright 1994, 1998, 1999, 2000, 2006, 2007, 2008
+ * Board of Trustees, Leland Stanford Jr. University
+ *
+ * See LICENSE for licensing terms.
+ */
#include <config.h>
@@ -37,6 +37,8 @@
#include <afs/cellconfig.h>
#include <ubik.h>
+#include <util/util.h>
+
/* Normally set by the AFS libraries. */
#ifndef SNAME_SZ
# define SNAME_SZ 40
@@ -44,9 +46,11 @@
# define REALM_SZ 40
#endif
-/* AFS currently doesn't prototype this function. Cheat on the first argument
- since it actually takes a function with a completely variable argument
- list. */
+/*
+ * AFS currently doesn't prototype this function. Cheat on the first argument
+ * since it actually takes a function with a completely variable argument
+ * list.
+ */
#if !HAVE_DECL_UBIK_CALL
afs_int32 ubik_Call(void *, struct ubik_client *, afs_int32, ...);
#endif
@@ -60,12 +64,15 @@ struct config {
int debug; /* Whether to enable debugging. */
int init; /* Keyfile initialization. */
int random; /* Randomize the key. */
+ int tgs; /* Enable the principal. */
+ int notgs; /* Disable the princial. */
char *keyfile; /* Name of srvtab to use. */
char *admin; /* Name of ADMIN user to use. */
char *password; /* Password to use. */
char *srvtab; /* srvtab file to generate. */
- char *service; /* Service principal to create. */
- char *delete; /* Service principal to delete. */
+ char *service; /* Principal to create/enable. */
+ char *delete; /* Principal to delete. */
+ char *examine; /* Principal to examine. */
char *k5srvtab; /* K5 converted srvtab to read for key. */
};
@@ -75,14 +82,17 @@ Usage: %s [options]\n\
-a adminuser Admin user\n\
-c k5srvtab Use the key from the given srvtab (for sync w/ K5)\n\
-D service Name of service to delete\n\
- -d turn on debugging\n\
+ -d Turn on debugging\n\
+ -e principal Examine the given principal\n\
-f srvtab Name of srvtab file to create\n\
-h This help\n\
-i Initialize DES key file\n\
-k keyfile File containing srvtab for admin user\n\
+ -n Set the principal NOTGS\n\
-p password Use given password to create key\n\
-r Use random key\n\
-s service Name of service to create\n\
+ -t Set the principal TGS\n\
-v Print version\n\
\n\
To create a srvtab for rcmd.slapshot and be prompted for the admin\n\
@@ -101,40 +111,6 @@ and then create a srvtab for rcmd.slapshot with:\n\
\n";
-/* Report a fatal error. */
-static void
-die(const char *format, ...)
-{
- va_list args;
-
- if (program != NULL)
- fprintf(stderr, "%s: ", program);
- va_start(args, format);
- vfprintf(stderr, format, args);
- va_end(args);
- fprintf(stderr, "\n");
- exit(1);
-}
-
-
-/* Report a fatal error, including strerror information. */
-static void
-sysdie(const char *format, ...)
-{
- int oerrno;
- va_list args;
-
- oerrno = errno;
- if (program != NULL)
- fprintf(stderr, "%s: ", program);
- va_start(args, format);
- vfprintf(stderr, format, args);
- va_end(args);
- fprintf(stderr, ": %s\n", strerror(oerrno));
- exit(1);
-}
-
-
/*
* Print out the usage message and then exit with the status given as the only
* argument. If status is zero, the message is printed to standard output;
@@ -282,7 +258,9 @@ authenticate(struct config *config, struct ktc_token *token)
}
-/* Delete a principal out of the AFS kaserver. */
+/*
+ * Delete a principal out of the AFS kaserver.
+ */
static void
delete_principal(struct config *config)
{
@@ -314,6 +292,122 @@ delete_principal(struct config *config)
/*
+ * Format a date. The output format expects ctime-style date formatting, so
+ * we use that. Takes a buffer into which to put the date. There will be a
+ * trailing newline.
+ */
+static void
+format_date(char *buffer, size_t size, time_t date)
+{
+ if (date == (time_t) NEVERDATE)
+ strlcpy(buffer, "never\n", size);
+ else
+ strlcpy(buffer, ctime(&date), size);
+}
+
+
+/*
+ * Enable or disable a principal in the AFS kaserver (by setting or clearing
+ * the NOTGS flag). The second argument says to enable if it's true, disable
+ * otherwise.
+ */
+static void
+enable_principal(struct config *config, int enable)
+{
+ struct ktc_token token;
+ struct ubik_client *conn;
+ struct kaentryinfo entry;
+ char name[MAXKTCNAMELEN];
+ char inst[MAXKTCNAMELEN];
+ char cell[MAXKTCNAMELEN];
+ long code;
+
+ /* Make connection to AuthServer. */
+ authenticate(config, &token);
+ parse_principal(config, config->delete, name, inst, cell);
+ code = ka_AuthServerConn(cell, KA_MAINTENANCE_SERVICE, &token, &conn);
+ if (config->debug)
+ printf("ka_AuthServerConn %s %ld\n", cell, code);
+ if (code != 0)
+ die("can't make connection to auth server");
+
+ /* Retrieve the principal information. */
+ code = ubik_Call(KAM_GetEntry, conn, 0, name, inst, KAMAJORVERSION,
+ &entry);
+ if (config->debug)
+ printf("ubik_Call KAM_GetEntry %ld\n", code);
+ if (code != 0) {
+ if (code == KANOENT)
+ die("no such entry in the database");
+ else
+ die("can't retrieve principal information");
+ }
+
+ /* Set the flags. */
+ if (enable)
+ entry.flags &= ~KAFNOTGS;
+ else
+ entry.flags |= KAFNOTGS;
+ code = ubik_Call(KAM_SetFields, conn, 0, name, inst, entry.flags, 0, 0,
+ -1, 0, 0);
+ if (config->debug)
+ printf("ubik_Call KAM_SetFields %ld\n", code);
+ if (code != 0)
+ die("can't %s principal", enable ? "enable" : "disable");
+ code = ubik_ClientDestroy(conn);
+ exit(0);
+}
+
+
+/*
+ * Examine a principal. The output format is compatible with the old Stanford
+ * Kerberos v4 kadmin, which may be compatible with Kerberos v4 kadmin in
+ * general (I haven't checked).
+ */
+static void
+examine_principal(struct config *config)
+{
+ struct ktc_token token;
+ struct ubik_client *conn;
+ struct kaentryinfo entry;
+ char name[MAXKTCNAMELEN];
+ char inst[MAXKTCNAMELEN];
+ char cell[MAXKTCNAMELEN];
+ long code;
+ char edate[64], cdate[64], mdate[64];
+
+ /* Make connection to AuthServer. */
+ authenticate(config, &token);
+ parse_principal(config, config->delete, name, inst, cell);
+ code = ka_AuthServerConn(cell, KA_MAINTENANCE_SERVICE, &token, &conn);
+ if (config->debug)
+ printf("ka_AuthServerConn %s %ld\n", cell, code);
+ if (code != 0)
+ die("can't make connection to auth server");
+
+ /* Retrieve and format the entry. */
+ code = ubik_Call(KAM_GetEntry, conn, 0, name, inst, KAMAJORVERSION,
+ &entry);
+ if (config->debug)
+ printf("ubik_Call KAM_GetEntry %ld\n", code);
+ if (code != 0)
+ die("can't retrieve current flags");
+ format_date(edate, sizeof(edate), entry.user_expiration);
+ format_date(mdate, sizeof(cdate), entry.modification_time);
+ format_date(cdate, sizeof(mdate), entry.change_password_time);
+ printf("status: %s\n", (entry.flags & KAFNOTGS) ? "disabled" : "enabled");
+ printf("account expiration: %s", edate);
+ printf("password last changed: %s", cdate);
+ printf("modification time: %s", mdate);
+ printf("modified by: %s%s%s\n", entry.modification_user.name,
+ (entry.modification_user.instance[0] != '\0') ? "." : "",
+ entry.modification_user.instance);
+ code = ubik_ClientDestroy(conn);
+ exit(0);
+}
+
+
+/*
* Create a new principal in the AFS kaserver (deleting it and recreating it
* if it already exists) with either the indicated key or with a random key,
* and then write out a srvtab for that principal. Also supported is reading
@@ -447,12 +541,15 @@ main(int argc, char *argv[])
case 'c': config.k5srvtab = optarg; break;
case 'D': config.delete = optarg; break;
case 'd': config.debug = 1; break;
+ case 'e': config.examine = optarg; break;
case 'f': config.srvtab = optarg; break;
case 'i': config.init = 1; break;
case 'k': config.keyfile = optarg; break;
+ case 'n': config.notgs = 1; break;
case 'p': config.password = optarg; break;
case 'r': config.random = 1; break;
case 's': config.service = optarg; break;
+ case 't': config.tgs = 1; break;
/* Usage doesn't return. */
case 'h':
@@ -468,10 +565,18 @@ main(int argc, char *argv[])
/* Take the right action. */
if (config.random && config.k5srvtab)
usage(1);
+ if (config.notgs && config.tgs)
+ die("cannot set principal both TGS and NOTGS at the same time");
+ if ((config.notgs || config.tgs) && config.service == NULL)
+ die("must specify a principal with -s");
if (config.debug)
- fprintf(stdout,"cell: %s\n", config.local_cell);
+ fprintf(stdout, "cell: %s\n", config.local_cell);
if (config.init)
initialize_admin_srvtab(&config);
+ else if (config.tgs || config.notgs)
+ enable_principal(&config, config.tgs);
+ else if (config.examine != NULL)
+ examine_principal(&config);
else if (config.service != NULL)
generate_srvtab(&config);
else if (config.delete != NULL)
diff --git a/portable/strlcat.c b/portable/strlcat.c
new file mode 100644
index 0000000..971a348
--- /dev/null
+++ b/portable/strlcat.c
@@ -0,0 +1,42 @@
+/* $Id$
+ *
+ * Replacement for a missing strlcat.
+ *
+ * Provides the same functionality as the *BSD function strlcat, originally
+ * developed by Todd Miller and Theo de Raadt. strlcat works similarly to
+ * strncat, except simpler. The result is always nul-terminated even if the
+ * source string is longer than the space remaining in the destination string,
+ * and the total space required is returned. The third argument is the total
+ * space available in the destination buffer, not just the amount of space
+ * remaining.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ * This work is hereby placed in the public domain by its author.
+ */
+
+#include <config.h>
+#include <system.h>
+
+/*
+ * If we're running the test suite, rename strlcat to avoid conflicts with
+ * the system version.
+ */
+#if TESTING
+# define strlcat test_strlcat
+size_t test_strlcat(char *, const char *, size_t);
+#endif
+
+size_t
+strlcat(char *dst, const char *src, size_t size)
+{
+ size_t used, length, copy;
+
+ used = strlen(dst);
+ length = strlen(src);
+ if (size > 0 && used < size - 1) {
+ copy = (length >= size - used) ? size - used - 1 : length;
+ memcpy(dst + used, src, copy);
+ dst[used + copy] = '\0';
+ }
+ return used + length;
+}
diff --git a/portable/strlcpy.c b/portable/strlcpy.c
new file mode 100644
index 0000000..1dd49a3
--- /dev/null
+++ b/portable/strlcpy.c
@@ -0,0 +1,40 @@
+/* $Id$
+ *
+ * Replacement for a missing strlcpy.
+ *
+ * Provides the same functionality as the *BSD function strlcpy, originally
+ * developed by Todd Miller and Theo de Raadt. strlcpy works similarly to
+ * strncpy, except saner and simpler. The result is always nul-terminated
+ * even if the source string is longer than the destination string, and the
+ * total space required is returned. The destination string is not nul-filled
+ * like strncpy does, just nul-terminated.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ * This work is hereby placed in the public domain by its author.
+ */
+
+#include <config.h>
+#include <system.h>
+
+/*
+ * If we're running the test suite, rename strlcpy to avoid conflicts with
+ * the system version.
+ */
+#if TESTING
+# define strlcpy test_strlcpy
+size_t test_strlcpy(char *, const char *, size_t);
+#endif
+
+size_t
+strlcpy(char *dst, const char *src, size_t size)
+{
+ size_t length, copy;
+
+ length = strlen(src);
+ if (size > 0) {
+ copy = (length >= size) ? size - 1 : length;
+ memcpy(dst, src, copy);
+ dst[copy] = '\0';
+ }
+ return length;
+}
diff --git a/system.h b/system.h
index cbe555f..0650a2d 100644
--- a/system.h
+++ b/system.h
@@ -73,6 +73,12 @@ extern int snprintf(char *, size_t, const char *, ...)
#if !HAVE_DECL_VSNPRINTF
extern int vsnprintf(char *, size_t, const char *, va_list);
#endif
+#if !HAVE_STRLCAT
+extern size_t strlcat(char *, const char *, size_t);
+#endif
+#if !HAVE_STRLCPY
+extern size_t strlcpy(char *, const char *, size_t);
+#endif
END_DECLS
diff --git a/tests/TESTS b/tests/TESTS
index 5bae099..4ff4a3b 100644
--- a/tests/TESTS
+++ b/tests/TESTS
@@ -3,6 +3,8 @@ client/full
client/pod
portable/asprintf
portable/snprintf
+portable/strlcat
+portable/strlcpy
server/admin
server/backend
server/keytab
diff --git a/tests/portable/strlcat-t.c b/tests/portable/strlcat-t.c
new file mode 100644
index 0000000..5ad8b6f
--- /dev/null
+++ b/tests/portable/strlcat-t.c
@@ -0,0 +1,77 @@
+/* $Id$
+ *
+ * strlcat test suite.
+ *
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * This code is derived from software contributed to the Internet Software
+ * Consortium by Rich Salz.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <system.h>
+
+#include <tests/libtest.h>
+
+size_t test_strlcat(char *, const char *, size_t);
+
+
+int
+main(void)
+{
+ char buffer[10] = "";
+
+ test_init(27);
+
+ ok_int(1, 3, test_strlcat(buffer, "foo", sizeof(buffer)));
+ ok_string(2, "foo", buffer);
+ ok_int(3, 7, test_strlcat(buffer, " bar", sizeof(buffer)));
+ ok_string(4, "foo bar", buffer);
+ ok_int(5, 9, test_strlcat(buffer, "!!", sizeof(buffer)));
+ ok_string(6, "foo bar!!", buffer);
+ ok_int(7, 10, test_strlcat(buffer, "!", sizeof(buffer)));
+ ok_string(8, "foo bar!!", buffer);
+ ok(9, buffer[9] == '\0');
+ buffer[0] = '\0';
+ ok_int(10, 11, test_strlcat(buffer, "hello world", sizeof(buffer)));
+ ok_string(11, "hello wor", buffer);
+ ok(12, buffer[9] == '\0');
+ buffer[0] = '\0';
+ ok_int(13, 7, test_strlcat(buffer, "sausage", 5));
+ ok_string(14, "saus", buffer);
+ ok_int(15, 14, test_strlcat(buffer, "bacon eggs", sizeof(buffer)));
+ ok_string(16, "sausbacon", buffer);
+
+ /* Make sure that with a size of 0, the destination isn't changed. */
+ ok_int(17, 11, test_strlcat(buffer, "!!", 0));
+ ok_string(18, "sausbacon", buffer);
+
+ /* Now play with empty strings. */
+ ok_int(19, 9, test_strlcat(buffer, "", 0));
+ ok_string(20, "sausbacon", buffer);
+ buffer[0] = '\0';
+ ok_int(21, 0, test_strlcat(buffer, "", sizeof(buffer)));
+ ok_string(22, "", buffer);
+ ok_int(23, 3, test_strlcat(buffer, "foo", 2));
+ ok_string(24, "f", buffer);
+ ok(25, buffer[1] == '\0');
+ ok_int(26, 1, test_strlcat(buffer, "", sizeof(buffer)));
+ ok(27, buffer[1] == '\0');
+
+ return 0;
+}
diff --git a/tests/portable/strlcat.c b/tests/portable/strlcat.c
new file mode 100644
index 0000000..8983bd8
--- /dev/null
+++ b/tests/portable/strlcat.c
@@ -0,0 +1,2 @@
+#define TESTING 1
+#include <portable/strlcat.c>
diff --git a/tests/portable/strlcpy-t.c b/tests/portable/strlcpy-t.c
new file mode 100644
index 0000000..6427374
--- /dev/null
+++ b/tests/portable/strlcpy-t.c
@@ -0,0 +1,72 @@
+/* $Id$
+ *
+ * strlcpy test suite.
+ *
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * This code is derived from software contributed to the Internet Software
+ * Consortium by Rich Salz.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <system.h>
+
+#include <tests/libtest.h>
+
+size_t test_strlcpy(char *, const char *, size_t);
+
+
+int
+main(void)
+{
+ char buffer[10];
+
+ test_init(23);
+
+ ok_int(1, 3, test_strlcpy(buffer, "foo", sizeof(buffer)));
+ ok_string(2, "foo", buffer);
+ ok_int(3, 9, test_strlcpy(buffer, "hello wor", sizeof(buffer)));
+ ok_string(4, "hello wor", buffer);
+ ok_int(5, 10, test_strlcpy(buffer, "world hell", sizeof(buffer)));
+ ok_string(6, "world hel", buffer);
+ ok(7, buffer[9] == '\0');
+ ok_int(8, 11, test_strlcpy(buffer, "hello world", sizeof(buffer)));
+ ok_string(9, "hello wor", buffer);
+ ok(10, buffer[9] == '\0');
+
+ /* Make sure that with a size of 0, the destination isn't changed. */
+ ok_int(11, 3, test_strlcpy(buffer, "foo", 0));
+ ok_string(12, "hello wor", buffer);
+
+ /* Now play with empty strings. */
+ ok_int(13, 0, test_strlcpy(buffer, "", 0));
+ ok_string(14, "hello wor", buffer);
+ ok_int(15, 0, test_strlcpy(buffer, "", sizeof(buffer)));
+ ok_string(16, "", buffer);
+ ok_int(17, 3, test_strlcpy(buffer, "foo", 2));
+ ok_string(18, "f", buffer);
+ ok(19, buffer[1] == '\0');
+ ok_int(20, 0, test_strlcpy(buffer, "", 1));
+ ok(21, buffer[0] == '\0');
+
+ /* Finally, check using strlcpy as strlen. */
+ ok_int(22, 3, test_strlcpy(NULL, "foo", 0));
+ ok_int(23, 11, test_strlcpy(NULL, "hello world", 0));
+
+ return 0;
+}
diff --git a/tests/portable/strlcpy.c b/tests/portable/strlcpy.c
new file mode 100644
index 0000000..d444595
--- /dev/null
+++ b/tests/portable/strlcpy.c
@@ -0,0 +1,2 @@
+#define TESTING 1
+#include <portable/strlcpy.c>