projects/pwm

view cmd.c @ 41:0af8d2d8cd1a

Improve error message of removegroup command

The removegroup comman may also fail if the group is not empty.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Tue Aug 20 20:19:53 2019 +0200 (2019-08-20)
parents e3ad9859c51d
children
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 "macro.h"
41 #include "pager.h"
42 #include "proc.h"
43 #include "pw.h"
44 #include "pwfile.h"
45 #include "tok.h"
46 #include "util.h"
48 #define TIME_FORMAT "%Y-%m-%dT%TZ"
49 #define TIME_SIZE (4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1)
51 #define CHARS_DIGIT "0123456789"
52 #define CHARS_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
53 #define CHARS_LOWER "abcdefghijklmnopqrstuvwxyz"
54 #define CHARS_PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
56 enum field_type {
57 FIELD_UNKNOWN = -1,
58 FIELD_GROUP,
59 FIELD_TITLE,
60 FIELD_USERNAME,
61 FIELD_PASSWORD,
62 FIELD_NOTES,
63 FIELD_URL,
64 FIELD_MTIME,
65 FIELD_CTIME
66 };
68 enum cmd_generatepassword_arg_type {
69 CMD_GP_ARG_UNKNOWN = -1,
70 CMD_GP_ARG_LEN,
71 CMD_GP_ARG_CHARS,
72 CMD_GP_ARG_CHARCLASS
73 };
75 enum charclass_type {
76 CHARCLASS_UNKNOWN = -1,
77 CHARCLASS_DIGIT,
78 CHARCLASS_UPPER,
79 CHARCLASS_LOWER,
80 CHARCLASS_PUNCT,
81 CHARCLASS_ALPHA,
82 CHARCLASS_ALNUM,
83 CHARCLASS_XDIGIT,
84 CHARCLASS_GRAPH
85 };
87 enum option_type {
88 OPTION_UNKNOWN = -1,
89 OPTION_FILENAME,
90 OPTION_PIPECOMMAND
91 };
93 static enum cmd_return cmd_set(struct pwm_ctx *, int, char *[]);
94 static enum cmd_return cmd_define(struct pwm_ctx *, int, char *[]);
95 static enum cmd_return cmd_status(struct pwm_ctx *, int, char *[]);
96 static enum cmd_return cmd_info(struct pwm_ctx *, int, char *[]);
97 static enum cmd_return cmd_list(struct pwm_ctx *, int, char *[]);
98 static enum cmd_return cmd_create(struct pwm_ctx *, int, char *[]);
99 static enum cmd_return cmd_modify(struct pwm_ctx *, int, char *[]);
100 static enum cmd_return cmd_generatepassword(struct pwm_ctx *, int, char *[]);
101 static enum cmd_return cmd_remove(struct pwm_ctx *, int, char *[]);
102 static enum cmd_return cmd_show(struct pwm_ctx *, int, char *[]);
103 static enum cmd_return cmd_pipe(struct pwm_ctx *, int, char *[]);
104 static enum cmd_return cmd_creategroup(struct pwm_ctx *, int, char *[]);
105 static enum cmd_return cmd_removegroup(struct pwm_ctx *, int, char *[]);
106 static enum cmd_return cmd_changepassword(struct pwm_ctx *, int, char *[]);
107 static enum cmd_return cmd_help(struct pwm_ctx *, int, char *[]);
108 static enum cmd_return cmd_write(struct pwm_ctx *, int, char *[]);
109 static enum cmd_return cmd_quit(struct pwm_ctx *, int, char *[]);
111 static const char *field_namev[] = {
112 "group",
113 "title",
114 "username",
115 "password",
116 "notes",
117 "url",
118 "ctime",
119 "mtime",
120 NULL
121 };
123 static const char *field_labels[] = {
124 "Group: ",
125 "Title: ",
126 "Username: ",
127 "Password: ",
128 "Notes: ",
129 "URL: ",
130 "Created: ",
131 "Modified: "
132 };
134 static const char *cmd_generatepassword_argv[] = {
135 "len",
136 "char",
137 "charclass",
138 NULL
139 };
141 static const char *charclass_namev[] = {
142 "digit",
143 "upper",
144 "lower",
145 "punct",
146 "alpha",
147 "alnum",
148 "xdigit",
149 "graph",
150 NULL
151 };
153 static const char *charclass_values[] = {
154 CHARS_DIGIT,
155 CHARS_UPPER,
156 CHARS_LOWER,
157 CHARS_PUNCT,
158 CHARS_UPPER CHARS_LOWER,
159 CHARS_DIGIT CHARS_UPPER CHARS_LOWER,
160 CHARS_DIGIT "abcdef",
161 CHARS_DIGIT CHARS_UPPER CHARS_LOWER CHARS_PUNCT
162 };
164 static const char *optionv[] = {
165 "filename",
166 "pipecommand",
167 NULL
168 };
170 static struct cmd cmds[] = {
171 { "S", "set", "set [option=value]", "Set an option or show option values",
172 cmd_set },
173 { "D", "define", "define name=value", "Define a macro", cmd_define },
174 { "t", "status", "status", "Redisplay an error message of the previous "
175 "command and unsaved changes", cmd_status },
176 { "i", "info", "info", "Show metadata information about the current file",
177 cmd_info },
178 { "ls", "list", "list [field~regex ...]", "List entries", cmd_list },
179 { "c", "create", "create field=value ...", "Create entry", cmd_create },
180 { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify },
181 { "gp", "generatepassword", "generatepassword [id] [len=n] [chars=n:chars] "
182 "[charclass=n:class] ...", "Randomly generate a password",
183 cmd_generatepassword },
184 { "rm", "remove", "remove id", "Delete entry", cmd_remove },
185 { "s", "show", "show id field", "Show entry", cmd_show },
186 { "p", "pipe", "pipe id field command", "Pipe entry to external command",
187 cmd_pipe },
188 { "cg", "creategroup", "creategroup name", "Create empty group",
189 cmd_creategroup },
190 { "rg", "removegroup", "removegroup name", "Delete empty group",
191 cmd_removegroup },
192 { "ch", "changepassword [password]", "changepassword", "Change password",
193 cmd_changepassword },
194 { "h", "help", "help [command]", "Show help text", cmd_help },
195 { "w", "write", "write", "Write the database", cmd_write },
196 { "q", "quit", "quit", "Quit", cmd_quit },
197 { "Q", "Quit", "Quit", "Quit without checking", cmd_quit },
198 { 0 }
199 };
201 static int
202 parse_arg(char *arg, const char *namev[], int sep, char **valuep)
203 {
204 char *p;
205 size_t i;
207 if ((sep != '\0') && ((p = strchr(arg, sep)) != NULL)) {
208 *p++ = '\0';
209 }
210 if (valuep != NULL) {
211 *valuep = p;
212 }
214 for (i = 0; namev[i] != NULL; i++) {
215 if (strcmp(namev[i], arg) == 0) {
216 return (i);
217 }
218 }
220 return (-1);
221 }
223 static int
224 parse_id(const char *arg, unsigned int *idp)
225 {
226 long x;
227 char *p;
229 errno = 0;
230 x = strtol(arg, &p, 10);
231 if ((errno != 0) || (*arg == '\0') || (*p != '\0') || (x > UINT_MAX) ||
232 (x <= 0)) {
233 return (-1);
234 }
235 *idp = (unsigned int)x;
237 return (0);
238 }
240 static inline int
241 filter_matches(regex_t *filter_re, const char *s)
242 {
243 if (filter_re == NULL) {
244 return (1);
245 }
246 if (s == NULL) {
247 return (0);
248 }
249 return (regexec(filter_re, s, 0, NULL, 0) == 0);
250 }
252 static enum cmd_return
253 cmd_set(struct pwm_ctx *ctx, int argc, char *argv[])
254 {
255 enum option_type type;
256 char *value;
258 if (argc == 1) {
259 /* show options */
260 if ((io_printf("%s: %s\n", optionv[OPTION_FILENAME],
261 ctx->filename) == IO_SIGNAL) ||
262 (io_printf("%s: %s\n", optionv[OPTION_PIPECOMMAND],
263 (ctx->pipecmd != NULL) ? ctx->pipecmd : "") == IO_SIGNAL)) {
264 return (CMD_SIGNAL);
265 }
267 return (CMD_OK);
268 } else if (argc != 2) {
269 return (CMD_USAGE);
270 }
272 type = parse_arg(argv[1], optionv, '=', &value);
273 if ((type >= 0) && (value == NULL)) {
274 pwm_err(ctx, "missing value for \"%s\"", argv[1]);
275 return (CMD_ERR);
276 }
277 switch (type) {
278 case OPTION_FILENAME:
279 free(ctx->filename);
280 ctx->filename = (value[0] != '\0') ? xstrdup(value) :
281 xasprintf(&ctx->filename, "%s/pwm.psafe3", ctx->dirname);
282 break;
283 case OPTION_PIPECOMMAND:
284 free(ctx->pipecmd);
285 ctx->pipecmd = (value[0] != '\0') ? xstrdup(value) : NULL;
286 break;
287 default:
288 pwm_err(ctx, "unknown option \"%s\"", argv[1]);
289 return (CMD_ERR);
290 }
292 return (CMD_OK);
293 }
295 static enum cmd_return
296 cmd_define(struct pwm_ctx *ctx, int argc, char *argv[])
297 {
298 int retval = CMD_ERR;
299 const char *value;
300 char *name = NULL;
301 size_t tokenc = 0;
302 union tok **tokenv = NULL;
303 struct macro_entry *macro_entry;
305 if (argc != 2) {
306 return (CMD_USAGE);
307 }
309 /* split into name and value */
310 value = strchr(argv[1], '=');
311 if (value == NULL) {
312 pwm_err(ctx, "bad macro definition \"%s\"", argv[1]);
313 goto out;
314 }
315 xasprintf(&name, "%.*s", value - argv[1], argv[1]);
316 value++;
318 /* tokenize macro value */
319 switch (tok_tokenize(value, &tokenc, &tokenv)) {
320 case TOK_ERR_UNTERMINATED_QUOTE:
321 pwm_err(ctx, "unterminated quote in macro");
322 goto out;
323 case TOK_ERR_TRAILING_BACKSLASH:
324 pwm_err(ctx, "trailing backslash in macro");
325 goto out;
326 case TOK_ERR_INVALID_MACRO_NAME:
327 pwm_err(ctx, "invalid macro name referenced in macro");
328 goto out;
329 case TOK_ERR_OK:
330 break;
331 }
333 /* parse macro definition */
334 switch (macro_parse(name, tokenc, tokenv, ctx->macro_head,
335 &macro_entry)) {
336 case MACRO_ERR_INVALID_NAME:
337 pwm_err(ctx, "invalid macro name \"%s\"", name);
338 goto out;
339 case MACRO_ERR_UNDEFINED_MACRO:
340 pwm_err(ctx, "macro definition references undefined macro");
341 goto out;
342 case MACRO_ERR_RECURSIVE:
343 pwm_err(ctx, "macro definition must not be recursive");
344 goto out;
345 case MACRO_ERR_OK:
346 break;
347 }
349 macro_add(ctx->macro_head, macro_entry);
350 retval = CMD_OK;
352 out:
353 tok_free(tokenv);
354 free(name);
356 return (retval);
357 }
359 static enum cmd_return
360 cmd_status(struct pwm_ctx *ctx, int argc, char *argv[])
361 {
362 if (argc != 1) {
363 return (CMD_USAGE);
364 }
366 if (ctx->errmsg != NULL) {
367 if (io_printf("%s\n", ctx->errmsg) == IO_SIGNAL) {
368 return (CMD_SIGNAL);
369 }
370 }
371 if (ctx->is_readonly) {
372 if (io_printf("Read-only mode\n") == IO_SIGNAL) {
373 return (CMD_SIGNAL);
374 }
375 } else {
376 if (io_printf("There are%sunsaved changes\n",
377 ctx->unsaved_changes ? " " : " no ") == IO_SIGNAL) {
378 return (CMD_SIGNAL);
379 }
380 }
382 return (CMD_STATUS);
383 }
385 static enum cmd_return
386 cmd_info(struct pwm_ctx *ctx, int argc, char *argv[])
387 {
388 enum cmd_return retval;
389 struct metadata *metadata;
390 struct pager *pager;
391 struct tm *tm;
392 char timebuf[TIME_SIZE];
394 if (argc != 1) {
395 return (CMD_USAGE);
396 }
398 metadata = pwfile_get_metadata(ctx);
400 pager = pager_create(STDOUT_FILENO);
401 pager_printf(pager, "Format: 0x%04x\n", metadata->version);
402 if (metadata->user != NULL) {
403 pager_printf(pager, "User: %s\n", metadata->user);
404 }
405 if (metadata->user != NULL) {
406 pager_printf(pager, "Host: %s\n", metadata->host);
407 }
408 if (metadata->user != NULL) {
409 pager_printf(pager, "Application: %s\n", metadata->application);
410 }
411 tm = gmtime(&metadata->timestamp);
412 strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
413 pager_printf(pager, "Last Saved: %s\n", timebuf);
414 retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
415 pager_destroy(pager);
417 pwfile_destroy_metadata(metadata);
419 return (retval);
420 }
422 static enum cmd_return
423 cmd_list(struct pwm_ctx *ctx, int argc, char *argv[])
424 {
425 int retval = CMD_ERR;
426 int i;
427 regex_t *group_re = NULL;
428 regex_t *title_re = NULL;
429 regex_t *username_re = NULL;
430 regex_t *notes_re = NULL;
431 regex_t *url_re = NULL;
432 enum field_type type;
433 char *value;
434 regex_t **repp;
435 int errcode;
436 char *errbuf;
437 size_t errbuf_size;
438 int is_filtered;
439 struct pager *pager = NULL;
440 union list_item **list = NULL;
441 size_t j;
442 const char *group = NULL;
443 struct record *record;
445 for (i = 1; i < argc; i++) {
446 type = parse_arg(argv[i], field_namev, '~', &value);
447 if ((type >= 0) && ((value == NULL) || (value[0] == '\0'))) {
448 /* skip empty expressions */
449 continue;
450 }
451 switch (type) {
452 case FIELD_GROUP:
453 repp = &group_re;
454 break;
455 case FIELD_TITLE:
456 repp = &title_re;
457 break;
458 case FIELD_USERNAME:
459 repp = &username_re;
460 break;
461 case FIELD_NOTES:
462 repp = &notes_re;
463 break;
464 case FIELD_URL:
465 repp = &url_re;
466 break;
467 default:
468 pwm_err(ctx, "unknown field name \"%s\"", argv[i]);
469 goto out;
470 }
472 if (*repp == NULL) {
473 *repp = xmalloc(sizeof (regex_t));
474 } else {
475 regfree(*repp);
476 }
477 errcode = regcomp(*repp, value, REG_EXTENDED | REG_NOSUB);
478 if (errcode != 0) {
479 errbuf_size = regerror(errcode, *repp, "", 0);
480 errbuf = xmalloc(errbuf_size);
481 regerror(errcode, *repp, errbuf, errbuf_size);
482 pwm_err(ctx, "bad regular expression \"%s\"", errbuf);
483 free(errbuf);
485 free(*repp);
486 *repp = NULL;
488 goto out;
489 }
490 }
492 is_filtered = ((group_re != NULL) || (title_re != NULL) ||
493 (username_re != NULL) || (notes_re != NULL) || (url_re != NULL));
494 pager = pager_create(STDOUT_FILENO);
495 list = pwfile_create_list(ctx);
496 for (j = 0; list[j] != NULL; j++) {
497 if (list[j]->any.type == ITEM_TYPE_GROUP) {
498 group = list[j]->group.group;
499 if (!is_filtered) {
500 pager_printf(pager, "[%s]\n", group);
501 }
502 } else if (is_filtered) {
503 record = pwfile_get_record(ctx, list[j]->record.id);
504 if (filter_matches(group_re, record->group) &&
505 filter_matches(title_re, record->title) &&
506 filter_matches(username_re, record->username) &&
507 filter_matches(notes_re, record->notes) &&
508 filter_matches(url_re, record->url)) {
509 if (group != NULL) {
510 pager_printf(pager, "[%s]\n", group);
511 group = NULL;
512 }
513 pager_printf(pager, "%4u %s\n",
514 list[j]->record.id,
515 (list[j]->record.title != NULL) ?
516 list[j]->record.title : "");
517 }
518 pwfile_destroy_record(record);
519 } else {
520 pager_printf(pager, "%4u %s\n", list[j]->record.id,
521 (list[j]->record.title != NULL) ?
522 list[j]->record.title : "");
523 }
524 }
525 retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
527 out:
528 pager_destroy(pager);
530 if (group_re != NULL) {
531 regfree(group_re);
532 free(group_re);
533 }
534 if (title_re != NULL) {
535 regfree(title_re);
536 free(title_re);
537 }
538 if (username_re != NULL) {
539 regfree(username_re);
540 free(username_re);
541 }
542 if (notes_re != NULL) {
543 regfree(notes_re);
544 free(notes_re);
545 }
546 if (url_re != NULL) {
547 regfree(url_re);
548 free(url_re);
549 }
551 pwfile_destroy_list(list);
554 return (retval);
555 }
557 static int
558 read_record_fields(struct pwm_ctx *ctx, struct record *record)
559 {
560 char group_buf[PWM_LINE_MAX] = { '\0' };
561 char title_buf[PWM_LINE_MAX] = { '\0' };
562 char username_buf[PWM_LINE_MAX] = { '\0' };
563 char password_buf[PWM_LINE_MAX] = { '\0' };
564 char notes_buf[PWM_LINE_MAX] = { '\0' };
565 char url_buf[PWM_LINE_MAX] = { '\0' };
567 if (io_get_line(NULL, "Group: ", 0, record->group, -1,
568 sizeof (group_buf), group_buf) == IO_SIGNAL) {
569 return (CMD_SIGNAL);
570 }
571 io_trim_nl(group_buf);
573 if (io_get_line(NULL, "Title: ", 0, record->title, -1,
574 sizeof (title_buf), title_buf) == IO_SIGNAL) {
575 return (CMD_SIGNAL);
576 }
577 io_trim_nl(title_buf);
579 if (io_get_line(NULL, "Username: ", 0, record->username, -1,
580 sizeof (username_buf), username_buf) == IO_SIGNAL) {
581 return (CMD_SIGNAL);
582 }
583 io_trim_nl(username_buf);
585 for (;;) {
586 switch (io_get_password("Password: ", "Confirm Password: ",
587 sizeof (password_buf), password_buf)) {
588 case IO_OK: /* FALLTHROUGH */
589 case IO_PASSWORD_EMPTY:
590 goto password_done;
591 case IO_SIGNAL:
592 return (CMD_SIGNAL);
593 case IO_PASSWORD_MISMATCH:
594 pwm_err(ctx, "passwords do not match");
595 continue;
596 default:
597 break;
598 }
599 }
601 password_done:
602 if (io_get_line(NULL, "Notes: ", 0, record->notes, -1,
603 sizeof (notes_buf), notes_buf) == IO_SIGNAL) {
604 return (CMD_SIGNAL);
605 }
606 io_trim_nl(notes_buf);
608 if (io_get_line(NULL, "URL: ", 0, record->url, -1, sizeof (url_buf),
609 url_buf) == IO_SIGNAL) {
610 return (CMD_SIGNAL);
611 }
612 io_trim_nl(url_buf);
614 free(record->group);
615 record->group = (group_buf[0] != '\0') ? xstrdup(group_buf) : NULL;
616 free(record->title);
617 record->title = (title_buf[0] != '\0') ? xstrdup(title_buf) : NULL;
618 free(record->username);
619 record->username = (username_buf[0] != '\0') ? xstrdup(username_buf) :
620 NULL;
621 /*
622 * the current password cannot be edited, keep the current password if
623 * the user pressed return or ^D instead of deleting it like other
624 * fields
625 */
626 if (password_buf[0] != '\0') {
627 free(record->password);
628 record->password = xstrdup(password_buf);
629 }
630 free(record->notes);
631 record->notes = (notes_buf[0] != '\0') ? xstrdup(notes_buf) : NULL;
632 free(record->url);
633 record->url = (url_buf[0] != '\0') ? xstrdup(url_buf) : NULL;
635 return (CMD_OK);
636 }
638 static enum cmd_return
639 cmd_create(struct pwm_ctx *ctx, int argc, char *argv[])
640 {
641 enum cmd_return retval = CMD_ERR;
642 int i;
643 struct record *record = NULL;
644 enum field_type type;
645 char *value;
647 if (!ctx->is_interactive && (argc < 2)) {
648 retval = CMD_USAGE;
649 goto out;
650 }
652 if (ctx->is_readonly) {
653 pwm_err(ctx, "cannot create new entries in read-only mode");
654 goto out;
655 }
657 record = pwfile_create_record();
659 for (i = 1; i < argc; i++) {
660 type = parse_arg(argv[i], field_namev, '=', &value);
661 if ((type >= 0) && ((value == NULL) || (value[0] == '\0'))) {
662 /* skip empty assignments */
663 continue;
664 }
665 switch (type) {
666 case FIELD_GROUP:
667 free(record->group);
668 record->group = xstrdup(value);
669 break;
670 case FIELD_TITLE:
671 free(record->title);
672 record->title = xstrdup(value);
673 break;
674 case FIELD_USERNAME:
675 free(record->username);
676 record->username = xstrdup(value);
677 break;
678 case FIELD_PASSWORD:
679 free(record->password);
680 record->password = xstrdup(value);
681 break;
682 case FIELD_NOTES:
683 free(record->notes);
684 record->notes = xstrdup(value);
685 break;
686 case FIELD_URL:
687 free(record->url);
688 record->url = xstrdup(value);
689 break;
690 default:
691 pwm_err(ctx, "unknown field name \"%s\"", argv[i]);
692 goto out;
693 }
694 }
696 if (ctx->is_interactive && (argc < 2)) {
697 if (read_record_fields(ctx, record) != 0) {
698 goto out;
699 }
700 }
702 pwfile_create_pws_record(ctx, record);
703 retval = CMD_OK;
705 out:
706 pwfile_destroy_record(record);
708 return (retval);
709 }
711 static enum cmd_return
712 cmd_modify(struct pwm_ctx *ctx, int argc, char *argv[])
713 {
714 int retval = CMD_ERR;
715 unsigned int id;
716 int i;
717 struct record *record = NULL;
718 enum field_type type;
719 char *value;
721 if (!ctx->is_interactive && (argc < 2)) {
722 retval = CMD_USAGE;
723 goto out;
724 }
726 if (parse_id(argv[1], &id) != 0) {
727 pwm_err(ctx, "invalid id %s", argv[1]);
728 goto out;
729 }
731 if (ctx->is_readonly) {
732 pwm_err(ctx, "cannot modify entries in read-only mode");
733 goto out;
734 }
736 record = pwfile_get_record(ctx, id);
738 for (i = 2; i < argc; i++) {
739 type = parse_arg(argv[i], field_namev, '=', &value);
740 if ((type >= 0) && ((value == NULL) || (value[0] == '\0'))) {
741 /* skip empty assignments */
742 continue;
743 }
744 switch (type) {
745 case FIELD_GROUP:
746 free(record->group);
747 record->group = xstrdup(value);
748 break;
749 case FIELD_TITLE:
750 free(record->title);
751 record->title = xstrdup(value);
752 break;
753 case FIELD_USERNAME:
754 free(record->username);
755 record->username = xstrdup(value);
756 break;
757 case FIELD_PASSWORD:
758 free(record->password);
759 record->password = xstrdup(value);
760 break;
761 case FIELD_NOTES:
762 free(record->notes);
763 record->notes = xstrdup(value);
764 break;
765 case FIELD_URL:
766 free(record->url);
767 record->url = xstrdup(value);
768 break;
769 default:
770 pwm_err(ctx, "unknown field name \"%s\"", argv[i]);
771 goto out;
772 }
773 }
775 if (ctx->is_interactive && (argc < 3)) {
776 if (read_record_fields(ctx, record) != 0) {
777 goto out;
778 }
779 }
781 pwfile_modify_pws_record(ctx, id, record);
782 retval = CMD_OK;
784 out:
785 pwfile_destroy_record(record);
787 return (retval);
788 }
790 static enum cmd_return
791 cmd_generatepassword(struct pwm_ctx *ctx, int argc, char *argv[])
792 {
793 enum cmd_return retval = CMD_ERR;
794 unsigned int id = 0;
795 int i = 1;
796 enum cmd_generatepassword_arg_type type;
797 char *value = NULL;
798 long x;
799 char *p;
800 size_t password_len = 16;
801 size_t chars_min;
802 const char *chars;
803 struct pw_char_group *char_groupv = NULL;
804 size_t char_groupv_len = 0;
805 int charclass;
806 size_t j;
807 char password[PWS3_MAX_PASSWORD_LEN + 1] = { 0 };
809 /* check if first argument is an id */
810 if ((argc > 1) && (parse_id(argv[1], &id) == 0)) {
811 i++;
812 if (ctx->is_readonly) {
813 pwm_err(ctx, "cannot modify entries in read-only mode");
814 goto out;
815 }
816 }
818 for (; i < argc; i++) {
819 type = parse_arg(argv[i], cmd_generatepassword_argv, '=',
820 &value);
821 if ((type >= 0) && ((value == NULL) || (value[0] == '\0'))) {
822 pwm_err(ctx, "invalid value for \"%s\"", argv[i]);
823 retval = CMD_USAGE;
824 goto out;
825 }
826 switch (type) {
827 case CMD_GP_ARG_LEN:
828 errno = 0;
829 x = strtol(value, &p, 10);
830 if ((errno != 0) || (*value == '\0') || (*p != '\0') ||
831 (x > PWS3_MAX_PASSWORD_LEN) || (x <= 0)) {
832 pwm_err(ctx, "invalid password length \"%s\"",
833 argv[i]);
834 goto out;
835 }
836 password_len = x;
837 break;
838 case CMD_GP_ARG_CHARS:
839 errno = 0;
840 x = strtol(value, &p, 10);
841 if ((errno != 0) || (*value == '\0') || (*p != ':') ||
842 (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) {
843 pwm_err(ctx, "invalid minimum number of "
844 "characters \"%s\"", argv[i]);
845 goto out;
846 }
847 chars_min = x;
849 chars = ++p;
850 while (*p != '\0') {
851 if (!isascii(*p) || !isprint(*p)) {
852 pwm_err(ctx, "invalid character in "
853 "character group \"%s\"", argv[i]);
854 goto out;
855 }
856 p++;
857 }
859 char_groupv = xrealloc(char_groupv,
860 sizeof (struct pw_char_group) * (char_groupv_len +
861 1));
862 char_groupv[char_groupv_len].chars = chars;
863 char_groupv[char_groupv_len].chars_min = chars_min;
864 char_groupv_len++;
865 break;
866 case CMD_GP_ARG_CHARCLASS:
867 errno = 0;
868 x = strtol(value, &p, 10);
869 if ((errno != 0) || (*value == '\0') || (*p != ':') ||
870 (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) {
871 pwm_err(ctx, "invalid minimum number of "
872 "characters \"%s\"", argv[i]);
873 goto out;
874 }
875 chars_min = x;
877 charclass = parse_arg(++p, charclass_namev, '\0', NULL);
878 if (charclass < 0) {
879 pwm_err(ctx, "unknown character class \"%s\"",
880 argv[i]);
881 goto out;
882 }
883 chars = charclass_values[charclass];
884 char_groupv = xrealloc(char_groupv,
885 sizeof (struct pw_char_group) * (char_groupv_len +
886 1));
887 char_groupv[char_groupv_len].chars = chars;
888 char_groupv[char_groupv_len].chars_min = chars_min;
889 char_groupv_len++;
890 break;
891 default:
892 pwm_err(ctx, "invalid argument \"%s\"", argv[i]);
893 retval = CMD_USAGE;
894 goto out;
895 }
896 }
898 for (j = 0; j < char_groupv_len; j++) {
899 if (char_groupv[j].chars_min > password_len) {
900 pwm_err(ctx, "invalid minimum number of characters "
901 "\"%zu:%s\"", char_groupv[j].chars_min,
902 char_groupv[j].chars);
903 goto out;
904 }
905 }
907 if (char_groupv_len == 0) {
908 /* use defaults */
909 char_groupv = xmalloc(sizeof (struct pw_char_group));
910 char_groupv[0].chars = charclass_values[CHARCLASS_GRAPH];
911 char_groupv[0].chars_min = 0;
912 char_groupv_len++;
913 }
915 if (pw_genrandom(char_groupv, char_groupv_len, password,
916 password_len) != 0) {
917 pwm_err(ctx, "failed to generate password that meets the given "
918 "constraints");
919 goto out;
920 }
922 if (id != 0) {
923 if (pwfile_modify_pws_record(ctx, id,
924 &(struct record){ .password = password }) != 0) {
925 pwm_err(ctx, "record %u does not exist", id);
926 goto out;
927 }
928 retval = CMD_OK;
929 } else {
930 retval = io_printf("%s\n", password);
931 }
933 out:
934 free(char_groupv);
936 return (retval);
937 }
939 static enum cmd_return
940 cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[])
941 {
942 unsigned int id;
944 if (argc != 2) {
945 return (CMD_USAGE);
946 }
948 if (parse_id(argv[1], &id) != 0) {
949 pwm_err(ctx, "invalid id %s", argv[1]);
950 return (CMD_ERR);
951 }
953 if (ctx->is_readonly) {
954 pwm_err(ctx, "cannot remove entries in read-only mode");
955 return (CMD_ERR);
956 }
958 if (pwfile_remove_pws_record(ctx, id) != 0) {
959 pwm_err(ctx, "failed to remove record %u", id);
960 return (CMD_ERR);
961 }
963 return (CMD_OK);
964 }
966 static int
967 print_record(struct record *record, int fields[], int show_labels, int fd)
968 {
969 struct pager *pager;
970 struct tm *tm;
971 char timebuf[TIME_SIZE];
972 int retval;
974 pager = pager_create(fd);
975 if (fields[FIELD_TITLE]) {
976 pager_printf(pager, "%s%s\n", show_labels ?
977 field_labels[FIELD_TITLE] : "", (record->title != NULL) ?
978 record->title : "");
979 }
980 if (fields[FIELD_GROUP]) {
981 pager_printf(pager, "%s%s\n", show_labels ?
982 field_labels[FIELD_GROUP] : "", (record->group != NULL) ?
983 record->group : "");
984 }
985 if (fields[FIELD_USERNAME]) {
986 pager_printf(pager, "%s%s\n", show_labels ?
987 field_labels[FIELD_USERNAME] : "",
988 (record->username != NULL) ? record->username : "");
989 }
990 if (fields[FIELD_PASSWORD]) {
991 pager_printf(pager, "%s%s\n", show_labels ?
992 field_labels[FIELD_PASSWORD] : "",
993 (record->password != NULL) ? record->password : "");
994 }
995 if (fields[FIELD_NOTES]) {
996 pager_printf(pager, "%s%s\n", show_labels ?
997 field_labels[FIELD_NOTES] : "", (record->notes != NULL) ?
998 record->notes : "");
999 }
1000 if (fields[FIELD_URL]) {
1001 pager_printf(pager, "%s%s\n", show_labels ?
1002 field_labels[FIELD_URL] : "", (record->url != NULL) ?
1003 record->url : "");
1005 if (fields[FIELD_CTIME]) {
1006 tm = gmtime(&record->ctime);
1007 strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
1008 pager_printf(pager, "%s%s\n", show_labels ?
1009 field_labels[FIELD_CTIME] : "", timebuf);
1011 if (fields[FIELD_MTIME]) {
1012 tm = gmtime(&record->mtime);
1013 strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
1014 pager_printf(pager, "%s%s\n", show_labels ?
1015 field_labels[FIELD_MTIME] : "", timebuf);
1017 retval = pager_show(pager);
1018 pager_destroy(pager);
1020 return (retval);
1023 static enum cmd_return
1024 cmd_show(struct pwm_ctx *ctx, int argc, char *argv[])
1026 enum cmd_return retval;
1027 unsigned int id;
1028 struct record *record;
1029 int i;
1030 enum field_type type;
1031 int fields[COUNTOF(field_namev) - 1] = {
1032 [FIELD_GROUP] = 1,
1033 [FIELD_TITLE] = 1,
1034 [FIELD_USERNAME] = 1,
1035 [FIELD_PASSWORD] = 0,
1036 [FIELD_NOTES] = 1,
1037 [FIELD_URL] = 1,
1038 [FIELD_MTIME] = 1,
1039 [FIELD_CTIME] = 1
1040 };
1042 if (argc < 2) {
1043 return (CMD_USAGE);
1046 if (parse_id(argv[1], &id) != 0) {
1047 pwm_err(ctx, "invalid id %s", argv[1]);
1048 return (CMD_ERR);
1051 if (argc > 2) {
1052 /* show only explicitly given field names */
1053 memset(fields, 0, sizeof (fields));
1056 for (i = 2; i < argc; i++) {
1057 type = parse_arg(argv[i], field_namev, '\0', NULL);
1058 if (type < 0) {
1059 pwm_err(ctx, "unknown field name \"%s\"", argv[i]);
1060 return (CMD_ERR);
1062 fields[type] = 1;
1065 record = pwfile_get_record(ctx, id);
1066 if (record == NULL) {
1067 pwm_err(ctx, "record %u does not exist", id);
1068 return (CMD_ERR);
1070 retval = (print_record(record, fields, 1, STDOUT_FILENO) != IO_SIGNAL) ?
1071 CMD_OK : CMD_SIGNAL;
1072 pwfile_destroy_record(record);
1074 return (retval);
1077 static enum cmd_return
1078 cmd_pipe(struct pwm_ctx *ctx, int argc, char *argv[])
1080 enum cmd_return retval = CMD_ERR;
1081 unsigned int id;
1082 struct record *record = NULL;
1083 enum field_type type;
1084 int fields[COUNTOF(field_namev) - 1] = { 0 };
1085 char *cmd;
1086 struct proc proc = { 0 };
1088 /* if pipecommand is set, the last argument is optional */
1089 if (((ctx->pipecmd == NULL) && (argc != 4)) ||
1090 ((ctx->pipecmd != NULL) && ((argc < 3) || (argc > 4)))) {
1091 return (CMD_USAGE);
1094 if (parse_id(argv[1], &id) != 0) {
1095 pwm_err(ctx, "invalid id %s", argv[1]);
1096 return (CMD_ERR);
1099 type = parse_arg(argv[2], field_namev, '\0', NULL);
1100 if (type < 0) {
1101 pwm_err(ctx, "unknown field name \"%s\"", argv[2]);
1102 return (CMD_ERR);
1104 fields[type] = 1;
1106 cmd = (argc == 4) ? argv[3] : ctx->pipecmd;
1107 if (proc_open(&proc, cmd, "w") != IO_OK) {
1108 goto out;
1112 record = pwfile_get_record(ctx, id);
1113 if (record == NULL) {
1114 pwm_err(ctx, "record %u does not exist", id);
1115 goto out;
1118 retval = (print_record(record, fields, 0, proc.fd) != IO_SIGNAL) ?
1119 CMD_OK : CMD_SIGNAL;
1121 out:
1122 pwfile_destroy_record(record);
1123 if (proc.pid != 0) {
1124 if (proc_close(&proc) == IO_SIGNAL) {
1125 retval = CMD_SIGNAL;
1129 return (retval);
1132 static enum cmd_return
1133 cmd_creategroup(struct pwm_ctx *ctx, int argc, char *argv[])
1135 char group_buf[PWM_LINE_MAX] = { '\0' };
1137 if (!ctx->is_interactive && (argc != 2)) {
1138 return (CMD_USAGE);
1141 if (ctx->is_readonly) {
1142 pwm_err(ctx, "cannot create groups in read-only mode");
1143 return (CMD_ERR);
1146 if (ctx->is_interactive && (argc != 2)) {
1147 if (io_get_line(NULL, "Group: ", 0, NULL, 0,
1148 sizeof (group_buf), group_buf) == IO_SIGNAL) {
1149 return (CMD_SIGNAL);
1151 io_trim_nl(group_buf);
1152 } else {
1153 strcpy(group_buf, argv[1]);
1156 if (group_buf[0] != '\0') {
1157 if (pwfile_create_group(ctx, group_buf) != 0) {
1158 pwm_err(ctx, "group \"%s\" already exists", group_buf);
1159 return (CMD_ERR);
1163 return (CMD_OK);
1166 static enum cmd_return
1167 cmd_removegroup(struct pwm_ctx *ctx, int argc, char *argv[])
1169 if (argc != 2) {
1170 return (CMD_USAGE);
1173 if (ctx->is_readonly) {
1174 pwm_err(ctx, "cannot remove groups in read-only mode");
1175 return (CMD_ERR);
1178 if (pwfile_remove_group(ctx, argv[1]) != 0) {
1179 pwm_err(ctx, "group \"%s\" is not empty or does not exist", argv[1]);
1180 return (CMD_ERR);
1183 return (CMD_OK);
1186 static enum cmd_return
1187 cmd_changepassword(struct pwm_ctx *ctx, int argc, char *argv[])
1189 size_t len;
1191 if (argc > 2) {
1192 return (CMD_USAGE);
1195 if (ctx->is_readonly) {
1196 pwm_err(ctx, "cannot modify entries in read-only mode");
1197 return (CMD_ERR);
1200 if (argc == 2) {
1201 len = strlen(argv[1]);
1202 if (len == 0) {
1203 pwm_err(ctx, "password must not be empty");
1204 return (CMD_ERR);
1205 } else if (len + 1 > sizeof (ctx->password)) {
1206 pwm_err(ctx, "password too long");
1207 return (CMD_ERR);
1209 memcpy(ctx->password, argv[1], len + 1);
1210 } else {
1211 if (pwm_read_password(ctx, 1) != 0) {
1212 return (CMD_ERR);
1216 return (CMD_OK);
1219 static enum cmd_return
1220 cmd_help(struct pwm_ctx *ctx, int argc, char *argv[])
1222 enum cmd_return retval = CMD_OK;
1223 struct pager *pager;
1224 struct cmd *cmd;
1226 if (argc > 2) {
1227 return (CMD_USAGE);
1230 if (argc == 2) {
1231 for (cmd = cmds; cmd->cmd_func != NULL; cmd++) {
1232 if ((strcmp(argv[1], cmd->abbrev_cmd) == 0) ||
1233 (strcmp(argv[1], cmd->full_cmd) == 0)) {
1234 if (io_printf("usage: %s\n", cmd->usage) ==
1235 IO_SIGNAL) {
1236 retval = CMD_SIGNAL;
1238 break;
1241 } else {
1242 pager = pager_create(STDOUT_FILENO);
1243 pager_printf(pager, "Commands:\n");
1244 for (cmd = cmds; cmd->cmd_func != NULL; cmd++) {
1245 pager_printf(pager, "%-2s %-16s %s\n", cmd->abbrev_cmd,
1246 cmd->full_cmd, cmd->description);
1248 retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
1249 pager_destroy(pager);
1252 return (retval);
1255 static enum cmd_return
1256 cmd_write(struct pwm_ctx *ctx, int argc, char *argv[])
1258 if (argc != 1) {
1259 return (CMD_USAGE);
1262 if (ctx->is_readonly) {
1263 pwm_err(ctx, "cannot write changes in read-only mode");
1264 return (CMD_ERR);
1267 return ((pwfile_write_file(ctx) == 0) ? CMD_OK : CMD_ERR);
1270 static enum cmd_return
1271 cmd_quit(struct pwm_ctx *ctx, int argc, char *argv[])
1273 if (argc != 1) {
1274 return (CMD_USAGE);
1277 if ((argv[0][0] == 'q') && ctx->unsaved_changes &&
1278 (ctx->prev_cmd != NULL) && (strcmp(ctx->prev_cmd, "quit") != 0)) {
1279 printf("Warning: There are unsaved changes\n");
1280 return (CMD_OK);
1283 return (CMD_QUIT);
1286 struct cmd *
1287 cmd_match(const char *cmd_name)
1289 size_t i;
1291 for (i = 0; cmds[i].cmd_func != NULL; i++) {
1292 if ((strcmp(cmds[i].abbrev_cmd, cmd_name) == 0) ||
1293 (strcmp(cmds[i].full_cmd, cmd_name) == 0)) {
1294 return (&cmds[i]);
1298 return (NULL);