From patchwork Fri Aug 20 07:45:14 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Igor Druzhinin X-Patchwork-Id: 120486 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.4/8.14.3) with ESMTP id o7K7jPsp029965 for ; Fri, 20 Aug 2010 07:45:26 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751028Ab0HTHp0 (ORCPT ); Fri, 20 Aug 2010 03:45:26 -0400 Received: from mail-ew0-f46.google.com ([209.85.215.46]:56936 "EHLO mail-ew0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750959Ab0HTHpZ (ORCPT ); Fri, 20 Aug 2010 03:45:25 -0400 Received: by ewy23 with SMTP id 23so1899748ewy.19 for ; Fri, 20 Aug 2010 00:45:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:from:to:cc:subject:date :message-id:x-mailer:in-reply-to:references; bh=cVDVYznuZo1gBVNYYnI2D8gKpZYNsoWHQ+FuVT1E7AM=; b=REfZFqAJNBHW1jx/gLx62/wJqrhgD8VhTBEgoNivx9pMuM2AYeYCXKjNqbYTpr3weu SlkRV+TSX58zLMb5TSonx20vQ9j1LkZNmNLJjuQFegbbZA6kM4OfsC29LrcUt3ZUCdr0 r1CAsmvCYtYK3buvYpXoBxURSDLcJwhPBe2tY= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; b=t2LCEjHgovaA397K09LXH/7d1k6DmYKCYpFEAI0G5eyjofYdE4UynwxxehtvMyS79q aSaqxU2OorFQtLhocDHzlrL8hBgh3xhgDk3SHQxzcy42muEy8sHm09Xt2E69fpp6pTdk OLDP/DfELK/QQEbNij/PnShMhGeFA2BqAkxhY= Received: by 10.213.47.76 with SMTP id m12mr1117761ebf.43.1282290323813; Fri, 20 Aug 2010 00:45:23 -0700 (PDT) Received: from localhost.localdomain ([79.126.74.3]) by mx.google.com with ESMTPS id u9sm4244832eeh.5.2010.08.20.00.45.22 (version=TLSv1/SSLv3 cipher=RC4-MD5); Fri, 20 Aug 2010 00:45:23 -0700 (PDT) From: Igor Druzhinin To: jlayton@samba.org Cc: linux-cifs@vger.kernel.org, Igor Druzhinin Subject: [PATCH 2/2] cifs-utils: infrastructure for stashing passwords in keyring Date: Fri, 20 Aug 2010 11:45:14 +0400 Message-Id: <1282290314-9477-2-git-send-email-jaxbrigs@gmail.com> X-Mailer: git-send-email 1.7.2.1 In-Reply-To: <1282290314-9477-1-git-send-email-jaxbrigs@gmail.com> References: <1282290314-9477-1-git-send-email-jaxbrigs@gmail.com> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Fri, 20 Aug 2010 07:45:26 +0000 (UTC) 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 . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include +#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", " [domain]" }, + { cifscreds_clear, "clear", " [domain]" }, + { cifscreds_clearall, "clearall", "" }, + { cifscreds_update, "update", " [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