smbinfo: add support for new key dump ioctl
[cifs-utils.git] / cifscreds.c
1 /*
2  * Credentials stashing utility for Linux CIFS VFS (virtual filesystem) client
3  * Copyright (C) 2010 Jeff Layton (jlayton@samba.org)
4  * Copyright (C) 2010 Igor Druzhinin (jaxbrigs@gmail.com)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif /* HAVE_CONFIG_H */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <keyutils.h>
30 #include <getopt.h>
31 #include <errno.h>
32 #include "cifskey.h"
33 #include "mount.h"
34 #include "resolve_host.h"
35 #include "util.h"
36
37 #define THIS_PROGRAM_NAME "cifscreds"
38
39 /* max length of appropriate command */
40 #define MAX_COMMAND_SIZE 32
41
42 struct cmdarg {
43         char            *host;
44         char            *user;
45         char            keytype;
46 };
47
48 struct command {
49         int (*action)(struct cmdarg *arg);
50         const char      name[MAX_COMMAND_SIZE];
51         const char      *format;
52 };
53
54 static int cifscreds_add(struct cmdarg *arg);
55 static int cifscreds_clear(struct cmdarg *arg);
56 static int cifscreds_clearall(struct cmdarg *arg);
57 static int cifscreds_update(struct cmdarg *arg);
58
59 static const char *thisprogram;
60
61 static struct command commands[] = {
62         { cifscreds_add,        "add",          "[-u username] [-d] <host|domain>" },
63         { cifscreds_clear,      "clear",        "[-u username] [-d] <host|domain>" },
64         { cifscreds_clearall,   "clearall",     "" },
65         { cifscreds_update,     "update",       "[-u username] [-d] <host|domain>" },
66         { NULL, "", NULL }
67 };
68
69 static struct option longopts[] = {
70         {"username", 1, NULL, 'u'},
71         {"domain", 0, NULL, 'd' },
72         {NULL, 0, NULL, 0}
73 };
74
75 /* display usage information */
76 static int
77 usage(void)
78 {
79         struct command *cmd;
80
81         fprintf(stderr, "Usage:\n");
82         for (cmd = commands; cmd->action; cmd++)
83                 fprintf(stderr, "\t%s %s %s\n", thisprogram,
84                         cmd->name, cmd->format);
85         fprintf(stderr, "\n");
86
87         return EXIT_FAILURE;
88 }
89
90 /* search all program's keys in keyring */
91 static key_serial_t key_search_all(void)
92 {
93         key_serial_t key, *pk;
94         void *keylist;
95         char *buffer;
96         int count, dpos, n, ret;
97
98         /* read the key payload data */
99         count = keyctl_read_alloc(DEST_KEYRING, &keylist);
100         if (count < 0)
101                 return 0;
102
103         count /= sizeof(key_serial_t);
104
105         if (count == 0) {
106                 ret = 0;
107                 goto key_search_all_out;
108         }
109
110         /* list the keys in the keyring */
111         pk = keylist;
112         do {
113                 key = *pk++;
114
115                 ret = keyctl_describe_alloc(key, &buffer);
116                 if (ret < 0)
117                         continue;
118
119                 n = sscanf(buffer, "%*[^;];%*d;%*d;%*x;%n", &dpos);
120                 if (n) {
121                         free(buffer);
122                         continue;
123                 }
124
125                 if (strstr(buffer + dpos, KEY_PREFIX ":") ==
126                         buffer + dpos
127                 ) {
128                         ret = key;
129                         free(buffer);
130                         goto key_search_all_out;
131                 }
132                 free(buffer);
133
134         } while (--count);
135
136         ret = 0;
137
138 key_search_all_out:
139         free(keylist);
140         return ret;
141 }
142
143 /* add command handler */
144 static int cifscreds_add(struct cmdarg *arg)
145 {
146         char addrstr[MAX_ADDR_LIST_LEN];
147         char *currentaddress, *nextaddress;
148         char *pass;
149         int ret = 0;
150
151         if (arg->host == NULL || arg->user == NULL)
152                 return usage();
153
154         if (arg->keytype == 'd')
155                 strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN);
156         else
157                 ret = resolve_host(arg->host, addrstr);
158
159         switch (ret) {
160         case EX_USAGE:
161                 fprintf(stderr, "error: Could not resolve address "
162                         "for %s\n", arg->host);
163                 return EXIT_FAILURE;
164
165         case EX_SYSERR:
166                 fprintf(stderr, "error: Problem parsing address list\n");
167                 return EXIT_FAILURE;
168         }
169
170         if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) {
171                 fprintf(stderr, "error: Incorrect username\n");
172                 return EXIT_FAILURE;
173         }
174
175         /* search for same credentials stashed for current host */
176         currentaddress = addrstr;
177         nextaddress = strchr(currentaddress, ',');
178         if (nextaddress)
179                 *nextaddress++ = '\0';
180
181         while (currentaddress) {
182                 if (key_search(currentaddress, arg->keytype) > 0) {
183                         printf("You already have stashed credentials "
184                                 "for %s (%s)\n", currentaddress, arg->host);
185                         printf("If you want to update them use:\n");
186                         printf("\t%s update\n", thisprogram);
187
188                         return EXIT_FAILURE;
189                 }
190
191                 switch(errno) {
192                 case ENOKEY:
193                         /* success */
194                         break;
195                 default:
196                         printf("Key search failed: %s\n", strerror(errno));
197                         return EXIT_FAILURE;
198                 }
199
200                 currentaddress = nextaddress;
201                 if (currentaddress) {
202                         *(currentaddress - 1) = ',';
203                         nextaddress = strchr(currentaddress, ',');
204                         if (nextaddress)
205                                 *nextaddress++ = '\0';
206                 }
207         }
208
209         /*
210          * if there isn't same credentials stashed add them to keyring
211          * and set permisson mask
212          */
213         pass = getpass("Password: ");
214
215         currentaddress = addrstr;
216         nextaddress = strchr(currentaddress, ',');
217         if (nextaddress)
218                 *nextaddress++ = '\0';
219
220         while (currentaddress) {
221                 key_serial_t key = key_add(currentaddress, arg->user, pass, arg->keytype);
222                 if (key <= 0) {
223                         fprintf(stderr, "error: Add credential key for %s: %s\n",
224                                 currentaddress, strerror(errno));
225                 } else {
226                         if (keyctl(KEYCTL_SETPERM, key, CIFS_KEY_PERMS) < 0) {
227                                 fprintf(stderr, "error: Setting permissons "
228                                         "on key, attempt to delete...\n");
229
230                                 if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
231                                         fprintf(stderr, "error: Deleting key from "
232                                                 "keyring for %s (%s)\n",
233                                                 currentaddress, arg->host);
234                                 }
235                         }
236                 }
237
238                 currentaddress = nextaddress;
239                 if (currentaddress) {
240                         nextaddress = strchr(currentaddress, ',');
241                         if (nextaddress)
242                                 *nextaddress++ = '\0';
243                 }
244         }
245
246         return EXIT_SUCCESS;
247 }
248
249 /* clear command handler */
250 static int cifscreds_clear(struct cmdarg *arg)
251 {
252         char addrstr[MAX_ADDR_LIST_LEN];
253         char *currentaddress, *nextaddress;
254         int ret = 0, count = 0, errors = 0;
255
256         if (arg->host == NULL || arg->user == NULL)
257                 return usage();
258
259         if (arg->keytype == 'd')
260                 strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN);
261         else
262                 ret = resolve_host(arg->host, addrstr);
263
264         switch (ret) {
265         case EX_USAGE:
266                 fprintf(stderr, "error: Could not resolve address "
267                         "for %s\n", arg->host);
268                 return EXIT_FAILURE;
269
270         case EX_SYSERR:
271                 fprintf(stderr, "error: Problem parsing address list\n");
272                 return EXIT_FAILURE;
273         }
274
275         if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) {
276                 fprintf(stderr, "error: Incorrect username\n");
277                 return EXIT_FAILURE;
278         }
279
280         /*
281          * search for same credentials stashed for current host
282          * and unlink them from session keyring
283          */
284         currentaddress = addrstr;
285         nextaddress = strchr(currentaddress, ',');
286         if (nextaddress)
287                 *nextaddress++ = '\0';
288
289         while (currentaddress) {
290                 key_serial_t key = key_search(currentaddress, arg->keytype);
291                 if (key > 0) {
292                         if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
293                                 fprintf(stderr, "error: Removing key from "
294                                         "keyring for %s (%s)\n",
295                                         currentaddress, arg->host);
296                                 errors++;
297                         } else {
298                                 count++;
299                         }
300                 }
301
302                 currentaddress = nextaddress;
303                 if (currentaddress) {
304                         nextaddress = strchr(currentaddress, ',');
305                         if (nextaddress)
306                                 *nextaddress++ = '\0';
307                 }
308         }
309
310         if (!count && !errors) {
311                 printf("You have no same stashed credentials "
312                         " for %s\n", arg->host);
313                 printf("If you want to add them use:\n");
314                 printf("\t%s add\n", thisprogram);
315
316                 return EXIT_FAILURE;
317         }
318
319         return EXIT_SUCCESS;
320 }
321
322 /* clearall command handler */
323 static int cifscreds_clearall(struct cmdarg *arg __attribute__ ((unused)))
324 {
325         key_serial_t key;
326         int count = 0, errors = 0;
327
328         /*
329          * search for all program's credentials stashed in session keyring
330          * and then unlink them
331          */
332         do {
333                 key = key_search_all();
334                 if (key > 0) {
335                         if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
336                                 fprintf(stderr, "error: Deleting key "
337                                         "from keyring");
338                                 errors++;
339                         } else {
340                                 count++;
341                         }
342                 }
343         } while (key > 0);
344
345         if (!count && !errors) {
346                 printf("You have no stashed " KEY_PREFIX
347                         " credentials\n");
348                 printf("If you want to add them use:\n");
349                 printf("\t%s add\n", thisprogram);
350
351                 return EXIT_FAILURE;
352         }
353
354         return EXIT_SUCCESS;
355 }
356
357 /* update command handler */
358 static int cifscreds_update(struct cmdarg *arg)
359 {
360         char addrstr[MAX_ADDR_LIST_LEN];
361         char *currentaddress, *nextaddress, *pass;
362         char *addrs[16];
363         int ret = 0, id, count = 0;
364
365         if (arg->host == NULL || arg->user == NULL)
366                 return usage();
367
368         if (arg->keytype == 'd')
369                 strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN);
370         else
371                 ret = resolve_host(arg->host, addrstr);
372
373         switch (ret) {
374         case EX_USAGE:
375                 fprintf(stderr, "error: Could not resolve address "
376                         "for %s\n", arg->host);
377                 return EXIT_FAILURE;
378
379         case EX_SYSERR:
380                 fprintf(stderr, "error: Problem parsing address list\n");
381                 return EXIT_FAILURE;
382         }
383
384         if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) {
385                 fprintf(stderr, "error: Incorrect username\n");
386                 return EXIT_FAILURE;
387         }
388
389         /* search for necessary credentials stashed in session keyring */
390         currentaddress = addrstr;
391         nextaddress = strchr(currentaddress, ',');
392         if (nextaddress)
393                 *nextaddress++ = '\0';
394
395         while (currentaddress) {
396                 if (key_search(currentaddress, arg->keytype) > 0) {
397                         addrs[count] = currentaddress;
398                         count++;
399                 }
400
401                 currentaddress = nextaddress;
402                 if (currentaddress) {
403                         nextaddress = strchr(currentaddress, ',');
404                         if (nextaddress)
405                                 *nextaddress++ = '\0';
406                 }
407         }
408
409         if (!count) {
410                 printf("You have no same stashed credentials "
411                         "for %s\n", arg->host);
412                 printf("If you want to add them use:\n");
413                 printf("\t%s add\n", thisprogram);
414
415                 return EXIT_FAILURE;
416         }
417
418         /* update payload of found keys */
419         pass = getpass("Password: ");
420
421         for (id = 0; id < count; id++) {
422                 key_serial_t key = key_add(addrs[id], arg->user, pass, arg->keytype);
423                 if (key <= 0)
424                         fprintf(stderr, "error: Update credential key "
425                                 "for %s: %s\n", addrs[id], strerror(errno));
426         }
427
428         return EXIT_SUCCESS;
429 }
430
431 static int
432 check_session_keyring(void)
433 {
434         key_serial_t    ses_key, uses_key;
435
436         ses_key = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
437         if (ses_key == -1) {
438                 if (errno == ENOKEY)
439                         fprintf(stderr, "Error: you have no session keyring. "
440                                         "Consider using pam_keyinit to "
441                                         "install one.\n");
442                 else
443                         fprintf(stderr, "Error: unable to query session "
444                                         "keyring: %s\n", strerror(errno));
445                 return (int)ses_key;
446         }
447
448         /* A problem querying the user-session keyring isn't fatal. */
449         uses_key = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
450         if (uses_key == -1)
451                 return 0;
452
453         if (ses_key == uses_key)
454                 fprintf(stderr, "Warning: you have no persistent session "
455                                 "keyring. cifscreds keys will not persist "
456                                 "after this process exits. See "
457                                 "pam_keyinit(8).\n");
458         return 0;
459 }
460
461 int main(int argc, char **argv)
462 {
463         struct command *cmd, *best;
464         struct cmdarg arg;
465         int n;
466
467         memset(&arg, 0, sizeof(arg));
468         arg.keytype = 'a';
469
470         thisprogram = (char *)basename(argv[0]);
471         if (thisprogram == NULL)
472                 thisprogram = THIS_PROGRAM_NAME;
473
474         if (argc == 1)
475                 return usage();
476
477         while((n = getopt_long(argc, argv, "du:", longopts, NULL)) != -1) {
478                 switch (n) {
479                 case 'd':
480                         arg.keytype = (char) n;
481                         break;
482                 case 'u':
483                         arg.user = optarg;
484                         break;
485                 default:
486                         return usage();
487                 }
488         }
489
490         if (optind >= argc)
491                 return usage();
492
493         /* find the best fit command */
494         best = NULL;
495         n = strnlen(argv[optind], MAX_COMMAND_SIZE);
496
497         for (cmd = commands; cmd->action; cmd++) {
498                 if (memcmp(cmd->name, argv[optind], n) != 0)
499                         continue;
500
501                 if (cmd->name[n] == 0) {
502                         /* exact match */
503                         best = cmd;
504                         break;
505                 }
506
507                 /* partial match */
508                 if (best) {
509                         fprintf(stderr, "Ambiguous command\n");
510                         return EXIT_FAILURE;
511                 }
512
513                 best = cmd;
514         }
515
516         if (!best) {
517                 fprintf(stderr, "Unknown command\n");
518                 return EXIT_FAILURE;
519         }
520
521         /* second argument should be host or domain */
522         if (argc >= 3)
523                 arg.host = argv[optind + 1];
524
525         if (arg.host && arg.keytype == 'd' &&
526             strpbrk(arg.host, DOMAIN_DISALLOWED_CHARS)) {
527                 fprintf(stderr, "error: Domain name contains invalid characters\n");
528                 return EXIT_FAILURE;
529         }
530
531         if (arg.user == NULL)
532                 arg.user = getusername(getuid());
533
534         if (check_session_keyring())
535                 return EXIT_FAILURE;
536
537         return best->action(&arg);
538 }