diff --git a/Makefile.am b/Makefile.am
index 1ac9249..38a16fe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -15,3 +15,8 @@ cifs_upcall_LDADD = -ltalloc -lkeyutils $(KRB5_LDADD)
 man_MANS += cifs.upcall.8
 endif
 
+if CONFIG_CIFSCREDS
+bin_PROGRAMS = cifscreds
+cifscreds_SOURCES = cifscreds.c resolve_host.c util.c
+cifscreds_LDADD = -lkeyutils
+endif
diff --git a/cifscreds.c b/cifscreds.c
new file mode 100644
index 0000000..f21a47f
--- /dev/null
+++ b/cifscreds.c
@@ -0,0 +1,582 @@
+/*
+ * Credentials stashing utility for Linux CIFS VFS (virtual filesystem) client
+ * Copyright (C) 2010 Jeff Layton (jlayton@samba.org)
+ * Copyright (C) 2010 Igor Druzhinin (jaxbrigs@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <keyutils.h>
+#include "mount.h"
+#include "resolve_host.h"
+
+#define THIS_PROGRAM_NAME "cifscreds"
+
+/* max length of appropriate command */
+#define MAX_COMMAND_SIZE 32
+
+/* max length of username, password and domain name */
+#define MAX_USERNAME_SIZE 32
+#define MOUNT_PASSWD_SIZE 128
+#define MAX_DOMAIN_SIZE 64
+
+/* allowed and disallowed characters for user and domain name */
+#define USER_DISALLOWED_CHARS "\\/\"[]:|<>+=;,?*@"
+#define DOMAIN_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz" \
+			     "ABCDEFGHIJKLMNOPQRSTUVWXYZ-."
+
+/* destination keyring */
+#define DEST_KEYRING KEY_SPEC_USER_KEYRING
+
+struct command {
+	int (*action)(int argc, char *argv[]);
+	const char	name[MAX_COMMAND_SIZE];
+	const char	*format;
+};
+
+static int cifscreds_add(int argc, char *argv[]);
+static int cifscreds_clear(int argc, char *argv[]);
+static int cifscreds_clearall(int argc, char *argv[]);
+static int cifscreds_update(int argc, char *argv[]);
+
+const char *thisprogram;
+
+struct command commands[] = {
+	{ cifscreds_add,	"add",		"<host> <user> [domain]" },
+	{ cifscreds_clear,	"clear",	"<host> <user> [domain]" },
+	{ cifscreds_clearall,	"clearall",	"" },
+	{ cifscreds_update,	"update",	"<host> <user> [domain]" },
+	{ NULL, "", NULL }
+};
+
+/* display usage information */
+static void usage(void)
+{
+	struct command *cmd;
+
+	fprintf(stderr, "Usage:\n");
+	for (cmd = commands; cmd->action; cmd++)
+		fprintf(stderr, "\t%s %s %s\n", thisprogram,
+			cmd->name, cmd->format);
+	fprintf(stderr, "\n");
+
+	exit(EXIT_FAILURE);
+}
+
+/* create key's description string from given credentials */
+static char *
+create_description(const char *addr, const char *user,
+		   const char *domain, char *desc)
+{
+	char *str_end;
+	int str_len;
+
+	sprintf(desc, "%s:%s:%s:", THIS_PROGRAM_NAME, addr, user);
+
+	if (domain != NULL) {
+		str_end = desc + strnlen(desc, INET6_ADDRSTRLEN + \
+					+ MAX_USERNAME_SIZE + \
+					+ sizeof(THIS_PROGRAM_NAME) + 3);
+		str_len = strnlen(domain, MAX_DOMAIN_SIZE);
+		while (str_len--) {
+			*str_end = tolower(*domain++);
+			str_end++;
+		}
+		*str_end = '\0';
+	}
+
+	return desc;
+}
+
+/* search a specific key in keyring */
+static key_serial_t
+key_search(const char *addr, const char *user, const char *domain)
+{
+	char desc[INET6_ADDRSTRLEN + MAX_USERNAME_SIZE + MAX_DOMAIN_SIZE + \
+		+ sizeof(THIS_PROGRAM_NAME) + 3];
+	key_serial_t key, *pk;
+	void *keylist;
+	char *buffer;
+	int count, dpos, n, ret;
+
+	create_description(addr, user, domain, desc);
+
+	/* read the key payload data */
+	count = keyctl_read_alloc(DEST_KEYRING, &keylist);
+	if (count < 0)
+		return 0;
+
+	count /= sizeof(key_serial_t);
+
+	if (count == 0) {
+		ret = 0;
+		goto key_search_out;
+	}
+
+	/* list the keys in the keyring */
+	pk = keylist;
+	do {
+		key = *pk++;
+
+		ret = keyctl_describe_alloc(key, &buffer);
+		if (ret < 0)
+			continue;
+
+		n = sscanf(buffer, "%*[^;];%*d;%*d;%*x;%n", &dpos);
+		if (n) {
+			free(buffer);
+			continue;
+		}
+
+		if (!strcmp(buffer + dpos, desc)) {
+			ret = key;
+			free(buffer);
+			goto key_search_out;
+		}
+		free(buffer);
+
+	} while (--count);
+
+	ret = 0;
+
+key_search_out:
+	free(keylist);
+	return ret;
+}
+
+/* search all program's keys in keyring */
+static key_serial_t key_search_all(void)
+{
+	key_serial_t key, *pk;
+	void *keylist;
+	char *buffer;
+	int count, dpos, n, ret;
+
+	/* read the key payload data */
+	count = keyctl_read_alloc(DEST_KEYRING, &keylist);
+	if (count < 0)
+		return 0;
+
+	count /= sizeof(key_serial_t);
+
+	if (count == 0) {
+		ret = 0;
+		goto key_search_all_out;
+	}
+
+	/* list the keys in the keyring */
+	pk = keylist;
+	do {
+		key = *pk++;
+
+		ret = keyctl_describe_alloc(key, &buffer);
+		if (ret < 0)
+			continue;
+
+		n = sscanf(buffer, "%*[^;];%*d;%*d;%*x;%n", &dpos);
+		if (n) {
+			free(buffer);
+			continue;
+		}
+
+		if (strstr(buffer + dpos, THIS_PROGRAM_NAME ":") ==
+			buffer + dpos
+		) {
+			ret = key;
+			free(buffer);
+			goto key_search_all_out;
+		}
+		free(buffer);
+
+	} while (--count);
+
+	ret = 0;
+
+key_search_all_out:
+	free(keylist);
+	return ret;
+}
+
+/* add or update a specific key to keyring */
+static key_serial_t
+key_add(const char *addr, const char *user,
+	const char *domain, const char *pass)
+{
+	char desc[INET6_ADDRSTRLEN + MAX_USERNAME_SIZE + MAX_DOMAIN_SIZE + \
+		+ sizeof(THIS_PROGRAM_NAME) + 3];
+
+	create_description(addr, user, domain, desc);
+
+	return add_key("user", desc, pass, strnlen(pass, MOUNT_PASSWD_SIZE) + 1,
+		DEST_KEYRING);
+}
+
+/* add command handler */
+static int cifscreds_add(int argc, char *argv[])
+{
+	char addrstr[MAX_ADDR_LIST_LEN];
+	char *currentaddress, *nextaddress;
+	char *pass;
+	int ret;
+
+	if (argc != 4 && argc != 5)
+		usage();
+
+	ret = resolve_host(argv[2], addrstr);
+	switch (ret) {
+	case EX_USAGE:
+		fprintf(stderr, "error: Could not resolve address "
+			"for %s\n", argv[2]);
+		return EXIT_FAILURE;
+
+	case EX_SYSERR:
+		fprintf(stderr, "error: Problem parsing address list\n");
+		return EXIT_FAILURE;
+	}
+
+	if (strpbrk(argv[3], USER_DISALLOWED_CHARS)) {
+		fprintf(stderr, "error: Incorrect username\n");
+		return EXIT_FAILURE;
+	}
+
+	if (argc == 5) {
+		if (strspn(argv[4], DOMAIN_ALLOWED_CHARS) !=
+			strnlen(argv[4], MAX_DOMAIN_SIZE)
+		) {
+			fprintf(stderr, "error: Incorrect domain name\n");
+			return EXIT_FAILURE;
+		}
+	}
+
+	/* search for same credentials stashed for current host */
+	currentaddress = addrstr;
+	nextaddress = strchr(currentaddress, ',');
+	if (nextaddress)
+		*nextaddress++ = '\0';
+
+	while (currentaddress) {
+		if (key_search(currentaddress, argv[3],
+			argc == 5 ? argv[4] : NULL) > 0
+		) {
+			printf("You already have stashed credentials "
+				"for %s (%s)\n", currentaddress, argv[2]);
+			printf("If you want to update them use:\n");
+			printf("\t%s update\n", thisprogram);
+
+			return EXIT_FAILURE;
+		}
+
+		currentaddress = nextaddress;
+		if (currentaddress) {
+			*(currentaddress - 1) = ',';
+			nextaddress = strchr(currentaddress, ',');
+			if (nextaddress)
+				*nextaddress++ = '\0';
+		}
+	}
+
+	/*
+	 * if there isn't same credentials stashed add them to keyring
+	 * and set permisson mask
+	 */
+	pass = getpass("Password: ");
+
+	currentaddress = addrstr;
+	nextaddress = strchr(currentaddress, ',');
+	if (nextaddress)
+		*nextaddress++ = '\0';
+
+	while (currentaddress) {
+		key_serial_t key = key_add(currentaddress, argv[3],
+					   argc == 5 ? argv[4] : NULL, pass);
+		if (key <= 0) {
+			fprintf(stderr, "error: Add credential key for %s\n",
+				currentaddress);
+		} else {
+			if (keyctl(KEYCTL_SETPERM, key, KEY_POS_VIEW | \
+				KEY_POS_WRITE | KEY_USR_VIEW | \
+				KEY_USR_WRITE) < 0
+			) {
+				fprintf(stderr, "error: Setting permissons "
+					"on key, attempt to delete...\n");
+
+				if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
+					fprintf(stderr, "error: Deleting key from "
+						"keyring for %s (%s)\n",
+						currentaddress, argv[2]);
+				}
+			}
+		}
+
+		currentaddress = nextaddress;
+		if (currentaddress) {
+			nextaddress = strchr(currentaddress, ',');
+			if (nextaddress)
+				*nextaddress++ = '\0';
+		}
+	}
+
+	return EXIT_SUCCESS;
+}
+
+/* clear command handler */
+static int cifscreds_clear(int argc, char *argv[])
+{
+	char addrstr[MAX_ADDR_LIST_LEN];
+	char *currentaddress, *nextaddress;
+	int ret, count = 0, errors = 0;
+
+	if (argc != 4 && argc != 5)
+		usage();
+
+	ret = resolve_host(argv[2], addrstr);
+	switch (ret) {
+	case EX_USAGE:
+		fprintf(stderr, "error: Could not resolve address "
+			"for %s\n", argv[2]);
+		return EXIT_FAILURE;
+
+	case EX_SYSERR:
+		fprintf(stderr, "error: Problem parsing address list\n");
+		return EXIT_FAILURE;
+	}
+
+	if (strpbrk(argv[3], USER_DISALLOWED_CHARS)) {
+		fprintf(stderr, "error: Incorrect username\n");
+		return EXIT_FAILURE;
+	}
+
+	if (argc == 5) {
+		if (strspn(argv[4], DOMAIN_ALLOWED_CHARS) !=
+			strnlen(argv[4], MAX_DOMAIN_SIZE)
+		) {
+			fprintf(stderr, "error: Incorrect domain name\n");
+			return EXIT_FAILURE;
+		}
+	}
+
+	/*
+	 * search for same credentials stashed for current host
+	 * and unlink them from session keyring
+	 */
+	currentaddress = addrstr;
+	nextaddress = strchr(currentaddress, ',');
+	if (nextaddress)
+		*nextaddress++ = '\0';
+
+	while (currentaddress) {
+		key_serial_t key = key_search(currentaddress, argv[3],
+						argc == 5 ? argv[4] : NULL);
+		if (key > 0) {
+			if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
+				fprintf(stderr, "error: Removing key from "
+					"keyring for %s (%s)\n",
+					currentaddress, argv[2]);
+				errors++;
+			} else {
+				count++;
+			}
+		}
+
+		currentaddress = nextaddress;
+		if (currentaddress) {
+			nextaddress = strchr(currentaddress, ',');
+			if (nextaddress)
+				*nextaddress++ = '\0';
+		}
+	}
+
+	if (!count && !errors) {
+		printf("You have no same stashed credentials "
+			" for %s\n", argv[2]);
+		printf("If you want to add them use:\n");
+		printf("\t%s add\n", thisprogram);
+
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+/* clearall command handler */
+static int cifscreds_clearall(int argc, char *argv[])
+{
+	key_serial_t key;
+	int count = 0, errors = 0;
+
+	if (argc != 2)
+		usage();
+
+	/*
+	 * search for all program's credentials stashed in session keyring
+	 * and then unlink them
+	 */
+	do {
+		key = key_search_all();
+		if (key > 0) {
+			if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
+				fprintf(stderr, "error: Deleting key "
+					"from keyring");
+				errors++;
+			} else {
+				count++;
+			}
+		}
+	} while (key > 0);
+
+	if (!count && !errors) {
+		printf("You have no stashed " THIS_PROGRAM_NAME
+			" credentials\n");
+		printf("If you want to add them use:\n");
+		printf("\t%s add\n", thisprogram);
+
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+/* update command handler */
+static int cifscreds_update(int argc, char *argv[])
+{
+	char addrstr[MAX_ADDR_LIST_LEN];
+	char *currentaddress, *nextaddress, *pass;
+	char *addrs[16];
+	int ret, id, count = 0;
+
+	if (argc != 4 && argc != 5)
+		usage();
+
+	ret = resolve_host(argv[2], addrstr);
+	switch (ret) {
+	case EX_USAGE:
+		fprintf(stderr, "error: Could not resolve address "
+			"for %s\n", argv[2]);
+		return EXIT_FAILURE;
+
+	case EX_SYSERR:
+		fprintf(stderr, "error: Problem parsing address list\n");
+		return EXIT_FAILURE;
+	}
+
+	if (strpbrk(argv[3], USER_DISALLOWED_CHARS)) {
+		fprintf(stderr, "error: Incorrect username\n");
+		return EXIT_FAILURE;
+	}
+
+	if (argc == 5) {
+		if (strspn(argv[4], DOMAIN_ALLOWED_CHARS) !=
+			strnlen(argv[4], MAX_DOMAIN_SIZE)
+		) {
+			fprintf(stderr, "error: Incorrect domain name\n");
+			return EXIT_FAILURE;
+		}
+	}
+
+	/* search for necessary credentials stashed in session keyring */
+	currentaddress = addrstr;
+	nextaddress = strchr(currentaddress, ',');
+	if (nextaddress)
+		*nextaddress++ = '\0';
+
+	while (currentaddress) {
+		if (key_search(currentaddress, argv[3],
+			argc == 5 ? argv[4] : NULL) > 0
+		) {
+			addrs[count] = currentaddress;
+			count++;
+		}
+
+		currentaddress = nextaddress;
+		if (currentaddress) {
+			nextaddress = strchr(currentaddress, ',');
+			if (nextaddress)
+				*nextaddress++ = '\0';
+		}
+	}
+
+	if (!count) {
+		printf("You have no same stashed credentials "
+			"for %s\n", argv[2]);
+		printf("If you want to add them use:\n");
+		printf("\t%s add\n", thisprogram);
+
+		return EXIT_FAILURE;
+	}
+
+	/* update payload of found keys */
+	pass = getpass("Password: ");
+
+	for (id = 0; id < count; id++) {
+		key_serial_t key = key_add(addrs[id], argv[3],
+					argc == 5 ? argv[4] : NULL, pass);
+		if (key <= 0)
+			fprintf(stderr, "error: Update credential key "
+				"for %s\n", addrs[id]);
+	}
+
+	return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+	struct command *cmd, *best;
+	int n;
+
+	thisprogram = (char *)basename(argv[0]);
+	if (thisprogram == NULL)
+		thisprogram = THIS_PROGRAM_NAME;
+
+	if (argc == 1)
+		usage();
+
+	/* find the best fit command */
+	best = NULL;
+	n = strnlen(argv[1], MAX_COMMAND_SIZE);
+
+	for (cmd = commands; cmd->action; cmd++) {
+		if (memcmp(cmd->name, argv[1], n) != 0)
+			continue;
+
+		if (cmd->name[n] == 0) {
+			/* exact match */
+			best = cmd;
+			break;
+		}
+
+		/* partial match */
+		if (best) {
+			fprintf(stderr, "Ambiguous command\n");
+			exit(EXIT_FAILURE);
+		}
+
+		best = cmd;
+	}
+
+	if (!best) {
+		fprintf(stderr, "Unknown command\n");
+		exit(EXIT_FAILURE);
+	}
+
+	exit(best->action(argc, argv));
+}
diff --git a/configure.ac b/configure.ac
index 266380a..c7d420d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,12 +16,18 @@ AC_ARG_ENABLE(cifsupcall,
 	enable_cifsupcall=$enableval,
 	enable_cifsupcall="maybe")
 
+AC_ARG_ENABLE(cifscreds,
+	[AC_HELP_STRING([--enable-cifscreds],
+			[Create cifscreds utility @<:@default=no@:>@])],
+	enable_cifscreds=$enableval,
+	enable_cifscreds="no")
+
 # Checks for programs.
 AC_PROG_CC
 AC_GNU_SOURCE
 
 # Checks for header files.
-AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h limits.h mntent.h netdb.h stddef.h stdint.h stdlib.h string.h strings.h sys/mount.h sys/param.h sys/socket.h sys/time.h syslog.h unistd.h], , [AC_MSG_ERROR([necessary header(s) not found])])
+AC_CHECK_HEADERS([arpa/inet.h ctype.h fcntl.h inttypes.h limits.h mntent.h netdb.h stddef.h stdint.h stdlib.h string.h strings.h sys/mount.h sys/param.h sys/socket.h sys/time.h syslog.h unistd.h], , [AC_MSG_ERROR([necessary header(s) not found])])
 
 if test $enable_cifsupcall != "no"; then
 	AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
