setcifsacl: fix up endianness conversions
[jlayton/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 "mount.h"
33 #include "resolve_host.h"
34 #include "util.h"
35
36 #define THIS_PROGRAM_NAME "cifscreds"
37 #define KEY_PREFIX        "cifs"
38
39 /* max length of appropriate command */
40 #define MAX_COMMAND_SIZE 32
41
42 /* max length of username, password and domain name */
43 #define MAX_USERNAME_SIZE 32
44 #define MOUNT_PASSWD_SIZE 128
45 #define MAX_DOMAIN_SIZE 64
46
47 /*
48  * disallowed characters for user and domain names. See:
49  * http://technet.microsoft.com/en-us/library/bb726984.aspx
50  * http://support.microsoft.com/kb/909264
51  */
52 #define USER_DISALLOWED_CHARS "\\/\"[]:|<>+=;,?*"
53 #define DOMAIN_DISALLOWED_CHARS "\\/:*?\"<>|"
54
55 /* destination keyring */
56 #define DEST_KEYRING KEY_SPEC_SESSION_KEYRING
57 #define CIFS_KEY_TYPE  "logon"
58 #define CIFS_KEY_PERMS (KEY_POS_VIEW|KEY_POS_WRITE|KEY_POS_SEARCH| \
59                         KEY_USR_VIEW|KEY_USR_WRITE|KEY_USR_SEARCH)
60
61 struct cmdarg {
62         char            *host;
63         char            *user;
64         char            keytype;
65 };
66
67 struct command {
68         int (*action)(struct cmdarg *arg);
69         const char      name[MAX_COMMAND_SIZE];
70         const char      *format;
71 };
72
73 static int cifscreds_add(struct cmdarg *arg);
74 static int cifscreds_clear(struct cmdarg *arg);
75 static int cifscreds_clearall(struct cmdarg *arg);
76 static int cifscreds_update(struct cmdarg *arg);
77
78 const char *thisprogram;
79
80 struct command commands[] = {
81         { cifscreds_add,        "add",          "[-u username] [-d] <host|domain>" },
82         { cifscreds_clear,      "clear",        "[-u username] [-d] <host|domain>" },
83         { cifscreds_clearall,   "clearall",     "" },
84         { cifscreds_update,     "update",       "[-u username] [-d] <host|domain>" },
85         { NULL, "", NULL }
86 };
87
88 struct option longopts[] = {
89         {"username", 1, NULL, 'u'},
90         {"domain", 0, NULL, 'd' },
91         {NULL, 0, NULL, 0}
92 };
93
94 /* display usage information */
95 static int
96 usage(void)
97 {
98         struct command *cmd;
99
100         fprintf(stderr, "Usage:\n");
101         for (cmd = commands; cmd->action; cmd++)
102                 fprintf(stderr, "\t%s %s %s\n", thisprogram,
103                         cmd->name, cmd->format);
104         fprintf(stderr, "\n");
105
106         return EXIT_FAILURE;
107 }
108
109 /* search a specific key in keyring */
110 static key_serial_t
111 key_search(const char *addr, char keytype)
112 {
113         char desc[INET6_ADDRSTRLEN + sizeof(KEY_PREFIX) + 4];
114
115         sprintf(desc, "%s:%c:%s", KEY_PREFIX, keytype, addr);
116
117         return keyctl_search(DEST_KEYRING, CIFS_KEY_TYPE, desc, 0);
118 }
119
120 /* search all program's keys in keyring */
121 static key_serial_t key_search_all(void)
122 {
123         key_serial_t key, *pk;
124         void *keylist;
125         char *buffer;
126         int count, dpos, n, ret;
127
128         /* read the key payload data */
129         count = keyctl_read_alloc(DEST_KEYRING, &keylist);
130         if (count < 0)
131                 return 0;
132
133         count /= sizeof(key_serial_t);
134
135         if (count == 0) {
136                 ret = 0;
137                 goto key_search_all_out;
138         }
139
140         /* list the keys in the keyring */
141         pk = keylist;
142         do {
143                 key = *pk++;
144
145                 ret = keyctl_describe_alloc(key, &buffer);
146                 if (ret < 0)
147                         continue;
148
149                 n = sscanf(buffer, "%*[^;];%*d;%*d;%*x;%n", &dpos);
150                 if (n) {
151                         free(buffer);
152                         continue;
153                 }
154
155                 if (strstr(buffer + dpos, KEY_PREFIX ":") ==
156                         buffer + dpos
157                 ) {
158                         ret = key;
159                         free(buffer);
160                         goto key_search_all_out;
161                 }
162                 free(buffer);
163
164         } while (--count);
165
166         ret = 0;
167
168 key_search_all_out:
169         free(keylist);
170         return ret;
171 }
172
173 /* add or update a specific key to keyring */
174 static key_serial_t
175 key_add(const char *addr, const char *user, const char *pass, char keytype)
176 {
177         int len;
178         char desc[INET6_ADDRSTRLEN + sizeof(KEY_PREFIX) + 4];
179         char val[MOUNT_PASSWD_SIZE +  MAX_USERNAME_SIZE + 2];
180
181         /* set key description */
182         sprintf(desc, "%s:%c:%s", KEY_PREFIX, keytype, addr);
183
184         /* set payload contents */
185         len = sprintf(val, "%s:%s", user, pass);
186
187         return add_key(CIFS_KEY_TYPE, desc, val, len + 1, DEST_KEYRING);
188 }
189
190 /* add command handler */
191 static int cifscreds_add(struct cmdarg *arg)
192 {
193         char addrstr[MAX_ADDR_LIST_LEN];
194         char *currentaddress, *nextaddress;
195         char *pass;
196         int ret = 0;
197
198         if (arg->host == NULL || arg->user == NULL)
199                 return usage();
200
201         if (arg->keytype == 'd')
202                 strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN);
203         else
204                 ret = resolve_host(arg->host, addrstr);
205
206         switch (ret) {
207         case EX_USAGE:
208                 fprintf(stderr, "error: Could not resolve address "
209                         "for %s\n", arg->host);
210                 return EXIT_FAILURE;
211
212         case EX_SYSERR:
213                 fprintf(stderr, "error: Problem parsing address list\n");
214                 return EXIT_FAILURE;
215         }
216
217         if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) {
218                 fprintf(stderr, "error: Incorrect username\n");
219                 return EXIT_FAILURE;
220         }
221
222         /* search for same credentials stashed for current host */
223         currentaddress = addrstr;
224         nextaddress = strchr(currentaddress, ',');
225         if (nextaddress)
226                 *nextaddress++ = '\0';
227
228         while (currentaddress) {
229                 if (key_search(currentaddress, arg->keytype) > 0) {
230                         printf("You already have stashed credentials "
231                                 "for %s (%s)\n", currentaddress, arg->host);
232                         printf("If you want to update them use:\n");
233                         printf("\t%s update\n", thisprogram);
234
235                         return EXIT_FAILURE;
236                 }
237
238                 currentaddress = nextaddress;
239                 if (currentaddress) {
240                         *(currentaddress - 1) = ',';
241                         nextaddress = strchr(currentaddress, ',');
242                         if (nextaddress)
243                                 *nextaddress++ = '\0';
244                 }
245         }
246
247         /*
248          * if there isn't same credentials stashed add them to keyring
249          * and set permisson mask
250          */
251         pass = getpass("Password: ");
252
253         currentaddress = addrstr;
254         nextaddress = strchr(currentaddress, ',');
255         if (nextaddress)
256                 *nextaddress++ = '\0';
257
258         while (currentaddress) {
259                 key_serial_t key = key_add(currentaddress, arg->user, pass, arg->keytype);
260                 if (key <= 0) {
261                         fprintf(stderr, "error: Add credential key for %s\n",
262                                 currentaddress);
263                 } else {
264                         if (keyctl(KEYCTL_SETPERM, key, CIFS_KEY_PERMS) < 0) {
265                                 fprintf(stderr, "error: Setting permissons "
266                                         "on key, attempt to delete...\n");
267
268                                 if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
269                                         fprintf(stderr, "error: Deleting key from "
270                                                 "keyring for %s (%s)\n",
271                                                 currentaddress, arg->host);
272                                 }
273                         }
274                 }
275
276                 currentaddress = nextaddress;
277                 if (currentaddress) {
278                         nextaddress = strchr(currentaddress, ',');
279                         if (nextaddress)
280                                 *nextaddress++ = '\0';
281                 }
282         }
283
284         return EXIT_SUCCESS;
285 }
286
287 /* clear command handler */
288 static int cifscreds_clear(struct cmdarg *arg)
289 {
290         char addrstr[MAX_ADDR_LIST_LEN];
291         char *currentaddress, *nextaddress;
292         int ret = 0, count = 0, errors = 0;
293
294         if (arg->host == NULL || arg->user == NULL)
295                 return usage();
296
297         if (arg->keytype == 'd')
298                 strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN);
299         else
300                 ret = resolve_host(arg->host, addrstr);
301
302         switch (ret) {
303         case EX_USAGE:
304                 fprintf(stderr, "error: Could not resolve address "
305                         "for %s\n", arg->host);
306                 return EXIT_FAILURE;
307
308         case EX_SYSERR:
309                 fprintf(stderr, "error: Problem parsing address list\n");
310                 return EXIT_FAILURE;
311         }
312
313         if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) {
314                 fprintf(stderr, "error: Incorrect username\n");
315                 return EXIT_FAILURE;
316         }
317
318         /*
319          * search for same credentials stashed for current host
320          * and unlink them from session keyring
321          */
322         currentaddress = addrstr;
323         nextaddress = strchr(currentaddress, ',');
324         if (nextaddress)
325                 *nextaddress++ = '\0';
326
327         while (currentaddress) {
328                 key_serial_t key = key_search(currentaddress, arg->keytype);
329                 if (key > 0) {
330                         if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
331                                 fprintf(stderr, "error: Removing key from "
332                                         "keyring for %s (%s)\n",
333                                         currentaddress, arg->host);
334                                 errors++;
335                         } else {
336                                 count++;
337                         }
338                 }
339
340                 currentaddress = nextaddress;
341                 if (currentaddress) {
342                         nextaddress = strchr(currentaddress, ',');
343                         if (nextaddress)
344                                 *nextaddress++ = '\0';
345                 }
346         }
347
348         if (!count && !errors) {
349                 printf("You have no same stashed credentials "
350                         " for %s\n", arg->host);
351                 printf("If you want to add them use:\n");
352                 printf("\t%s add\n", thisprogram);
353
354                 return EXIT_FAILURE;
355         }
356
357         return EXIT_SUCCESS;
358 }
359
360 /* clearall command handler */
361 static int cifscreds_clearall(struct cmdarg *arg __attribute__ ((unused)))
362 {
363         key_serial_t key;
364         int count = 0, errors = 0;
365
366         /*
367          * search for all program's credentials stashed in session keyring
368          * and then unlink them
369          */
370         do {
371                 key = key_search_all();
372                 if (key > 0) {
373                         if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) {
374                                 fprintf(stderr, "error: Deleting key "
375                                         "from keyring");
376                                 errors++;
377                         } else {
378                                 count++;
379                         }
380                 }
381         } while (key > 0);
382
383         if (!count && !errors) {
384                 printf("You have no stashed " KEY_PREFIX
385                         " credentials\n");
386                 printf("If you want to add them use:\n");
387                 printf("\t%s add\n", thisprogram);
388
389                 return EXIT_FAILURE;
390         }
391
392         return EXIT_SUCCESS;
393 }
394
395 /* update command handler */
396 static int cifscreds_update(struct cmdarg *arg)
397 {
398         char addrstr[MAX_ADDR_LIST_LEN];
399         char *currentaddress, *nextaddress, *pass;
400         char *addrs[16];
401         int ret = 0, id, count = 0;
402
403         if (arg->host == NULL || arg->user == NULL)
404                 return usage();
405
406         if (arg->keytype == 'd')
407                 strlcpy(addrstr, arg->host, MAX_ADDR_LIST_LEN);
408         else
409                 ret = resolve_host(arg->host, addrstr);
410
411         switch (ret) {
412         case EX_USAGE:
413                 fprintf(stderr, "error: Could not resolve address "
414                         "for %s\n", arg->host);
415                 return EXIT_FAILURE;
416
417         case EX_SYSERR:
418                 fprintf(stderr, "error: Problem parsing address list\n");
419                 return EXIT_FAILURE;
420         }
421
422         if (strpbrk(arg->user, USER_DISALLOWED_CHARS)) {
423                 fprintf(stderr, "error: Incorrect username\n");
424                 return EXIT_FAILURE;
425         }
426
427         /* search for necessary credentials stashed in session keyring */
428         currentaddress = addrstr;
429         nextaddress = strchr(currentaddress, ',');
430         if (nextaddress)
431                 *nextaddress++ = '\0';
432
433         while (currentaddress) {
434                 if (key_search(currentaddress, arg->keytype) > 0) {
435                         addrs[count] = currentaddress;
436                         count++;
437                 }
438
439                 currentaddress = nextaddress;
440                 if (currentaddress) {
441                         nextaddress = strchr(currentaddress, ',');
442                         if (nextaddress)
443                                 *nextaddress++ = '\0';
444                 }
445         }
446
447         if (!count) {
448                 printf("You have no same stashed credentials "
449                         "for %s\n", arg->host);
450                 printf("If you want to add them use:\n");
451                 printf("\t%s add\n", thisprogram);
452
453                 return EXIT_FAILURE;
454         }
455
456         /* update payload of found keys */
457         pass = getpass("Password: ");
458
459         for (id = 0; id < count; id++) {
460                 key_serial_t key = key_add(addrs[id], arg->user, pass, arg->keytype);
461                 if (key <= 0)
462                         fprintf(stderr, "error: Update credential key "
463                                 "for %s\n", addrs[id]);
464         }
465
466         return EXIT_SUCCESS;
467 }
468
469 static int
470 check_session_keyring(void)
471 {
472         key_serial_t    ses_key, uses_key;
473
474         ses_key = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
475         if (ses_key == -1) {
476                 if (errno == ENOKEY)
477                         fprintf(stderr, "Error: you have no session keyring. "
478                                         "Consider using pam_keyinit to "
479                                         "install one.\n");
480                 else
481                         fprintf(stderr, "Error: unable to query session "
482                                         "keyring: %s\n", strerror(errno));
483                 return (int)ses_key;
484         }
485
486         /* A problem querying the user-session keyring isn't fatal. */
487         uses_key = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
488         if (uses_key == -1)
489                 return 0;
490
491         if (ses_key == uses_key)
492                 fprintf(stderr, "Warning: you have no persistent session "
493                                 "keyring. cifscreds keys will not persist "
494                                 "after this process exits. See "
495                                 "pam_keyinit(8).\n");
496         return 0;
497 }
498
499 int main(int argc, char **argv)
500 {
501         struct command *cmd, *best;
502         struct cmdarg arg;
503         int n;
504
505         memset(&arg, 0, sizeof(arg));
506         arg.keytype = 'a';
507
508         thisprogram = (char *)basename(argv[0]);
509         if (thisprogram == NULL)
510                 thisprogram = THIS_PROGRAM_NAME;
511
512         if (argc == 1)
513                 return usage();
514
515         while((n = getopt_long(argc, argv, "du:", longopts, NULL)) != -1) {
516                 switch (n) {
517                 case 'd':
518                         arg.keytype = (char) n;
519                         break;
520                 case 'u':
521                         arg.user = optarg;
522                         break;
523                 default:
524                         return usage();
525                 }
526         }
527
528         /* find the best fit command */
529         best = NULL;
530         n = strnlen(argv[optind], MAX_COMMAND_SIZE);
531
532         for (cmd = commands; cmd->action; cmd++) {
533                 if (memcmp(cmd->name, argv[optind], n) != 0)
534                         continue;
535
536                 if (cmd->name[n] == 0) {
537                         /* exact match */
538                         best = cmd;
539                         break;
540                 }
541
542                 /* partial match */
543                 if (best) {
544                         fprintf(stderr, "Ambiguous command\n");
545                         return EXIT_FAILURE;
546                 }
547
548                 best = cmd;
549         }
550
551         if (!best) {
552                 fprintf(stderr, "Unknown command\n");
553                 return EXIT_FAILURE;
554         }
555
556         /* second argument should be host or domain */
557         if (argc >= 3)
558                 arg.host = argv[optind + 1];
559
560         if (arg.host && arg.keytype == 'd' &&
561             strpbrk(arg.host, DOMAIN_DISALLOWED_CHARS)) {
562                 fprintf(stderr, "error: Domain name contains invalid characters\n");
563                 return EXIT_FAILURE;
564         }
565
566         if (arg.user == NULL)
567                 arg.user = getusername(getuid());
568
569         if (check_session_keyring())
570                 return EXIT_FAILURE;
571
572         return best->action(&arg);
573 }