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