@@ -82,6 +88,10 @@ if test $enable_cifsupcall != "no"; then
 	AC_SUBST(KRB5_LDADD)
 fi
 
+if test $enable_cifscreds = "yes"; then
+	AC_CHECK_HEADERS([keyutils.h], , [AC_MSG_ERROR([keyutils.h not found, consider installing keyutils-libs-devel.])])
+fi
+
 # Checks for typedefs, structures, and compiler characteristics.
 AC_HEADER_STDBOOL
 AC_TYPE_UID_T
@@ -98,7 +108,7 @@ AC_FUNC_REALLOC
 AC_FUNC_STRNLEN
 
 # check for required functions
-AC_CHECK_FUNCS([alarm atexit endpwent getmntent getpass gettimeofday inet_ntop memset realpath setenv strchr strdup strerror strncasecmp strndup strpbrk strrchr strstr strtol strtoul uname], , [AC_MSG_ERROR([necessary functions(s) not found])])
+AC_CHECK_FUNCS([alarm atexit endpwent getmntent getpass gettimeofday inet_ntop memset realpath setenv strchr strcmp strdup strerror strncasecmp strndup strpbrk strrchr strstr strtol strtoul tolower uname], , [AC_MSG_ERROR([necessary functions(s) not found])])
 
 # ugly, but I'm not sure how to check for functions in a library that's not in $LIBS
 cu_saved_libs=$LIBS
@@ -117,6 +127,7 @@ fi
 LIBS=$cu_saved_libs
 
 AM_CONDITIONAL(CONFIG_CIFSUPCALL, [test "$enable_cifsupcall" != "no"])
+AM_CONDITIONAL(CONFIG_CIFSCREDS, [test "$enable_cifscreds" = "yes"])
 
 LIBCAP_NG_PATH
 
