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;