# HG changeset patch # User Guido Berhoerster # Date 1504780850 -7200 # Node ID ec01c579024aed7ddf3a10fdeff3b38280bd2074 # Parent ee4d36c852871aeddb189917087b613de09b3858 Add fully interactive mode diff -r ee4d36c85287 -r ec01c579024a cmd.c --- a/cmd.c Wed Sep 06 16:41:58 2017 +0200 +++ b/cmd.c Thu Sep 07 12:40:50 2017 +0200 @@ -402,18 +402,101 @@ return (retval); } +static int +read_record_fields(struct pwm_ctx *ctx, struct record *record) +{ + char group_buf[PWM_LINE_MAX] = { '\0' }; + char title_buf[PWM_LINE_MAX] = { '\0' }; + char username_buf[PWM_LINE_MAX] = { '\0' }; + char password_buf[PWM_LINE_MAX] = { '\0' }; + char notes_buf[PWM_LINE_MAX] = { '\0' }; + char url_buf[PWM_LINE_MAX] = { '\0' }; + + if (io_get_line(NULL, "Group: ", 0, record->group, -1, + sizeof (group_buf), group_buf) == IO_SIGNAL) { + return (CMD_SIGNAL); + } + io_trim_nl(group_buf); + + if (io_get_line(NULL, "Title: ", 0, record->title, -1, + sizeof (title_buf), title_buf) == IO_SIGNAL) { + return (CMD_SIGNAL); + } + io_trim_nl(title_buf); + + if (io_get_line(NULL, "Username: ", 0, record->username, -1, + sizeof (username_buf), username_buf) == IO_SIGNAL) { + return (CMD_SIGNAL); + } + io_trim_nl(username_buf); + + for (;;) { + switch (io_get_password("Password: ", "Confirm Password: ", + sizeof (password_buf), password_buf)) { + case IO_OK: /* FALLTHROUGH */ + case IO_PASSWORD_EMPTY: + goto password_done; + case IO_SIGNAL: + return (CMD_SIGNAL); + case IO_PASSWORD_MISMATCH: + pwm_err(ctx, "passwords do not match"); + continue; + } + } + +password_done: + if (io_get_line(NULL, "Notes: ", 0, record->notes, -1, + sizeof (notes_buf), notes_buf) == IO_SIGNAL) { + return (CMD_SIGNAL); + } + io_trim_nl(notes_buf); + + if (io_get_line(NULL, "URL: ", 0, record->url, -1, sizeof (url_buf), + url_buf) == IO_SIGNAL) { + return (CMD_SIGNAL); + } + io_trim_nl(url_buf); + + free(record->group); + record->group = (group_buf[0] != '\0') ? xstrdup(group_buf) : NULL; + free(record->title); + record->title = (title_buf[0] != '\0') ? xstrdup(title_buf) : NULL; + free(record->username); + record->username = (username_buf[0] != '\0') ? xstrdup(username_buf) : + NULL; + /* + * the current password cannot be edited, keep the current password if + * the user pressed return or ^D instead of deleting it like other + * fields + */ + if (password_buf[0] != '\0') { + free(record->password); + record->password = xstrdup(password_buf); + } + free(record->notes); + record->notes = (notes_buf[0] != '\0') ? xstrdup(notes_buf) : NULL; + free(record->url); + record->url = (url_buf[0] != '\0') ? xstrdup(url_buf) : NULL; + + return (CMD_OK); +} + static enum cmd_return cmd_create(struct pwm_ctx *ctx, int argc, char *argv[]) { + enum cmd_return retval = CMD_ERR; int i; - struct record record = { 0 }; + struct record *record = NULL; enum field_type type; char *value; - if (argc < 2) { - return (CMD_USAGE); + if (!ctx->is_interactive && (argc < 2)) { + retval = CMD_USAGE; + goto out; } + record = pwfile_create_record(); + for (i = 1; i < argc; i++) { type = parse_arg(argv[i], field_namev, '=', &value); if (type == FIELD_UNKNOWN) { @@ -425,57 +508,76 @@ } switch (type) { case FIELD_GROUP: - record.group = value; + free(record->group); + record->group = xstrdup(value); break; case FIELD_TITLE: - record.title = value; + free(record->title); + record->title = xstrdup(value); break; case FIELD_USERNAME: - record.username = value; + free(record->username); + record->username = xstrdup(value); break; case FIELD_PASSWORD: - record.password = value; + free(record->password); + record->password = xstrdup(value); break; case FIELD_NOTES: - record.notes = value; + free(record->notes); + record->notes = xstrdup(value); break; case FIELD_URL: - record.url = value; + free(record->url); + record->url = xstrdup(value); break; default: pwm_err(ctx, "bad field name \"%s\"", argv[i]); - return (CMD_ERR); + goto out; } } - pwfile_create_record(ctx, &record); + if (ctx->is_interactive && (argc < 2)) { + if (read_record_fields(ctx, record) != 0) { + goto out; + } + } - return (CMD_OK); + pwfile_create_pws_record(ctx, record); + retval = CMD_OK; + +out: + pwfile_destroy_record(record); + + return (retval); } static enum cmd_return cmd_modify(struct pwm_ctx *ctx, int argc, char *argv[]) { + int retval = CMD_ERR; unsigned int id; int i; - struct record record = { 0 }; + struct record *record = NULL; enum field_type type; char *value; - if (argc < 2) { - return (CMD_USAGE); + if (!ctx->is_interactive && (argc < 2)) { + retval = CMD_USAGE; + goto out; } if (parse_id(argv[1], &id) != 0) { pwm_err(ctx, "invalid id %s", argv[1]); - return (CMD_ERR); + goto out; } + record = pwfile_get_record(ctx, id); for (i = 2; i < argc; i++) { type = parse_arg(argv[i], field_namev, '=', &value); if (type == FIELD_UNKNOWN) { pwm_err(ctx, "bad field assignment \"%s\"", argv[i]); - return (CMD_ERR); + goto out; } if (value[0] == '\0') { /* skip empty assignments */ @@ -483,32 +585,48 @@ } switch (type) { case FIELD_GROUP: - record.group = value; + free(record->group); + record->group = xstrdup(value); break; case FIELD_TITLE: - record.title = value; + free(record->title); + record->title = xstrdup(value); break; case FIELD_USERNAME: - record.username = value; + free(record->username); + record->username = xstrdup(value); break; case FIELD_PASSWORD: - record.password = value; + free(record->password); + record->password = xstrdup(value); break; case FIELD_NOTES: - record.notes = value; + free(record->notes); + record->notes = xstrdup(value); break; case FIELD_URL: - record.url = value; + free(record->url); + record->url = xstrdup(value); break; default: pwm_err(ctx, "bad field name \"%s\"", argv[i]); - return (CMD_ERR); + goto out; } } - pwfile_modify_record(ctx, id, &record); + if (ctx->is_interactive && (argc < 3)) { + if (read_record_fields(ctx, record) != 0) { + goto out; + } + } - return (CMD_OK); + pwfile_modify_pws_record(ctx, id, record); + retval = CMD_OK; + +out: + pwfile_destroy_record(record); + + return (retval); } static enum cmd_return @@ -633,7 +751,7 @@ } if (id != 0) { - if (pwfile_modify_record(ctx, id, + if (pwfile_modify_pws_record(ctx, id, &(struct record){ .password = password }) != 0) { pwm_err(ctx, "record %u does not exist", id); goto out; @@ -663,7 +781,7 @@ return (CMD_ERR); } - if (pwfile_remove_record(ctx, id) != 0) { + if (pwfile_remove_pws_record(ctx, id) != 0) { pwm_err(ctx, "failed to remove record %u", id); return (CMD_ERR); } @@ -835,13 +953,27 @@ static enum cmd_return cmd_creategroup(struct pwm_ctx *ctx, int argc, char *argv[]) { - if (argc != 2) { + char group_buf[PWM_LINE_MAX] = { '\0' }; + + if (!ctx->is_interactive && (argc != 2)) { return (CMD_USAGE); } - if (pwfile_create_group(ctx, argv[1]) != 0) { - pwm_err(ctx, "group \"%s\" already exists", argv[1]); - return (CMD_ERR); + if (ctx->is_interactive && (argc != 2)) { + if (io_get_line(NULL, "Group: ", 0, NULL, 0, + sizeof (group_buf), group_buf) == IO_SIGNAL) { + return (CMD_SIGNAL); + } + io_trim_nl(group_buf); + } else { + strcpy(group_buf, argv[1]); + } + + if (group_buf[0] != '\0') { + if (pwfile_create_group(ctx, group_buf) != 0) { + pwm_err(ctx, "group \"%s\" already exists", group_buf); + return (CMD_ERR); + } } return (CMD_OK); diff -r ee4d36c85287 -r ec01c579024a io.c --- a/io.c Wed Sep 06 16:41:58 2017 +0200 +++ b/io.c Thu Sep 07 12:40:50 2017 +0200 @@ -47,6 +47,17 @@ siglongjmp(signal_env, signal_no); } +void +io_trim_nl(char *s) +{ + size_t len; + + len = strlen(s); + if ((len > 0) && (s[len - 1] == '\n')) { + s[len - 1] = '\0'; + } +} + int io_gl_complete_nothing(WordCompletion *cpl, void *data, const char *line, int word_end) diff -r ee4d36c85287 -r ec01c579024a io.h --- a/io.h Wed Sep 06 16:41:58 2017 +0200 +++ b/io.h Thu Sep 07 12:40:50 2017 +0200 @@ -37,6 +37,7 @@ IO_PASSWORD_MISMATCH = -6 }; +void io_trim_nl(char *); int io_gl_complete_nothing(WordCompletion *, void *, const char *, int); enum io_status io_get_char(const char *, int *); diff -r ee4d36c85287 -r ec01c579024a pwfile.c --- a/pwfile.c Wed Sep 06 16:41:58 2017 +0200 +++ b/pwfile.c Thu Sep 07 12:40:50 2017 +0200 @@ -827,7 +827,7 @@ } int -pwfile_create_record(struct pwm_ctx *ctx, struct record *record) +pwfile_create_pws_record(struct pwm_ctx *ctx, struct record *record) { struct pws3_record *pws3_record; const unsigned char *uuid; @@ -853,7 +853,7 @@ } int -pwfile_modify_record(struct pwm_ctx *ctx, unsigned int id, +pwfile_modify_pws_record(struct pwm_ctx *ctx, unsigned int id, struct record *record) { const unsigned char *uuid; @@ -871,7 +871,7 @@ } int -pwfile_remove_record(struct pwm_ctx *ctx, unsigned int id) +pwfile_remove_pws_record(struct pwm_ctx *ctx, unsigned int id) { const unsigned char *uuid; struct record_id_entry *entry; @@ -943,6 +943,24 @@ } struct record * +pwfile_create_record(void) +{ + struct record *record; + + record = xmalloc(sizeof (struct record)); + record->ctime = (time_t)0; + record->mtime = (time_t)0; + record->group = NULL; + record->title = NULL; + record->username = NULL; + record->password = NULL; + record->notes = NULL; + record->url = NULL; + + return (record); +} + +struct record * pwfile_get_record(struct pwm_ctx *ctx, unsigned int id) { struct record *record; diff -r ee4d36c85287 -r ec01c579024a pwfile.h --- a/pwfile.h Wed Sep 06 16:41:58 2017 +0200 +++ b/pwfile.h Thu Sep 07 12:40:50 2017 +0200 @@ -79,12 +79,13 @@ void pwfile_destroy_list(union list_item **); struct metadata * pwfile_get_metadata(struct pwm_ctx *); void pwfile_destroy_metadata(struct metadata *); -int pwfile_create_record(struct pwm_ctx *, struct record *); -int pwfile_modify_record(struct pwm_ctx *, unsigned int, +int pwfile_create_pws_record(struct pwm_ctx *, struct record *); +int pwfile_modify_pws_record(struct pwm_ctx *, unsigned int, struct record *); -int pwfile_remove_record(struct pwm_ctx *, unsigned int); +int pwfile_remove_pws_record(struct pwm_ctx *, unsigned int); int pwfile_create_group(struct pwm_ctx *, const char *); int pwfile_remove_group(struct pwm_ctx *, const char *); +struct record * pwfile_create_record(void); struct record * pwfile_get_record(struct pwm_ctx *, unsigned int); void pwfile_destroy_record(struct record *); diff -r ee4d36c85287 -r ec01c579024a pwm.1.xml --- a/pwm.1.xml Wed Sep 06 16:41:58 2017 +0200 +++ b/pwm.1.xml Thu Sep 07 12:40:50 2017 +0200 @@ -34,7 +34,7 @@ guido+pwm@berhoerster.name - 6 September, 2017 + 7 September, 2017 pwm @@ -240,6 +240,9 @@ Create a new entry assigning each given field to the corresponsing value. + If no fields are specified in interactive mode, + pwm will prompt the user for the content of + each field. @@ -268,6 +271,10 @@ id assigning each given field to the corresponsing value. + If no fields are specified and pwm is + running in interactive mode, it will prompt the user for the + content of each field, allowing him to edit any previous + content. @@ -374,6 +381,9 @@ Create a new empty group named name. + In interactive-mode the name + argument is optional, if it is not specified pwm + will prompt the user for it. diff -r ee4d36c85287 -r ec01c579024a pwm.c --- a/pwm.c Wed Sep 06 16:41:58 2017 +0200 +++ b/pwm.c Thu Sep 07 12:40:50 2017 +0200 @@ -101,7 +101,7 @@ } static int -run_input_loop(struct pwm_ctx *ctx, int is_interactive) +run_input_loop(struct pwm_ctx *ctx) { int retval = -1; char prompt[8 + 2 + 1]; @@ -141,7 +141,7 @@ fprintf(stderr, "line too long\n"); goto out; case IO_EOF: - if (is_interactive) { + if (ctx->is_interactive) { /* treat as "q" command */ strcpy(buf, "q\n"); io_retval = IO_OK; @@ -164,13 +164,13 @@ err(1, "tok_tokenize"); case TOK_ERR_UNTERMINATED_QUOTE: fprintf(stderr, "unterminated quote\n"); - if (!is_interactive) { + if (!ctx->is_interactive) { goto out; } goto next; case TOK_ERR_TRAILING_BACKSLASH: fprintf(stderr, "trailing backslash\n"); - if (!is_interactive) { + if (!ctx->is_interactive) { goto out; } goto next; @@ -185,7 +185,7 @@ cmd = cmd_match(argv[0]); if (cmd == NULL) { pwm_err(ctx, "unknown command: %s", argv[0]); - if (is_interactive) { + if (ctx->is_interactive) { goto next; } else { goto out; @@ -198,7 +198,7 @@ case CMD_USAGE: fprintf(stderr, "usage: %s\n", cmd->usage); case CMD_ERR: /* FALLTHROUGH */ - if (!is_interactive) { + if (!ctx->is_interactive) { goto out; } break; @@ -310,7 +310,6 @@ { int status = EXIT_FAILURE; char *locale; - int is_interactive; int errflag = 0; int c; const char *master_password_filename = NULL; @@ -343,7 +342,7 @@ goto out; } - is_interactive = isatty(STDIN_FILENO); + ctx.is_interactive = isatty(STDIN_FILENO); while (!errflag && (c = getopt(argc, argv, "P:h")) != -1) { switch (c) { @@ -386,7 +385,7 @@ goto out; } - if (is_interactive) { + if (ctx.is_interactive) { printf("pwm version %s\n", VERSION); } else if (master_password_filename == NULL) { fprintf(stderr, "master password file must be specified when " @@ -419,7 +418,7 @@ } /* run main input loop */ - status = (run_input_loop(&ctx, is_interactive) != 0); + status = (run_input_loop(&ctx) != 0); out: pwfile_destroy(&ctx); diff -r ee4d36c85287 -r ec01c579024a pwm.h --- a/pwm.h Wed Sep 06 16:41:58 2017 +0200 +++ b/pwm.h Thu Sep 07 12:40:50 2017 +0200 @@ -44,6 +44,7 @@ #endif /* !PWM_HISTORY_MAX */ struct pwm_ctx { + int is_interactive; const char *prev_cmd; char *errmsg; char *dirname;