Mercurial > projects > pwm
view cmd.c @ 11:85bce13237cf
Add filter expressions to list command
Refactor field parsing so it can be used for parsing fields, field assignments
and filter expressions.
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Mon, 31 Jul 2017 09:20:21 +0200 |
parents | 17fb30016e64 |
children | 8768fbd09bc5 |
line wrap: on
line source
/* * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "compat.h" #ifdef HAVE_ERR_H #include <err.h> #endif /* HAVE_ERR_H */ #include <errno.h> #include <limits.h> #ifdef HAVE_READPASSPHRASE_H #include <readpassphrase.h> #endif /* READPASSPHRASE_H */ #include <regex.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include "cmd.h" #include "pwfile.h" #include "util.h" #define TIME_FORMAT "%Y-%m-%dT%TZ" #define TIME_SIZE (4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1) enum field_type { FIELD_UNKNOWN = -1, FIELD_GROUP, FIELD_TITLE, FIELD_USERNAME, FIELD_PASSWORD, FIELD_NOTES, FIELD_URL, FIELD_MTIME, FIELD_CTIME }; static enum cmd_return cmd_info(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_list(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_create(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_modify(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_remove(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_show(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_pipe(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_creategroup(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_removegroup(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_changepassword(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_help(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_write(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_quit(struct pwm_ctx *, int, char *[]); static const char *field_names[] = { "group", "title", "username", "password", "notes", "url", "ctime", "mtime" }; static const char *field_labels[] = { "Group: ", "Title: ", "Username: ", "Password: ", "Notes: ", "URL: ", "Created: ", "Modified: " }; static struct cmd cmds[] = { { "i", "info", "info", "Show metadata information about the current file", cmd_info }, { "ls", "list", "list [field~regex ...]", "List entries", cmd_list }, { "c", "create", "create field=value ...", "Create entry", cmd_create }, { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify }, { "rm", "remove", "remove id", "Delete entry", cmd_remove }, { "s", "show", "show id field", "Show entry", cmd_show }, { "p", "pipe", "pipe id field command", "Pipe entry to external command", cmd_pipe }, { "cg", "creategroup", "creategroup name", "Create empty group", cmd_creategroup }, { "rg", "removegroup", "removegroup name", "Delete empty group", cmd_removegroup }, { "ch", "changepassword", "changepassword", "Change password", cmd_changepassword }, { "h", "help", "help", "Show help text", cmd_help }, { "w", "write", "write", "Write the database", cmd_write }, { "q", "quit", "quit", "Quit", cmd_quit }, { 0 } }; static enum field_type parse_field(char *field_arg, int sep, char **valuep) { int i; size_t field_name_len; for (i = 0; i < (int)COUNTOF(field_names); i++) { field_name_len = strlen(field_names[i]); if ((strncmp(field_names[i], field_arg, field_name_len) == 0) && (field_arg[field_name_len] == sep)) { if (valuep != NULL) { *valuep = field_arg + field_name_len + 1; } return (i); } } return (FIELD_UNKNOWN); } static int parse_id(const char *arg, unsigned int *idp) { long x; char *p; errno = 0; x = strtol(arg, &p, 10); if ((errno != 0) || (*arg == '\0') || (*p != '\0') || (x > UINT_MAX) || (x <= 0)) { return (-1); } *idp = (unsigned int)x; return (0); } static enum cmd_return cmd_info(struct pwm_ctx *ctx, int argc, char *argv[]) { struct metadata *metadata; struct tm *tm; char timebuf[TIME_SIZE]; if (argc != 1) { return (CMD_USAGE); } metadata = pwfile_get_metadata(ctx); printf("Format: 0x%04x\n", metadata->version); if (metadata->user != NULL) { printf("User: %s\n", metadata->user); } if (metadata->user != NULL) { printf("Host: %s\n", metadata->host); } if (metadata->user != NULL) { printf("Application: %s\n", metadata->application); } tm = gmtime(&metadata->timestamp); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); printf("Last Saved: %s\n", timebuf); pwfile_destroy_metadata(metadata); return (CMD_OK); } static enum cmd_return cmd_list(struct pwm_ctx *ctx, int argc, char *argv[]) { int retval = CMD_ERR; int i; regex_t *group_re = NULL; regex_t *title_re = NULL; regex_t *username_re = NULL; regex_t *notes_re = NULL; regex_t *url_re = NULL; enum field_type type; char *value; regex_t **repp; int errcode; char *errbuf; size_t errbuf_size; union list_item **list = NULL; size_t j; struct record *record; for (i = 1; i < argc; i++) { type = parse_field(argv[i], '~', &value); if (type == FIELD_UNKNOWN) { fprintf(stderr, "bad field name \"%s\"\n", argv[i]); goto out; } if (value[0] == '\0') { /* skip empty expressions */ continue; } switch (type) { case FIELD_GROUP: repp = &group_re; break; case FIELD_TITLE: repp = &title_re; break; case FIELD_USERNAME: repp = &username_re; break; case FIELD_NOTES: repp = ¬es_re; break; case FIELD_URL: repp = &url_re; break; default: fprintf(stderr, "bad field name \"%s\"\n", argv[i]); goto out; } if (*repp == NULL) { *repp = xmalloc(sizeof (regex_t)); } else { regfree(*repp); } errcode = regcomp(*repp, value, REG_EXTENDED | REG_NOSUB); if (errcode != 0) { errbuf_size = regerror(errcode, *repp, "", 0); errbuf = xmalloc(errbuf_size); regerror(errcode, *repp, errbuf, errbuf_size); fprintf(stderr, "bad regular expression \"%s\"\n", errbuf); free(errbuf); free(*repp); *repp = NULL; goto out; } } list = pwfile_create_list(ctx); for (j = 0; list[j] != NULL; j++) { if (list[j]->any.type == ITEM_TYPE_GROUP) { printf("[%s]\n", list[j]->group.group); } else { record = pwfile_get_record(ctx, list[j]->record.id); if (((group_re == NULL) || (regexec(group_re, record->group, 0, NULL, 0) == 0)) && ((title_re == NULL) || (regexec(title_re, record->title, 0, NULL, 0) == 0)) && ((username_re == NULL) || (regexec(username_re, record->username, 0, NULL, 0) == 0)) && ((notes_re == NULL) || (regexec(notes_re, record->notes, 0, NULL, 0) == 0)) && ((url_re == NULL) || (regexec(url_re, record->url, 0, NULL, 0) == 0))) { printf("%4u %s\n", list[j]->record.id, (list[j]->record.title != NULL) ? list[j]->record.title : ""); } pwfile_destroy_record(record); } } retval = CMD_OK; out: if (group_re != NULL) { regfree(group_re); free(group_re); } if (title_re != NULL) { regfree(title_re); free(title_re); } if (username_re != NULL) { regfree(username_re); free(username_re); } if (notes_re != NULL) { regfree(notes_re); free(notes_re); } if (url_re != NULL) { regfree(url_re); free(url_re); } pwfile_destroy_list(list); return (retval); } static enum cmd_return cmd_create(struct pwm_ctx *ctx, int argc, char *argv[]) { int i; struct record record = { 0 }; enum field_type type; char *value; if (argc < 2) { return (CMD_USAGE); } for (i = 1; i < argc; i++) { type = parse_field(argv[i], '=', &value); if (type == FIELD_UNKNOWN) { fprintf(stderr, "bad field assignment \"%s\"\n", argv[i]); } if (value[0] == '\0') { /* skip empty assignments */ continue; } switch (type) { case FIELD_GROUP: record.group = value; break; case FIELD_TITLE: record.title = value; break; case FIELD_USERNAME: record.username = value; break; case FIELD_PASSWORD: record.password = value; break; case FIELD_NOTES: record.notes = value; break; case FIELD_URL: record.url = value; break; default: fprintf(stderr, "bad field name \"%s\"\n", argv[i]); return (CMD_ERR); } } pwfile_create_record(ctx, &record); return (CMD_OK); } static enum cmd_return cmd_modify(struct pwm_ctx *ctx, int argc, char *argv[]) { unsigned int id; int i; struct record record = { 0 }; enum field_type type; char *value; if (argc < 2) { return (CMD_USAGE); } if (parse_id(argv[1], &id) != 0) { fprintf(stderr, "invalid id %s\n", argv[1]); return (CMD_ERR); } for (i = 2; i < argc; i++) { type = parse_field(argv[i], '=', &value); if (type == FIELD_UNKNOWN) { fprintf(stderr, "bad field assignment \"%s\"\n", argv[i]); return (CMD_ERR); } if (value[0] == '\0') { /* skip empty assignments */ continue; } switch (type) { case FIELD_GROUP: record.group = value; break; case FIELD_TITLE: record.title = value; break; case FIELD_USERNAME: record.username = value; break; case FIELD_PASSWORD: record.password = value; break; case FIELD_NOTES: record.notes = value; break; case FIELD_URL: record.url = value; break; default: fprintf(stderr, "bad field name \"%s\"\n", argv[i]); return (CMD_ERR); } } pwfile_modify_record(ctx, id, &record); return (CMD_OK); } static enum cmd_return cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[]) { unsigned int id; if (argc != 2) { return (CMD_USAGE); } if (parse_id(argv[1], &id) != 0) { fprintf(stderr, "invalid id %s\n", argv[1]); return (CMD_ERR); } if (pwfile_remove_record(ctx, id) != 0) { fprintf(stderr, "failed to remove record %u\n", id); return (CMD_ERR); } return (CMD_OK); } static int print_field(const char *label, const char *value, int show_label, FILE *fp) { fprintf(fp, "%s%s\n", show_label ? label : "", (value != NULL) ? value : ""); if (ferror(fp)) { warn("fprintf"); return (-1); } return (0); } static void print_record(struct record *record, int fields[], int show_labels, FILE *fp) { struct tm *tm; char timebuf[TIME_SIZE]; if (fields[FIELD_TITLE]) { if (print_field(field_labels[FIELD_TITLE], record->title, show_labels, fp) != 0) { return; } } if (fields[FIELD_GROUP]) { if (print_field(field_labels[FIELD_GROUP], record->group, show_labels, fp)) { return; } } if (fields[FIELD_USERNAME]) { if (print_field(field_labels[FIELD_USERNAME], record->username, show_labels, fp)) { return; } } if (fields[FIELD_PASSWORD]) { if (print_field(field_labels[FIELD_PASSWORD], record->password, show_labels, fp)) { return; } } if (fields[FIELD_NOTES]) { if (print_field(field_labels[FIELD_NOTES], record->notes, show_labels, fp)) { return; } } if (fields[FIELD_URL]) { if (print_field(field_labels[FIELD_URL], record->url, show_labels, fp)) { return; } } if (fields[FIELD_CTIME]) { tm = gmtime(&record->ctime); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); if (print_field(field_labels[FIELD_CTIME], timebuf, show_labels, fp)) { return; } } if (fields[FIELD_MTIME]) { tm = gmtime(&record->mtime); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); if (print_field(field_labels[FIELD_MTIME], timebuf, show_labels, fp)) { return; } } } static enum cmd_return cmd_show(struct pwm_ctx *ctx, int argc, char *argv[]) { unsigned int id; struct record *record; int i; enum field_type type; int fields[COUNTOF(field_names)] = { 0 }; if (argc < 2) { return (CMD_USAGE); } if (parse_id(argv[1], &id) != 0) { fprintf(stderr, "invalid id %s\n", argv[1]); return (CMD_ERR); } for (i = 2; i < argc; i++) { type = parse_field(argv[i], '\0', NULL); if (type < 0) { fprintf(stderr, "bad field name \"%s\"\n", argv[i]); return (CMD_ERR); } fields[type] = 1; } record = pwfile_get_record(ctx, id); if (record == NULL) { fprintf(stderr, "record %u does not exist\n", id); return (CMD_ERR); } print_record(record, fields, 1, stdout); pwfile_destroy_record(record); return (CMD_OK); } static enum cmd_return cmd_pipe(struct pwm_ctx *ctx, int argc, char *argv[]) { enum cmd_return retval = CMD_ERR; unsigned int id; struct record *record = NULL; enum field_type type; int fields[COUNTOF(field_names)] = { 0 }; FILE *fp = NULL; if (argc != 4) { return (CMD_USAGE); } if (parse_id(argv[1], &id) != 0) { fprintf(stderr, "invalid id %s\n", argv[1]); return (CMD_ERR); } type = parse_field(argv[2], '\0', NULL); if (type < 0) { fprintf(stderr, "bad field name \"%s\"\n", argv[2]); return (CMD_ERR); } fields[type] = 1; fp = popen(argv[3], "w"); if (fp == NULL) { warn("popen"); goto out; } record = pwfile_get_record(ctx, id); if (record == NULL) { fprintf(stderr, "record %u does not exist\n", id); goto out; } print_record(record, fields, 0, fp); retval = CMD_OK; out: pwfile_destroy_record(record); if (fp != NULL) { pclose(fp); } return (retval); } static enum cmd_return cmd_creategroup(struct pwm_ctx *ctx, int argc, char *argv[]) { if (argc != 2) { return (CMD_USAGE); } if (pwfile_create_group(ctx, argv[1]) != 0) { fprintf(stderr, "group \"%s\" already exists\n", argv[1]); return (CMD_ERR); } return (CMD_OK); } static enum cmd_return cmd_removegroup(struct pwm_ctx *ctx, int argc, char *argv[]) { if (argc != 2) { return (CMD_USAGE); } if (pwfile_remove_group(ctx, argv[1]) != 0) { fprintf(stderr, "there is no empty group \"%s\"\n", argv[1]); return (CMD_ERR); } return (CMD_OK); } static enum cmd_return cmd_changepassword(struct pwm_ctx *ctx, int argc, char *argv[]) { size_t password_len; char password_buf[PWS3_MAX_PASSWORD_LEN + 1]; char confirm_buf[PWS3_MAX_PASSWORD_LEN + 1]; if (argc > 2) { return (CMD_USAGE); } else if (argc == 2) { password_len = strlen(argv[1]); if (password_len == 0) { fprintf(stderr, "password must not be empty\n"); return (CMD_ERR); } else if (password_len + 1 > sizeof (ctx->password)) { fprintf(stderr, "password too long\n"); return (CMD_ERR); } memcpy(ctx->password, argv[1], password_len + 1); } else { if (readpassphrase("Enter password: ", password_buf, sizeof (password_buf), RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) { err(1, "readpassphrase"); } password_len = strlen(password_buf); if (password_len == 0) { fprintf(stderr, "password must not be empty\n"); return (CMD_ERR); } if (readpassphrase("Confirm password: ", confirm_buf, sizeof (confirm_buf), RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) { err(1, "readpassphrase"); } if (strcmp(password_buf, confirm_buf) != 0) { fprintf(stderr, "passwords do not match\n"); return (CMD_ERR); } memcpy(ctx->password, password_buf, password_len + 1); } return (CMD_OK); } static enum cmd_return cmd_help(struct pwm_ctx *ctx, int argc, char *argv[]) { struct cmd *cmd; if (argc != 1) { return (CMD_USAGE); } printf("Commands:\n"); for (cmd = cmds; cmd->cmd_func != NULL; cmd++) { printf("%-2s %-16s %s\n", cmd->abbrev_cmd, cmd->full_cmd, cmd->description); } return (CMD_OK); } static enum cmd_return cmd_write(struct pwm_ctx *ctx, int argc, char *argv[]) { if (argc != 1) { return (CMD_USAGE); } return ((pwfile_write_file(ctx) == 0) ? CMD_OK : CMD_ERR); } static enum cmd_return cmd_quit(struct pwm_ctx *ctx, int argc, char *argv[]) { if (argc != 1) { return (CMD_USAGE); } return (CMD_QUIT); } struct cmd * cmd_match(const char *cmd_name) { size_t i; for (i = 0; cmds[i].cmd_func != NULL; i++) { if ((strcmp(cmds[i].abbrev_cmd, cmd_name) == 0) || (strcmp(cmds[i].full_cmd, cmd_name) == 0)) { return (&cmds[i]); } } return (NULL); }