projects/pwm

view cmd.c @ 26:5bdea77d0c1d

Add pwm-clip utility for setting the X11 CLIPBOARD selection
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Thu Sep 21 09:45:59 2017 +0200 (2017-09-21)
parents ec01c579024a
children 722a45b4028b
line source
1 /*
2 * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
24 #include "compat.h"
26 #include <ctype.h>
27 #ifdef HAVE_ERR_H
28 #include <err.h>
29 #endif /* HAVE_ERR_H */
30 #include <errno.h>
31 #include <limits.h>
32 #include <regex.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
38 #include "cmd.h"
39 #include "io.h"
40 #include "pager.h"
41 #include "proc.h"
42 #include "pw.h"
43 #include "pwfile.h"
44 #include "util.h"
46 #define TIME_FORMAT "%Y-%m-%dT%TZ"
47 #define TIME_SIZE (4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1)
49 #define CHARS_DIGIT "0123456789"
50 #define CHARS_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
51 #define CHARS_LOWER "abcdefghijklmnopqrstuvwxyz"
52 #define CHARS_PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
54 enum field_type {
55 FIELD_UNKNOWN = -1,
56 FIELD_GROUP,
57 FIELD_TITLE,
58 FIELD_USERNAME,
59 FIELD_PASSWORD,
60 FIELD_NOTES,
61 FIELD_URL,
62 FIELD_MTIME,
63 FIELD_CTIME
64 };
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 };
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 };
85 static enum cmd_return cmd_status(struct pwm_ctx *, int, char *[]);
86 static enum cmd_return cmd_info(struct pwm_ctx *, int, char *[]);
87 static enum cmd_return cmd_list(struct pwm_ctx *, int, char *[]);
88 static enum cmd_return cmd_create(struct pwm_ctx *, int, char *[]);
89 static enum cmd_return cmd_modify(struct pwm_ctx *, int, char *[]);
90 static enum cmd_return cmd_generatepassword(struct pwm_ctx *, int, char *[]);
91 static enum cmd_return cmd_remove(struct pwm_ctx *, int, char *[]);
92 static enum cmd_return cmd_show(struct pwm_ctx *, int, char *[]);
93 static enum cmd_return cmd_pipe(struct pwm_ctx *, int, char *[]);
94 static enum cmd_return cmd_creategroup(struct pwm_ctx *, int, char *[]);
95 static enum cmd_return cmd_removegroup(struct pwm_ctx *, int, char *[]);
96 static enum cmd_return cmd_changepassword(struct pwm_ctx *, int, char *[]);
97 static enum cmd_return cmd_help(struct pwm_ctx *, int, char *[]);
98 static enum cmd_return cmd_write(struct pwm_ctx *, int, char *[]);
99 static enum cmd_return cmd_quit(struct pwm_ctx *, int, char *[]);
101 static const char *field_namev[] = {
102 "group",
103 "title",
104 "username",
105 "password",
106 "notes",
107 "url",
108 "ctime",
109 "mtime",
110 NULL
111 };
113 static const char *field_labels[] = {
114 "Group: ",
115 "Title: ",
116 "Username: ",
117 "Password: ",
118 "Notes: ",
119 "URL: ",
120 "Created: ",
121 "Modified: "
122 };
124 static const char *cmd_generatepassword_argv[] = {
125 "len",
126 "char",
127 "charclass",
128 NULL
129 };
131 static const char *charclass_namev[] = {
132 "digit",
133 "upper",
134 "lower",
135 "punct",
136 "alpha",
137 "alnum",
138 "xdigit",
139 "graph",
140 NULL
141 };
143 static const char *charclass_values[] = {
144 CHARS_DIGIT,
145 CHARS_UPPER,
146 CHARS_LOWER,
147 CHARS_PUNCT,
148 CHARS_UPPER CHARS_LOWER,
149 CHARS_DIGIT CHARS_UPPER CHARS_LOWER,
150 CHARS_DIGIT "abcdef",
151 CHARS_DIGIT CHARS_UPPER CHARS_LOWER CHARS_PUNCT
152 };
154 static struct cmd cmds[] = {
155 { "t", "status", "status", "Redisplay an error message of the previous "
156 "command and unsaved changes", cmd_status },
157 { "i", "info", "info", "Show metadata information about the current file",
158 cmd_info },
159 { "ls", "list", "list [field~regex ...]", "List entries", cmd_list },
160 { "c", "create", "create field=value ...", "Create entry", cmd_create },
161 { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify },
162 { "gp", "generatepassword", "generatepassword [id] [len=n] [chars=n:chars] "
163 "[charclass=n:class] ...", "Randomly generate a password",
164 cmd_generatepassword },
165 { "rm", "remove", "remove id", "Delete entry", cmd_remove },
166 { "s", "show", "show id field", "Show entry", cmd_show },
167 { "p", "pipe", "pipe id field command", "Pipe entry to external command",
168 cmd_pipe },
169 { "cg", "creategroup", "creategroup name", "Create empty group",
170 cmd_creategroup },
171 { "rg", "removegroup", "removegroup name", "Delete empty group",
172 cmd_removegroup },
173 { "ch", "changepassword", "changepassword", "Change password",
174 cmd_changepassword },
175 { "h", "help", "help [command]", "Show help text", cmd_help },
176 { "w", "write", "write", "Write the database", cmd_write },
177 { "q", "quit", "quit", "Quit", cmd_quit },
178 { "Q", "Quit", "Quit", "Quit without checking", cmd_quit },
179 { 0 }
180 };
182 static int
183 parse_arg(char *arg, const char *namev[], int sep, char **valuep)
184 {
185 size_t i;
186 size_t name_len;
188 for (i = 0; namev[i] != NULL; i++) {
189 name_len = strlen(namev[i]);
190 if ((strncmp(namev[i], arg, name_len) == 0) &&
191 (arg[name_len] == sep)) {
192 if (valuep != NULL) {
193 *valuep = arg + name_len + 1;
194 }
195 return (i);
196 }
197 }
199 return (-1);
200 }
202 static int
203 parse_id(const char *arg, unsigned int *idp)
204 {
205 long x;
206 char *p;
208 errno = 0;
209 x = strtol(arg, &p, 10);
210 if ((errno != 0) || (*arg == '\0') || (*p != '\0') || (x > UINT_MAX) ||
211 (x <= 0)) {
212 return (-1);
213 }
214 *idp = (unsigned int)x;
216 return (0);
217 }
219 static enum cmd_return
220 cmd_status(struct pwm_ctx *ctx, int argc, char *argv[])
221 {
222 if (argc != 1) {
223 return (CMD_USAGE);
224 }
226 if (ctx->errmsg != NULL) {
227 if (io_printf("%s\n", ctx->errmsg) == IO_SIGNAL) {
228 return (CMD_SIGNAL);
229 }
230 }
231 if (ctx->is_readonly) {
232 if (io_printf("Read-only mode\n") == IO_SIGNAL) {
233 return (CMD_SIGNAL);
234 }
235 } else {
236 if (io_printf("There are%sunsaved changes\n",
237 ctx->unsaved_changes ? " " : " no ") == IO_SIGNAL) {
238 return (CMD_SIGNAL);
239 }
240 }
242 return (CMD_STATUS);
243 }
245 static enum cmd_return
246 cmd_info(struct pwm_ctx *ctx, int argc, char *argv[])
247 {
248 enum cmd_return retval;
249 struct metadata *metadata;
250 struct pager *pager;
251 struct tm *tm;
252 char timebuf[TIME_SIZE];
254 if (argc != 1) {
255 return (CMD_USAGE);
256 }
258 metadata = pwfile_get_metadata(ctx);
260 pager = pager_create(STDOUT_FILENO);
261 pager_printf(pager, "Format: 0x%04x\n", metadata->version);
262 if (metadata->user != NULL) {
263 pager_printf(pager, "User: %s\n", metadata->user);
264 }
265 if (metadata->user != NULL) {
266 pager_printf(pager, "Host: %s\n", metadata->host);
267 }
268 if (metadata->user != NULL) {
269 pager_printf(pager, "Application: %s\n", metadata->application);
270 }
271 tm = gmtime(&metadata->timestamp);
272 strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
273 pager_printf(pager, "Last Saved: %s\n", timebuf);
274 retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
275 pager_destroy(pager);
277 pwfile_destroy_metadata(metadata);
279 return (retval);
280 }
282 static enum cmd_return
283 cmd_list(struct pwm_ctx *ctx, int argc, char *argv[])
284 {
285 int retval = CMD_ERR;
286 int i;
287 regex_t *group_re = NULL;
288 regex_t *title_re = NULL;
289 regex_t *username_re = NULL;
290 regex_t *notes_re = NULL;
291 regex_t *url_re = NULL;
292 enum field_type type;
293 char *value;
294 regex_t **repp;
295 int errcode;
296 char *errbuf;
297 size_t errbuf_size;
298 struct pager *pager = NULL;
299 union list_item **list = NULL;
300 size_t j;
301 struct record *record;
303 for (i = 1; i < argc; i++) {
304 type = parse_arg(argv[i], field_namev, '~', &value);
305 if (type == FIELD_UNKNOWN) {
306 pwm_err(ctx, "bad field name \"%s\"", argv[i]);
307 goto out;
308 }
309 if (value[0] == '\0') {
310 /* skip empty expressions */
311 continue;
312 }
313 switch (type) {
314 case FIELD_GROUP:
315 repp = &group_re;
316 break;
317 case FIELD_TITLE:
318 repp = &title_re;
319 break;
320 case FIELD_USERNAME:
321 repp = &username_re;
322 break;
323 case FIELD_NOTES:
324 repp = &notes_re;
325 break;
326 case FIELD_URL:
327 repp = &url_re;
328 break;
329 default:
330 pwm_err(ctx, "bad field name \"%s\"", argv[i]);
331 goto out;
332 }
334 if (*repp == NULL) {
335 *repp = xmalloc(sizeof (regex_t));
336 } else {
337 regfree(*repp);
338 }
339 errcode = regcomp(*repp, value, REG_EXTENDED | REG_NOSUB);
340 if (errcode != 0) {
341 errbuf_size = regerror(errcode, *repp, "", 0);
342 errbuf = xmalloc(errbuf_size);
343 regerror(errcode, *repp, errbuf, errbuf_size);
344 pwm_err(ctx, "bad regular expression \"%s\"", errbuf);
345 free(errbuf);
347 free(*repp);
348 *repp = NULL;
350 goto out;
351 }
352 }
354 pager = pager_create(STDOUT_FILENO);
355 list = pwfile_create_list(ctx);
356 for (j = 0; list[j] != NULL; j++) {
357 if (list[j]->any.type == ITEM_TYPE_GROUP) {
358 pager_printf(pager, "[%s]\n", list[j]->group.group);
359 } else {
360 record = pwfile_get_record(ctx, list[j]->record.id);
361 if (((group_re == NULL) || (regexec(group_re,
362 record->group, 0, NULL, 0) == 0)) &&
363 ((title_re == NULL) || (regexec(title_re,
364 record->title, 0, NULL, 0) == 0)) &&
365 ((username_re == NULL) || (regexec(username_re,
366 record->username, 0, NULL, 0) == 0)) &&
367 ((notes_re == NULL) || (regexec(notes_re,
368 record->notes, 0, NULL, 0) == 0)) &&
369 ((url_re == NULL) || (regexec(url_re,
370 record->url, 0, NULL, 0) == 0))) {
371 pager_printf(pager, "%4u %s\n",
372 list[j]->record.id,
373 (list[j]->record.title != NULL) ?
374 list[j]->record.title : "");
375 }
376 pwfile_destroy_record(record);
377 }
378 }
379 retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
381 out:
382 pager_destroy(pager);
384 if (group_re != NULL) {
385 regfree(group_re);
386 free(group_re);
387 }
388 if (title_re != NULL) {
389 regfree(title_re);
390 free(title_re);
391 }
392 if (username_re != NULL) {
393 regfree(username_re);
394 free(username_re);
395 }
396 if (notes_re != NULL) {
397 regfree(notes_re);
398 free(notes_re);
399 }
400 if (url_re != NULL) {
401 regfree(url_re);
402 free(url_re);
403 }
405 pwfile_destroy_list(list);
408 return (retval);
409 }
411 static int
412 read_record_fields(struct pwm_ctx *ctx, struct record *record)
413 {
414 char group_buf[PWM_LINE_MAX] = { '\0' };
415 char title_buf[PWM_LINE_MAX] = { '\0' };
416 char username_buf[PWM_LINE_MAX] = { '\0' };
417 char password_buf[PWM_LINE_MAX] = { '\0' };
418 char notes_buf[PWM_LINE_MAX] = { '\0' };
419 char url_buf[PWM_LINE_MAX] = { '\0' };
421 if (io_get_line(NULL, "Group: ", 0, record->group, -1,
422 sizeof (group_buf), group_buf) == IO_SIGNAL) {
423 return (CMD_SIGNAL);
424 }
425 io_trim_nl(group_buf);
427 if (io_get_line(NULL, "Title: ", 0, record->title, -1,
428 sizeof (title_buf), title_buf) == IO_SIGNAL) {
429 return (CMD_SIGNAL);
430 }
431 io_trim_nl(title_buf);
433 if (io_get_line(NULL, "Username: ", 0, record->username, -1,
434 sizeof (username_buf), username_buf) == IO_SIGNAL) {
435 return (CMD_SIGNAL);
436 }
437 io_trim_nl(username_buf);
439 for (;;) {
440 switch (io_get_password("Password: ", "Confirm Password: ",
441 sizeof (password_buf), password_buf)) {
442 case IO_OK: /* FALLTHROUGH */
443 case IO_PASSWORD_EMPTY:
444 goto password_done;
445 case IO_SIGNAL:
446 return (CMD_SIGNAL);
447 case IO_PASSWORD_MISMATCH:
448 pwm_err(ctx, "passwords do not match");
449 continue;
450 }
451 }
453 password_done:
454 if (io_get_line(NULL, "Notes: ", 0, record->notes, -1,
455 sizeof (notes_buf), notes_buf) == IO_SIGNAL) {
456 return (CMD_SIGNAL);
457 }
458 io_trim_nl(notes_buf);
460 if (io_get_line(NULL, "URL: ", 0, record->url, -1, sizeof (url_buf),
461 url_buf) == IO_SIGNAL) {
462 return (CMD_SIGNAL);
463 }
464 io_trim_nl(url_buf);
466 free(record->group);
467 record->group = (group_buf[0] != '\0') ? xstrdup(group_buf) : NULL;
468 free(record->title);
469 record->title = (title_buf[0] != '\0') ? xstrdup(title_buf) : NULL;
470 free(record->username);
471 record->username = (username_buf[0] != '\0') ? xstrdup(username_buf) :
472 NULL;
473 /*
474 * the current password cannot be edited, keep the current password if
475 * the user pressed return or ^D instead of deleting it like other
476 * fields
477 */
478 if (password_buf[0] != '\0') {
479 free(record->password);
480 record->password = xstrdup(password_buf);
481 }
482 free(record->notes);
483 record->notes = (notes_buf[0] != '\0') ? xstrdup(notes_buf) : NULL;
484 free(record->url);
485 record->url = (url_buf[0] != '\0') ? xstrdup(url_buf) : NULL;
487 return (CMD_OK);
488 }
490 static enum cmd_return
491 cmd_create(struct pwm_ctx *ctx, int argc, char *argv[])
492 {
493 enum cmd_return retval = CMD_ERR;
494 int i;
495 struct record *record = NULL;
496 enum field_type type;
497 char *value;
499 if (!ctx->is_interactive && (argc < 2)) {
500 retval = CMD_USAGE;
501 goto out;
502 }
504 if (ctx->is_readonly) {
505 pwm_err(ctx, "cannot create new entries in read-only mode");
506 goto out;
507 }
509 record = pwfile_create_record();
511 for (i = 1; i < argc; i++) {
512 type = parse_arg(argv[i], field_namev, '=', &value);
513 if (type == FIELD_UNKNOWN) {
514 pwm_err(ctx, "bad field assignment \"%s\"", argv[i]);
515 }
516 if (value[0] == '\0') {
517 /* skip empty assignments */
518 continue;
519 }
520 switch (type) {
521 case FIELD_GROUP:
522 free(record->group);
523 record->group = xstrdup(value);
524 break;
525 case FIELD_TITLE:
526 free(record->title);
527 record->title = xstrdup(value);
528 break;
529 case FIELD_USERNAME:
530 free(record->username);
531 record->username = xstrdup(value);
532 break;
533 case FIELD_PASSWORD:
534 free(record->password);
535 record->password = xstrdup(value);
536 break;
537 case FIELD_NOTES:
538 free(record->notes);
539 record->notes = xstrdup(value);
540 break;
541 case FIELD_URL:
542 free(record->url);
543 record->url = xstrdup(value);
544 break;
545 default:
546 pwm_err(ctx, "bad field name \"%s\"", argv[i]);
547 goto out;
548 }
549 }
551 if (ctx->is_interactive && (argc < 2)) {
552 if (read_record_fields(ctx, record) != 0) {
553 goto out;
554 }
555 }
557 pwfile_create_pws_record(ctx, record);
558 retval = CMD_OK;
560 out:
561 pwfile_destroy_record(record);
563 return (retval);
564 }
566 static enum cmd_return
567 cmd_modify(struct pwm_ctx *ctx, int argc, char *argv[])
568 {
569 int retval = CMD_ERR;
570 unsigned int id;
571 int i;
572 struct record *record = NULL;
573 enum field_type type;
574 char *value;
576 if (!ctx->is_interactive && (argc < 2)) {
577 retval = CMD_USAGE;
578 goto out;
579 }
581 if (parse_id(argv[1], &id) != 0) {
582 pwm_err(ctx, "invalid id %s", argv[1]);
583 goto out;
584 }
586 if (ctx->is_readonly) {
587 pwm_err(ctx, "cannot modify entries in read-only mode");
588 goto out;
589 }
591 record = pwfile_get_record(ctx, id);
593 for (i = 2; i < argc; i++) {
594 type = parse_arg(argv[i], field_namev, '=', &value);
595 if (type == FIELD_UNKNOWN) {
596 pwm_err(ctx, "bad field assignment \"%s\"", argv[i]);
597 goto out;
598 }
599 if (value[0] == '\0') {
600 /* skip empty assignments */
601 continue;
602 }
603 switch (type) {
604 case FIELD_GROUP:
605 free(record->group);
606 record->group = xstrdup(value);
607 break;
608 case FIELD_TITLE:
609 free(record->title);
610 record->title = xstrdup(value);
611 break;
612 case FIELD_USERNAME:
613 free(record->username);
614 record->username = xstrdup(value);
615 break;
616 case FIELD_PASSWORD:
617 free(record->password);
618 record->password = xstrdup(value);
619 break;
620 case FIELD_NOTES:
621 free(record->notes);
622 record->notes = xstrdup(value);
623 break;
624 case FIELD_URL:
625 free(record->url);
626 record->url = xstrdup(value);
627 break;
628 default:
629 pwm_err(ctx, "bad field name \"%s\"", argv[i]);
630 goto out;
631 }
632 }
634 if (ctx->is_interactive && (argc < 3)) {
635 if (read_record_fields(ctx, record) != 0) {
636 goto out;
637 }
638 }
640 pwfile_modify_pws_record(ctx, id, record);
641 retval = CMD_OK;
643 out:
644 pwfile_destroy_record(record);
646 return (retval);
647 }
649 static enum cmd_return
650 cmd_generatepassword(struct pwm_ctx *ctx, int argc, char *argv[])
651 {
652 enum cmd_return retval = CMD_ERR;
653 unsigned int id = 0;
654 int i = 1;
655 char *value = NULL;
656 long x;
657 char *p;
658 size_t password_len = 16;
659 size_t chars_min;
660 const char *chars;
661 struct pw_char_group *char_groupv = NULL;
662 size_t char_groupv_len = 0;
663 int charclass;
664 size_t j;
665 char password[PWS3_MAX_PASSWORD_LEN + 1] = { 0 };
667 /* check if first argument is an id */
668 if ((argc > 1) && (parse_id(argv[1], &id) == 0)) {
669 i++;
670 if (ctx->is_readonly) {
671 pwm_err(ctx, "cannot modify entries in read-only mode");
672 goto out;
673 }
674 }
676 for (; i < argc; i++) {
677 switch (parse_arg(argv[i], cmd_generatepassword_argv, '=',
678 &value)) {
679 case CMD_GP_ARG_LEN:
680 errno = 0;
681 x = strtol(value, &p, 10);
682 if ((errno != 0) || (*value == '\0') || (*p != '\0') ||
683 (x > PWS3_MAX_PASSWORD_LEN) || (x <= 0)) {
684 pwm_err(ctx, "invalid password length \"%s\"",
685 argv[i]);
686 goto out;
687 }
688 password_len = x;
689 break;
690 case CMD_GP_ARG_CHARS:
691 errno = 0;
692 x = strtol(value, &p, 10);
693 if ((errno != 0) || (*value == '\0') || (*p != ':') ||
694 (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) {
695 pwm_err(ctx, "invalid minimum number of "
696 "characters \"%s\"", argv[i]);
697 goto out;
698 }
699 chars_min = x;
701 chars = ++p;
702 while (*p != '\0') {
703 if (!isascii(*p) || !isprint(*p)) {
704 pwm_err(ctx, "invalid character in "
705 "character group \"%s\"", argv[i]);
706 goto out;
707 }
708 p++;
709 }
711 char_groupv = xrealloc(char_groupv,
712 sizeof (struct pw_char_group) * (char_groupv_len +
713 1));
714 char_groupv[char_groupv_len].chars = chars;
715 char_groupv[char_groupv_len].chars_min = chars_min;
716 char_groupv_len++;
717 break;
718 case CMD_GP_ARG_CHARCLASS:
719 errno = 0;
720 x = strtol(value, &p, 10);
721 if ((errno != 0) || (*value == '\0') || (*p != ':') ||
722 (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) {
723 pwm_err(ctx, "invalid minimum number of "
724 "characters \"%s\"", argv[i]);
725 goto out;
726 }
727 chars_min = x;
729 charclass = parse_arg(++p, charclass_namev, '\0', NULL);
730 if (charclass < 0) {
731 pwm_err(ctx, "unknown character class \"%s\"",
732 argv[i]);
733 goto out;
734 }
735 chars = charclass_values[charclass];
736 char_groupv = xrealloc(char_groupv,
737 sizeof (struct pw_char_group) * (char_groupv_len +
738 1));
739 char_groupv[char_groupv_len].chars = chars;
740 char_groupv[char_groupv_len].chars_min = chars_min;
741 char_groupv_len++;
742 break;
743 default:
744 pwm_err(ctx, "invalid argument \"%s\"", argv[i]);
745 retval = CMD_USAGE;
746 goto out;
747 }
748 }
750 for (j = 0; j < char_groupv_len; j++) {
751 if (char_groupv[j].chars_min > password_len) {
752 pwm_err(ctx, "invalid minimum number of characters "
753 "\"%zu:%s\"", char_groupv[j].chars_min,
754 char_groupv[j].chars);
755 goto out;
756 }
757 }
759 if (char_groupv_len == 0) {
760 /* use defaults */
761 char_groupv = xmalloc(sizeof (struct pw_char_group));
762 char_groupv[0].chars = charclass_values[CHARCLASS_GRAPH];
763 char_groupv[0].chars_min = 0;
764 char_groupv_len++;
765 }
767 if (pw_genrandom(char_groupv, char_groupv_len, password,
768 password_len) != 0) {
769 pwm_err(ctx, "failed to generate password that meets the given "
770 "constraints");
771 goto out;
772 }
774 if (id != 0) {
775 if (pwfile_modify_pws_record(ctx, id,
776 &(struct record){ .password = password }) != 0) {
777 pwm_err(ctx, "record %u does not exist", id);
778 goto out;
779 }
780 retval = CMD_OK;
781 } else {
782 retval = io_printf("%s\n", password);
783 }
785 out:
786 free(char_groupv);
788 return (retval);
789 }
791 static enum cmd_return
792 cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[])
793 {
794 unsigned int id;
796 if (argc != 2) {
797 return (CMD_USAGE);
798 }
800 if (parse_id(argv[1], &id) != 0) {
801 pwm_err(ctx, "invalid id %s", argv[1]);
802 return (CMD_ERR);
803 }
805 if (ctx->is_readonly) {
806 pwm_err(ctx, "cannot remove entries in read-only mode");
807 return (CMD_ERR);
808 }
810 if (pwfile_remove_pws_record(ctx, id) != 0) {
811 pwm_err(ctx, "failed to remove record %u", id);
812 return (CMD_ERR);
813 }
815 return (CMD_OK);
816 }
818 static int
819 print_record(struct record *record, int fields[], int show_labels, int fd)
820 {
821 struct pager *pager;
822 struct tm *tm;
823 char timebuf[TIME_SIZE];
824 int retval;
826 pager = pager_create(fd);
827 if (fields[FIELD_TITLE]) {
828 pager_printf(pager, "%s%s\n", show_labels ?
829 field_labels[FIELD_TITLE] : "", (record->title != NULL) ?
830 record->title : "");
831 }
832 if (fields[FIELD_GROUP]) {
833 pager_printf(pager, "%s%s\n", show_labels ?
834 field_labels[FIELD_GROUP] : "", (record->group != NULL) ?
835 record->group : "");
836 }
837 if (fields[FIELD_USERNAME]) {
838 pager_printf(pager, "%s%s\n", show_labels ?
839 field_labels[FIELD_USERNAME] : "",
840 (record->username != NULL) ? record->username : "");
841 }
842 if (fields[FIELD_PASSWORD]) {
843 pager_printf(pager, "%s%s\n", show_labels ?
844 field_labels[FIELD_PASSWORD] : "",
845 (record->password != NULL) ? record->password : "");
846 }
847 if (fields[FIELD_NOTES]) {
848 pager_printf(pager, "%s%s\n", show_labels ?
849 field_labels[FIELD_NOTES] : "", (record->notes != NULL) ?
850 record->notes : "");
851 }
852 if (fields[FIELD_URL]) {
853 pager_printf(pager, "%s%s\n", show_labels ?
854 field_labels[FIELD_URL] : "", (record->url != NULL) ?
855 record->url : "");
856 }
857 if (fields[FIELD_CTIME]) {
858 tm = gmtime(&record->ctime);
859 strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
860 pager_printf(pager, "%s%s\n", show_labels ?
861 field_labels[FIELD_CTIME] : "", timebuf);
862 }
863 if (fields[FIELD_MTIME]) {
864 tm = gmtime(&record->mtime);
865 strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
866 pager_printf(pager, "%s%s\n", show_labels ?
867 field_labels[FIELD_MTIME] : "", timebuf);
868 }
869 retval = pager_show(pager);
870 pager_destroy(pager);
872 return (retval);
873 }
875 static enum cmd_return
876 cmd_show(struct pwm_ctx *ctx, int argc, char *argv[])
877 {
878 enum cmd_return retval;
879 unsigned int id;
880 struct record *record;
881 int i;
882 enum field_type type;
883 int fields[COUNTOF(field_namev) - 1] = {
884 [FIELD_GROUP] = 1,
885 [FIELD_TITLE] = 1,
886 [FIELD_USERNAME] = 1,
887 [FIELD_PASSWORD] = 0,
888 [FIELD_NOTES] = 1,
889 [FIELD_URL] = 1,
890 [FIELD_MTIME] = 1,
891 [FIELD_CTIME] = 1
892 };
894 if (argc < 2) {
895 return (CMD_USAGE);
896 }
898 if (parse_id(argv[1], &id) != 0) {
899 pwm_err(ctx, "invalid id %s", argv[1]);
900 return (CMD_ERR);
901 }
903 if (argc > 2) {
904 /* show only explicitly given field names */
905 memset(fields, 0, sizeof (fields));
906 }
908 for (i = 2; i < argc; i++) {
909 type = parse_arg(argv[i], field_namev, '\0', NULL);
910 if (type < 0) {
911 pwm_err(ctx, "bad field name \"%s\"", argv[i]);
912 return (CMD_ERR);
913 }
914 fields[type] = 1;
915 }
917 record = pwfile_get_record(ctx, id);
918 if (record == NULL) {
919 pwm_err(ctx, "record %u does not exist", id);
920 return (CMD_ERR);
921 }
922 retval = (print_record(record, fields, 1, STDOUT_FILENO) != IO_SIGNAL) ?
923 CMD_OK : CMD_SIGNAL;
924 pwfile_destroy_record(record);
926 return (retval);
927 }
929 static enum cmd_return
930 cmd_pipe(struct pwm_ctx *ctx, int argc, char *argv[])
931 {
932 enum cmd_return retval = CMD_ERR;
933 unsigned int id;
934 struct record *record = NULL;
935 enum field_type type;
936 int fields[COUNTOF(field_namev) - 1] = { 0 };
937 struct proc proc = { 0 };
939 if (argc != 4) {
940 return (CMD_USAGE);
941 }
943 if (parse_id(argv[1], &id) != 0) {
944 pwm_err(ctx, "invalid id %s", argv[1]);
945 return (CMD_ERR);
946 }
948 type = parse_arg(argv[2], field_namev, '\0', NULL);
949 if (type < 0) {
950 pwm_err(ctx, "bad field name \"%s\"", argv[2]);
951 return (CMD_ERR);
952 }
953 fields[type] = 1;
955 if (proc_open(&proc, argv[3], "w") != IO_OK) {
956 goto out;
957 }
959 record = pwfile_get_record(ctx, id);
960 if (record == NULL) {
961 pwm_err(ctx, "record %u does not exist", id);
962 goto out;
963 }
965 retval = (print_record(record, fields, 0, proc.fd) != IO_SIGNAL) ?
966 CMD_OK : CMD_SIGNAL;
968 out:
969 pwfile_destroy_record(record);
970 if (proc.pid != 0) {
971 if (proc_close(&proc) == IO_SIGNAL) {
972 retval = CMD_SIGNAL;
973 }
974 }
976 return (retval);
977 }
979 static enum cmd_return
980 cmd_creategroup(struct pwm_ctx *ctx, int argc, char *argv[])
981 {
982 char group_buf[PWM_LINE_MAX] = { '\0' };
984 if (!ctx->is_interactive && (argc != 2)) {
985 return (CMD_USAGE);
986 }
988 if (ctx->is_readonly) {
989 pwm_err(ctx, "cannot create groups in read-only mode");
990 return (CMD_ERR);
991 }
993 if (ctx->is_interactive && (argc != 2)) {
994 if (io_get_line(NULL, "Group: ", 0, NULL, 0,
995 sizeof (group_buf), group_buf) == IO_SIGNAL) {
996 return (CMD_SIGNAL);
997 }
998 io_trim_nl(group_buf);
999 } else {
1000 strcpy(group_buf, argv[1]);
1003 if (group_buf[0] != '\0') {
1004 if (pwfile_create_group(ctx, group_buf) != 0) {
1005 pwm_err(ctx, "group \"%s\" already exists", group_buf);
1006 return (CMD_ERR);
1010 return (CMD_OK);
1013 static enum cmd_return
1014 cmd_removegroup(struct pwm_ctx *ctx, int argc, char *argv[])
1016 if (argc != 2) {
1017 return (CMD_USAGE);
1020 if (ctx->is_readonly) {
1021 pwm_err(ctx, "cannot remove groups in read-only mode");
1022 return (CMD_ERR);
1025 if (pwfile_remove_group(ctx, argv[1]) != 0) {
1026 pwm_err(ctx, "empty group \"%s\" does not exist", argv[1]);
1027 return (CMD_ERR);
1030 return (CMD_OK);
1033 static enum cmd_return
1034 cmd_changepassword(struct pwm_ctx *ctx, int argc, char *argv[])
1036 size_t len;
1038 if (argc > 2) {
1039 return (CMD_USAGE);
1042 if (ctx->is_readonly) {
1043 pwm_err(ctx, "cannot modify entries in read-only mode");
1044 return (CMD_ERR);
1047 if (argc == 2) {
1048 len = strlen(argv[1]);
1049 if (len == 0) {
1050 pwm_err(ctx, "password must not be empty");
1051 return (CMD_ERR);
1052 } else if (len + 1 > sizeof (ctx->password)) {
1053 pwm_err(ctx, "password too long");
1054 return (CMD_ERR);
1056 memcpy(ctx->password, argv[1], len + 1);
1057 } else {
1058 if (pwm_read_password(ctx, 1) != 0) {
1059 return (CMD_ERR);
1063 return (CMD_OK);
1066 static enum cmd_return
1067 cmd_help(struct pwm_ctx *ctx, int argc, char *argv[])
1069 enum cmd_return retval = CMD_OK;
1070 struct pager *pager;
1071 struct cmd *cmd;
1073 if (argc > 2) {
1074 return (CMD_USAGE);
1077 pager = pager_create(STDOUT_FILENO);
1078 if (argc == 2) {
1079 for (cmd = cmds; cmd->cmd_func != NULL; cmd++) {
1080 if ((strcmp(argv[1], cmd->abbrev_cmd) == 0) ||
1081 (strcmp(argv[1], cmd->full_cmd) == 0)) {
1082 pager_printf(pager, "%s\n", cmd->usage);
1083 break;
1086 } else {
1087 printf("Commands:\n");
1088 for (cmd = cmds; cmd->cmd_func != NULL; cmd++) {
1089 pager_printf(pager, "%-2s %-16s %s\n", cmd->abbrev_cmd,
1090 cmd->full_cmd, cmd->description);
1093 retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
1094 pager_destroy(pager);
1096 return (retval);
1099 static enum cmd_return
1100 cmd_write(struct pwm_ctx *ctx, int argc, char *argv[])
1102 if (argc != 1) {
1103 return (CMD_USAGE);
1106 if (ctx->is_readonly) {
1107 pwm_err(ctx, "cannot write changes in read-only mode");
1108 return (CMD_ERR);
1111 return ((pwfile_write_file(ctx) == 0) ? CMD_OK : CMD_ERR);
1114 static enum cmd_return
1115 cmd_quit(struct pwm_ctx *ctx, int argc, char *argv[])
1117 if (argc != 1) {
1118 return (CMD_USAGE);
1121 if ((argv[0][0] == 'q') && ctx->unsaved_changes &&
1122 (ctx->prev_cmd != NULL) && (strcmp(ctx->prev_cmd, "quit") != 0)) {
1123 printf("Warning: There are unsaved changes\n");
1124 return (CMD_OK);
1127 return (CMD_QUIT);
1130 struct cmd *
1131 cmd_match(const char *cmd_name)
1133 size_t i;
1135 for (i = 0; cmds[i].cmd_func != NULL; i++) {
1136 if ((strcmp(cmds[i].abbrev_cmd, cmd_name) == 0) ||
1137 (strcmp(cmds[i].full_cmd, cmd_name) == 0)) {
1138 return (&cmds[i]);
1142 return (NULL);