Mercurial > projects > pwm
comparison cmd.c @ 12:8768fbd09bc5
Add generatepassword command to generate random passwords
Refactor and generalize handling of named arguments.
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Thu, 03 Aug 2017 10:22:07 +0200 |
parents | 85bce13237cf |
children | cf81eb0c2d5a |
comparison
equal
deleted
inserted
replaced
11:85bce13237cf | 12:8768fbd09bc5 |
---|---|
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
22 */ | 22 */ |
23 | 23 |
24 #include "compat.h" | 24 #include "compat.h" |
25 | 25 |
26 #include <ctype.h> | |
26 #ifdef HAVE_ERR_H | 27 #ifdef HAVE_ERR_H |
27 #include <err.h> | 28 #include <err.h> |
28 #endif /* HAVE_ERR_H */ | 29 #endif /* HAVE_ERR_H */ |
29 #include <errno.h> | 30 #include <errno.h> |
30 #include <limits.h> | 31 #include <limits.h> |
36 #include <string.h> | 37 #include <string.h> |
37 #include <time.h> | 38 #include <time.h> |
38 #include <unistd.h> | 39 #include <unistd.h> |
39 | 40 |
40 #include "cmd.h" | 41 #include "cmd.h" |
42 #include "pw.h" | |
41 #include "pwfile.h" | 43 #include "pwfile.h" |
42 #include "util.h" | 44 #include "util.h" |
43 | 45 |
44 #define TIME_FORMAT "%Y-%m-%dT%TZ" | 46 #define TIME_FORMAT "%Y-%m-%dT%TZ" |
45 #define TIME_SIZE (4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1) | 47 #define TIME_SIZE (4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1) |
48 | |
49 #define CHARS_DIGIT "0123456789" | |
50 #define CHARS_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
51 #define CHARS_LOWER "abcdefghijklmnopqrstuvwxyz" | |
52 #define CHARS_PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" | |
46 | 53 |
47 enum field_type { | 54 enum field_type { |
48 FIELD_UNKNOWN = -1, | 55 FIELD_UNKNOWN = -1, |
49 FIELD_GROUP, | 56 FIELD_GROUP, |
50 FIELD_TITLE, | 57 FIELD_TITLE, |
54 FIELD_URL, | 61 FIELD_URL, |
55 FIELD_MTIME, | 62 FIELD_MTIME, |
56 FIELD_CTIME | 63 FIELD_CTIME |
57 }; | 64 }; |
58 | 65 |
66 enum cmd_generatepassword_arg_type { | |
67 CMD_GP_ARG_UNKNOWN = -1, | |
68 CMD_GP_ARG_LEN, | |
69 CMD_GP_ARG_CHARS, | |
70 CMD_GP_ARG_CHARCLASS | |
71 }; | |
72 | |
73 enum charclass_type { | |
74 CHARCLASS_UNKNOWN = -1, | |
75 CHARCLASS_DIGIT, | |
76 CHARCLASS_UPPER, | |
77 CHARCLASS_LOWER, | |
78 CHARCLASS_PUNCT, | |
79 CHARCLASS_ALPHA, | |
80 CHARCLASS_ALNUM, | |
81 CHARCLASS_XDIGIT, | |
82 CHARCLASS_GRAPH | |
83 }; | |
84 | |
59 static enum cmd_return cmd_info(struct pwm_ctx *, int, char *[]); | 85 static enum cmd_return cmd_info(struct pwm_ctx *, int, char *[]); |
60 static enum cmd_return cmd_list(struct pwm_ctx *, int, char *[]); | 86 static enum cmd_return cmd_list(struct pwm_ctx *, int, char *[]); |
61 static enum cmd_return cmd_create(struct pwm_ctx *, int, char *[]); | 87 static enum cmd_return cmd_create(struct pwm_ctx *, int, char *[]); |
62 static enum cmd_return cmd_modify(struct pwm_ctx *, int, char *[]); | 88 static enum cmd_return cmd_modify(struct pwm_ctx *, int, char *[]); |
89 static enum cmd_return cmd_generatepassword(struct pwm_ctx *, int, char *[]); | |
63 static enum cmd_return cmd_remove(struct pwm_ctx *, int, char *[]); | 90 static enum cmd_return cmd_remove(struct pwm_ctx *, int, char *[]); |
64 static enum cmd_return cmd_show(struct pwm_ctx *, int, char *[]); | 91 static enum cmd_return cmd_show(struct pwm_ctx *, int, char *[]); |
65 static enum cmd_return cmd_pipe(struct pwm_ctx *, int, char *[]); | 92 static enum cmd_return cmd_pipe(struct pwm_ctx *, int, char *[]); |
66 static enum cmd_return cmd_creategroup(struct pwm_ctx *, int, char *[]); | 93 static enum cmd_return cmd_creategroup(struct pwm_ctx *, int, char *[]); |
67 static enum cmd_return cmd_removegroup(struct pwm_ctx *, int, char *[]); | 94 static enum cmd_return cmd_removegroup(struct pwm_ctx *, int, char *[]); |
68 static enum cmd_return cmd_changepassword(struct pwm_ctx *, int, char *[]); | 95 static enum cmd_return cmd_changepassword(struct pwm_ctx *, int, char *[]); |
69 static enum cmd_return cmd_help(struct pwm_ctx *, int, char *[]); | 96 static enum cmd_return cmd_help(struct pwm_ctx *, int, char *[]); |
70 static enum cmd_return cmd_write(struct pwm_ctx *, int, char *[]); | 97 static enum cmd_return cmd_write(struct pwm_ctx *, int, char *[]); |
71 static enum cmd_return cmd_quit(struct pwm_ctx *, int, char *[]); | 98 static enum cmd_return cmd_quit(struct pwm_ctx *, int, char *[]); |
72 | 99 |
73 static const char *field_names[] = { | 100 static const char *field_namev[] = { |
74 "group", | 101 "group", |
75 "title", | 102 "title", |
76 "username", | 103 "username", |
77 "password", | 104 "password", |
78 "notes", | 105 "notes", |
79 "url", | 106 "url", |
80 "ctime", | 107 "ctime", |
81 "mtime" | 108 "mtime", |
109 NULL | |
82 }; | 110 }; |
83 | 111 |
84 static const char *field_labels[] = { | 112 static const char *field_labels[] = { |
85 "Group: ", | 113 "Group: ", |
86 "Title: ", | 114 "Title: ", |
90 "URL: ", | 118 "URL: ", |
91 "Created: ", | 119 "Created: ", |
92 "Modified: " | 120 "Modified: " |
93 }; | 121 }; |
94 | 122 |
123 static const char *cmd_generatepassword_argv[] = { | |
124 "len", | |
125 "char", | |
126 "charclass", | |
127 NULL | |
128 }; | |
129 | |
130 static const char *charclass_namev[] = { | |
131 "digit", | |
132 "upper", | |
133 "lower", | |
134 "punct", | |
135 "alpha", | |
136 "alnum", | |
137 "xdigit", | |
138 "graph", | |
139 NULL | |
140 }; | |
141 | |
142 static const char *charclass_values[] = { | |
143 CHARS_DIGIT, | |
144 CHARS_UPPER, | |
145 CHARS_LOWER, | |
146 CHARS_PUNCT, | |
147 CHARS_UPPER CHARS_LOWER, | |
148 CHARS_DIGIT CHARS_UPPER CHARS_LOWER, | |
149 CHARS_DIGIT "abcdef", | |
150 CHARS_DIGIT CHARS_UPPER CHARS_LOWER CHARS_PUNCT | |
151 }; | |
152 | |
95 static struct cmd cmds[] = { | 153 static struct cmd cmds[] = { |
96 { "i", "info", "info", "Show metadata information about the current file", | 154 { "i", "info", "info", "Show metadata information about the current file", |
97 cmd_info }, | 155 cmd_info }, |
98 { "ls", "list", "list [field~regex ...]", "List entries", cmd_list }, | 156 { "ls", "list", "list [field~regex ...]", "List entries", cmd_list }, |
99 { "c", "create", "create field=value ...", "Create entry", cmd_create }, | 157 { "c", "create", "create field=value ...", "Create entry", cmd_create }, |
100 { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify }, | 158 { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify }, |
159 { "gp", "generatepassword", "generatepassword [id] [len=n] [chars=n:chars] " | |
160 "[charclass=n:class] ...", "Randomly generate a password", | |
161 cmd_generatepassword }, | |
101 { "rm", "remove", "remove id", "Delete entry", cmd_remove }, | 162 { "rm", "remove", "remove id", "Delete entry", cmd_remove }, |
102 { "s", "show", "show id field", "Show entry", cmd_show }, | 163 { "s", "show", "show id field", "Show entry", cmd_show }, |
103 { "p", "pipe", "pipe id field command", "Pipe entry to external command", | 164 { "p", "pipe", "pipe id field command", "Pipe entry to external command", |
104 cmd_pipe }, | 165 cmd_pipe }, |
105 { "cg", "creategroup", "creategroup name", "Create empty group", | 166 { "cg", "creategroup", "creategroup name", "Create empty group", |
112 { "w", "write", "write", "Write the database", cmd_write }, | 173 { "w", "write", "write", "Write the database", cmd_write }, |
113 { "q", "quit", "quit", "Quit", cmd_quit }, | 174 { "q", "quit", "quit", "Quit", cmd_quit }, |
114 { 0 } | 175 { 0 } |
115 }; | 176 }; |
116 | 177 |
117 static enum field_type | 178 static int |
118 parse_field(char *field_arg, int sep, char **valuep) | 179 parse_arg(char *arg, const char *namev[], int sep, char **valuep) |
119 { | 180 { |
120 int i; | 181 size_t i; |
121 size_t field_name_len; | 182 size_t name_len; |
122 | 183 |
123 for (i = 0; i < (int)COUNTOF(field_names); i++) { | 184 for (i = 0; namev[i] != NULL; i++) { |
124 field_name_len = strlen(field_names[i]); | 185 name_len = strlen(namev[i]); |
125 if ((strncmp(field_names[i], field_arg, field_name_len) == 0) && | 186 if ((strncmp(namev[i], arg, name_len) == 0) && |
126 (field_arg[field_name_len] == sep)) { | 187 (arg[name_len] == sep)) { |
127 if (valuep != NULL) { | 188 if (valuep != NULL) { |
128 *valuep = field_arg + field_name_len + 1; | 189 *valuep = arg + name_len + 1; |
129 } | 190 } |
130 return (i); | 191 return (i); |
131 } | 192 } |
132 } | 193 } |
133 | 194 |
134 return (FIELD_UNKNOWN); | 195 return (-1); |
135 } | 196 } |
136 | 197 |
137 static int | 198 static int |
138 parse_id(const char *arg, unsigned int *idp) | 199 parse_id(const char *arg, unsigned int *idp) |
139 { | 200 { |
201 union list_item **list = NULL; | 262 union list_item **list = NULL; |
202 size_t j; | 263 size_t j; |
203 struct record *record; | 264 struct record *record; |
204 | 265 |
205 for (i = 1; i < argc; i++) { | 266 for (i = 1; i < argc; i++) { |
206 type = parse_field(argv[i], '~', &value); | 267 type = parse_arg(argv[i], field_namev, '~', &value); |
207 if (type == FIELD_UNKNOWN) { | 268 if (type == FIELD_UNKNOWN) { |
208 fprintf(stderr, "bad field name \"%s\"\n", argv[i]); | 269 fprintf(stderr, "bad field name \"%s\"\n", argv[i]); |
209 goto out; | 270 goto out; |
210 } | 271 } |
211 if (value[0] == '\0') { | 272 if (value[0] == '\0') { |
317 if (argc < 2) { | 378 if (argc < 2) { |
318 return (CMD_USAGE); | 379 return (CMD_USAGE); |
319 } | 380 } |
320 | 381 |
321 for (i = 1; i < argc; i++) { | 382 for (i = 1; i < argc; i++) { |
322 type = parse_field(argv[i], '=', &value); | 383 type = parse_arg(argv[i], field_namev, '=', &value); |
323 if (type == FIELD_UNKNOWN) { | 384 if (type == FIELD_UNKNOWN) { |
324 fprintf(stderr, "bad field assignment \"%s\"\n", | 385 fprintf(stderr, "bad field assignment \"%s\"\n", |
325 argv[i]); | 386 argv[i]); |
326 } | 387 } |
327 if (value[0] == '\0') { | 388 if (value[0] == '\0') { |
375 fprintf(stderr, "invalid id %s\n", argv[1]); | 436 fprintf(stderr, "invalid id %s\n", argv[1]); |
376 return (CMD_ERR); | 437 return (CMD_ERR); |
377 } | 438 } |
378 | 439 |
379 for (i = 2; i < argc; i++) { | 440 for (i = 2; i < argc; i++) { |
380 type = parse_field(argv[i], '=', &value); | 441 type = parse_arg(argv[i], field_namev, '=', &value); |
381 if (type == FIELD_UNKNOWN) { | 442 if (type == FIELD_UNKNOWN) { |
382 fprintf(stderr, "bad field assignment \"%s\"\n", | 443 fprintf(stderr, "bad field assignment \"%s\"\n", |
383 argv[i]); | 444 argv[i]); |
384 return (CMD_ERR); | 445 return (CMD_ERR); |
385 } | 446 } |
413 } | 474 } |
414 | 475 |
415 pwfile_modify_record(ctx, id, &record); | 476 pwfile_modify_record(ctx, id, &record); |
416 | 477 |
417 return (CMD_OK); | 478 return (CMD_OK); |
479 } | |
480 | |
481 static enum cmd_return | |
482 cmd_generatepassword(struct pwm_ctx *ctx, int argc, char *argv[]) | |
483 { | |
484 enum cmd_return retval = CMD_ERR; | |
485 unsigned int id = 0; | |
486 int i = 1; | |
487 char *value = NULL; | |
488 long x; | |
489 char *p; | |
490 size_t password_len = 16; | |
491 size_t chars_min; | |
492 const char *chars; | |
493 struct pw_char_group *char_groupv = NULL; | |
494 size_t char_groupv_len = 0; | |
495 int charclass; | |
496 size_t j; | |
497 char password[PWS3_MAX_PASSWORD_LEN + 1] = { 0 }; | |
498 | |
499 /* check if first argument is an id */ | |
500 if ((argc > 1) && (parse_id(argv[1], &id) == 0)) { | |
501 i++; | |
502 } | |
503 | |
504 for (; i < argc; i++) { | |
505 switch (parse_arg(argv[i], cmd_generatepassword_argv, '=', | |
506 &value)) { | |
507 case CMD_GP_ARG_LEN: | |
508 errno = 0; | |
509 x = strtol(value, &p, 10); | |
510 if ((errno != 0) || (*value == '\0') || (*p != '\0') || | |
511 (x > PWS3_MAX_PASSWORD_LEN) || (x <= 0)) { | |
512 fprintf(stderr, "invalid password length " | |
513 "\"%s\"\n", argv[i]); | |
514 goto out; | |
515 } | |
516 password_len = x; | |
517 break; | |
518 case CMD_GP_ARG_CHARS: | |
519 errno = 0; | |
520 x = strtol(value, &p, 10); | |
521 if ((errno != 0) || (*value == '\0') || (*p != ':') || | |
522 (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) { | |
523 fprintf(stderr, "invalid minimum number of " | |
524 "characters \"%s\"\n", argv[i]); | |
525 goto out; | |
526 } | |
527 chars_min = x; | |
528 | |
529 chars = ++p; | |
530 while (*p != '\0') { | |
531 if (!isascii(*p) || !isprint(*p)) { | |
532 fprintf(stderr, "invalid character in " | |
533 "character group \"%s\"\n", | |
534 argv[i]); | |
535 goto out; | |
536 } | |
537 p++; | |
538 } | |
539 | |
540 char_groupv = xrealloc(char_groupv, | |
541 sizeof (struct pw_char_group) * (char_groupv_len + | |
542 1)); | |
543 char_groupv[char_groupv_len].chars = chars; | |
544 char_groupv[char_groupv_len].chars_min = chars_min; | |
545 char_groupv_len++; | |
546 break; | |
547 case CMD_GP_ARG_CHARCLASS: | |
548 errno = 0; | |
549 x = strtol(value, &p, 10); | |
550 if ((errno != 0) || (*value == '\0') || (*p != ':') || | |
551 (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) { | |
552 fprintf(stderr, "invalid minimum number of " | |
553 "characters \"%s\"\n", argv[i]); | |
554 goto out; | |
555 } | |
556 chars_min = x; | |
557 | |
558 charclass = parse_arg(++p, charclass_namev, '\0', NULL); | |
559 if (charclass < 0) { | |
560 fprintf(stderr, "unknown character class " | |
561 "\"%s\"\n", argv[i]); | |
562 goto out; | |
563 } | |
564 chars = charclass_values[charclass]; | |
565 char_groupv = xrealloc(char_groupv, | |
566 sizeof (struct pw_char_group) * (char_groupv_len + | |
567 1)); | |
568 char_groupv[char_groupv_len].chars = chars; | |
569 char_groupv[char_groupv_len].chars_min = chars_min; | |
570 char_groupv_len++; | |
571 break; | |
572 default: | |
573 fprintf(stderr, "invalid argument \"%s\"\n", argv[i]); | |
574 retval = CMD_USAGE; | |
575 goto out; | |
576 } | |
577 } | |
578 | |
579 for (j = 0; j < char_groupv_len; j++) { | |
580 if (char_groupv[j].chars_min > password_len) { | |
581 fprintf(stderr, "invalid minimum number of " | |
582 "characters \"%zu:%s\"\n", char_groupv[j].chars_min, | |
583 char_groupv[j].chars); | |
584 goto out; | |
585 } | |
586 } | |
587 | |
588 if (char_groupv_len == 0) { | |
589 /* use defaults */ | |
590 char_groupv = xmalloc(sizeof (struct pw_char_group)); | |
591 char_groupv[0].chars = charclass_values[CHARCLASS_GRAPH]; | |
592 char_groupv[0].chars_min = 0; | |
593 char_groupv_len++; | |
594 } | |
595 | |
596 if (pw_genrandom(char_groupv, char_groupv_len, password, | |
597 password_len) != 0) { | |
598 fprintf(stderr, "failed to generate password that meets the " | |
599 "given constraints\n"); | |
600 goto out; | |
601 } | |
602 | |
603 if (id != 0) { | |
604 if (pwfile_modify_record(ctx, id, | |
605 &(struct record){ .password = password }) != 0) { | |
606 fprintf(stderr, "record %u does not exist\n", id); | |
607 goto out; | |
608 } | |
609 } else { | |
610 printf("%s\n", password); | |
611 } | |
612 | |
613 retval = CMD_OK; | |
614 | |
615 out: | |
616 free(char_groupv); | |
617 | |
618 return (retval); | |
418 } | 619 } |
419 | 620 |
420 static enum cmd_return | 621 static enum cmd_return |
421 cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[]) | 622 cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[]) |
422 { | 623 { |
516 { | 717 { |
517 unsigned int id; | 718 unsigned int id; |
518 struct record *record; | 719 struct record *record; |
519 int i; | 720 int i; |
520 enum field_type type; | 721 enum field_type type; |
521 int fields[COUNTOF(field_names)] = { 0 }; | 722 int fields[COUNTOF(field_namev) - 1] = { 0 }; |
522 | 723 |
523 if (argc < 2) { | 724 if (argc < 2) { |
524 return (CMD_USAGE); | 725 return (CMD_USAGE); |
525 } | 726 } |
526 | 727 |
528 fprintf(stderr, "invalid id %s\n", argv[1]); | 729 fprintf(stderr, "invalid id %s\n", argv[1]); |
529 return (CMD_ERR); | 730 return (CMD_ERR); |
530 } | 731 } |
531 | 732 |
532 for (i = 2; i < argc; i++) { | 733 for (i = 2; i < argc; i++) { |
533 type = parse_field(argv[i], '\0', NULL); | 734 type = parse_arg(argv[i], field_namev, '\0', NULL); |
534 if (type < 0) { | 735 if (type < 0) { |
535 fprintf(stderr, "bad field name \"%s\"\n", argv[i]); | 736 fprintf(stderr, "bad field name \"%s\"\n", argv[i]); |
536 return (CMD_ERR); | 737 return (CMD_ERR); |
537 } | 738 } |
538 fields[type] = 1; | 739 fields[type] = 1; |
554 { | 755 { |
555 enum cmd_return retval = CMD_ERR; | 756 enum cmd_return retval = CMD_ERR; |
556 unsigned int id; | 757 unsigned int id; |
557 struct record *record = NULL; | 758 struct record *record = NULL; |
558 enum field_type type; | 759 enum field_type type; |
559 int fields[COUNTOF(field_names)] = { 0 }; | 760 int fields[COUNTOF(field_namev) - 1] = { 0 }; |
560 FILE *fp = NULL; | 761 FILE *fp = NULL; |
561 | 762 |
562 if (argc != 4) { | 763 if (argc != 4) { |
563 return (CMD_USAGE); | 764 return (CMD_USAGE); |
564 } | 765 } |
566 if (parse_id(argv[1], &id) != 0) { | 767 if (parse_id(argv[1], &id) != 0) { |
567 fprintf(stderr, "invalid id %s\n", argv[1]); | 768 fprintf(stderr, "invalid id %s\n", argv[1]); |
568 return (CMD_ERR); | 769 return (CMD_ERR); |
569 } | 770 } |
570 | 771 |
571 type = parse_field(argv[2], '\0', NULL); | 772 type = parse_arg(argv[2], field_namev, '\0', NULL); |
572 if (type < 0) { | 773 if (type < 0) { |
573 fprintf(stderr, "bad field name \"%s\"\n", argv[2]); | 774 fprintf(stderr, "bad field name \"%s\"\n", argv[2]); |
574 return (CMD_ERR); | 775 return (CMD_ERR); |
575 } | 776 } |
576 fields[type] = 1; | 777 fields[type] = 1; |