/* * 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 #include #include "cifskey.h" #include "mount.h" #include "resolve_host.h" #include "util.h" #define THIS_PROGRAM_NAME "cifscreds" /* max length of appropriate command */ #define MAX_COMMAND_SIZE 32 struct cmdarg { char *host; char *user; char keytype; }; struct command { int (*action)(struct cmdarg *arg); const char name[MAX_COMMAND_SIZE]; const char *format; }; static int cifscreds_add(struct cmdarg *arg); static int cifscreds_clear(struct cmdarg *arg); static int cifscreds_clearall(struct cmdarg *arg); static int cifscreds_update(struct cmdarg *arg); static const char *thisprogram; static struct command commands[] = { { cifscreds_add, "add", "[-u username] [-d] " }, { cifscreds_clear, "clear", "[-u username] [-d] " }, { cifscreds_clearall, "clearall", "" }, { cifscreds_update, "update", "[-u username] [-d] " }, { NULL, "", NULL } }; static struct option longopts[] = { {"username", 1, NULL, 'u'}, {"domain", 0, NULL, 'd' }, {NULL, 0, NULL, 0} }; /* display usage information */ static int 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"); return EXIT_FAILURE; } /* 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, KEY_PREFIX ":") == 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 command handler */ static int cifscreds_add(struct cmdarg *arg) { char addrstr[MAX_ADDR_LIST_LEN]; char *currentaddress, *nextaddress; char *pass; int ret = 0; if (arg->host == NULL || arg->user == NULL) return usage(); if (arg->keytype == 'd') strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN); else ret = resolve_host(arg->host, addrstr); switch (ret) { case EX_USAGE: fprintf(stderr, "error: Could not resolve address " "for %s\n", arg->host); return EXIT_FAILURE; case EX_SYSERR: fprintf(stderr, "error: Problem parsing address list\n"); return EXIT_FAILURE; } if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) { fprintf(stderr, "error: Incorrect username\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, arg->keytype) > 0) { printf("You already have stashed credentials " "for %s (%s)\n", currentaddress, arg->host); printf("If you want to update them use:\n"); printf("\t%s update\n", thisprogram); return EXIT_FAILURE; } switch(errno) { case ENOKEY: /* success */ break; default: printf("Key search failed: %s\n", strerror(errno)); 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, arg->user, pass, arg->keytype); if (key <= 0) { fprintf(stderr, "error: Add credential key for %s: %s\n", currentaddress, strerror(errno)); } else { if (keyctl(KEYCTL_SETPERM, key, CIFS_KEY_PERMS) < 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, arg->host); } } } currentaddress = nextaddress; if (currentaddress) { nextaddress = strchr(currentaddress, ','); if (nextaddress) *nextaddress++ = '\0'; } } return EXIT_SUCCESS; } /* clear command handler */ static int cifscreds_clear(struct cmdarg *arg) { char addrstr[MAX_ADDR_LIST_LEN]; char *currentaddress, *nextaddress; int ret = 0, count = 0, errors = 0; if (arg->host == NULL || arg->user == NULL) return usage(); if (arg->keytype == 'd') strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN); else ret = resolve_host(arg->host, addrstr); switch (ret) { case EX_USAGE: fprintf(stderr, "error: Could not resolve address " "for %s\n", arg->host); return EXIT_FAILURE; case EX_SYSERR: fprintf(stderr, "error: Problem parsing address list\n"); return EXIT_FAILURE; } if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) { fprintf(stderr, "error: Incorrect username\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, arg->keytype); if (key > 0) { if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) { fprintf(stderr, "error: Removing key from " "keyring for %s (%s)\n", currentaddress, arg->host); 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", arg->host); 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(struct cmdarg *arg __attribute__ ((unused))) { key_serial_t key; int count = 0, errors = 0; /* * 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 " KEY_PREFIX " 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(struct cmdarg *arg) { char addrstr[MAX_ADDR_LIST_LEN]; char *currentaddress, *nextaddress, *pass; char *addrs[16]; int ret = 0, id, count = 0; if (arg->host == NULL || arg->user == NULL) return usage(); if (arg->keytype == 'd') strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN); else ret = resolve_host(arg->host, addrstr); switch (ret) { case EX_USAGE: fprintf(stderr, "error: Could not resolve address " "for %s\n", arg->host); return EXIT_FAILURE; case EX_SYSERR: fprintf(stderr, "error: Problem parsing address list\n"); return EXIT_FAILURE; } if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) { fprintf(stderr, "error: Incorrect username\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, arg->keytype) > 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", arg->host); 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], arg->user, pass, arg->keytype); if (key <= 0) fprintf(stderr, "error: Update credential key " "for %s: %s\n", addrs[id], strerror(errno)); } return EXIT_SUCCESS; } static int check_session_keyring(void) { key_serial_t ses_key, uses_key; ses_key = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0); if (ses_key == -1) { if (errno == ENOKEY) fprintf(stderr, "Error: you have no session keyring. " "Consider using pam_keyinit to " "install one.\n"); else fprintf(stderr, "Error: unable to query session " "keyring: %s\n", strerror(errno)); return (int)ses_key; } /* A problem querying the user-session keyring isn't fatal. */ uses_key = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0); if (uses_key == -1) return 0; if (ses_key == uses_key) fprintf(stderr, "Warning: you have no persistent session " "keyring. cifscreds keys will not persist " "after this process exits. See " "pam_keyinit(8).\n"); return 0; } int main(int argc, char **argv) { struct command *cmd, *best; struct cmdarg arg; int n; memset(&arg, 0, sizeof(arg)); arg.keytype = 'a'; thisprogram = (char *)basename(argv[0]); if (thisprogram == NULL) thisprogram = THIS_PROGRAM_NAME; if (argc == 1) return usage(); while((n = getopt_long(argc, argv, "du:", longopts, NULL)) != -1) { switch (n) { case 'd': arg.keytype = (char) n; break; case 'u': arg.user = optarg; break; default: return usage(); } } if (optind >= argc) return usage(); /* find the best fit command */ best = NULL; n = strnlen(argv[optind], MAX_COMMAND_SIZE); for (cmd = commands; cmd->action; cmd++) { if (memcmp(cmd->name, argv[optind], n) != 0) continue; if (cmd->name[n] == 0) { /* exact match */ best = cmd; break; } /* partial match */ if (best) { fprintf(stderr, "Ambiguous command\n"); return EXIT_FAILURE; } best = cmd; } if (!best) { fprintf(stderr, "Unknown command\n"); return EXIT_FAILURE; } /* second argument should be host or domain */ if (argc >= 3) arg.host = argv[optind + 1]; if (arg.host && arg.keytype == 'd' && strpbrk(arg.host, DOMAIN_DISALLOWED_CHARS)) { fprintf(stderr, "error: Domain name contains invalid characters\n"); return EXIT_FAILURE; } if (arg.user == NULL) arg.user = getusername(getuid()); if (check_session_keyring()) return EXIT_FAILURE; return best->action(&arg); }