/*
* 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));
}