Mercurial > projects > pwm
view cmd.c @ 40:e3ad9859c51d
Show only groups of matching entries when using ls with filters
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Fri, 09 Aug 2019 14:04:46 +0200 |
parents | 8b55f7b1c6b3 |
children | 0af8d2d8cd1a |
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" #include <ctype.h> #ifdef HAVE_ERR_H #include <err.h> #endif /* HAVE_ERR_H */ #include <errno.h> #include <limits.h> #include <regex.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include "cmd.h" #include "io.h" #include "macro.h" #include "pager.h" #include "proc.h" #include "pw.h" #include "pwfile.h" #include "tok.h" #include "util.h" #define TIME_FORMAT "%Y-%m-%dT%TZ" #define TIME_SIZE (4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1) #define CHARS_DIGIT "0123456789" #define CHARS_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" #define CHARS_LOWER "abcdefghijklmnopqrstuvwxyz" #define CHARS_PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" enum field_type { FIELD_UNKNOWN = -1, FIELD_GROUP, FIELD_TITLE, FIELD_USERNAME, FIELD_PASSWORD, FIELD_NOTES, FIELD_URL, FIELD_MTIME, FIELD_CTIME }; enum cmd_generatepassword_arg_type { CMD_GP_ARG_UNKNOWN = -1, CMD_GP_ARG_LEN, CMD_GP_ARG_CHARS, CMD_GP_ARG_CHARCLASS }; enum charclass_type { CHARCLASS_UNKNOWN = -1, CHARCLASS_DIGIT, CHARCLASS_UPPER, CHARCLASS_LOWER, CHARCLASS_PUNCT, CHARCLASS_ALPHA, CHARCLASS_ALNUM, CHARCLASS_XDIGIT, CHARCLASS_GRAPH }; enum option_type { OPTION_UNKNOWN = -1, OPTION_FILENAME, OPTION_PIPECOMMAND }; static enum cmd_return cmd_set(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_define(struct pwm_ctx *, int, char *[]); static enum cmd_return cmd_status(struct pwm_ctx *, int, char *[]); 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_generatepassword(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_namev[] = { "group", "title", "username", "password", "notes", "url", "ctime", "mtime", NULL }; static const char *field_labels[] = { "Group: ", "Title: ", "Username: ", "Password: ", "Notes: ", "URL: ", "Created: ", "Modified: " }; static const char *cmd_generatepassword_argv[] = { "len", "char", "charclass", NULL }; static const char *charclass_namev[] = { "digit", "upper", "lower", "punct", "alpha", "alnum", "xdigit", "graph", NULL }; static const char *charclass_values[] = { CHARS_DIGIT, CHARS_UPPER, CHARS_LOWER, CHARS_PUNCT, CHARS_UPPER CHARS_LOWER, CHARS_DIGIT CHARS_UPPER CHARS_LOWER, CHARS_DIGIT "abcdef", CHARS_DIGIT CHARS_UPPER CHARS_LOWER CHARS_PUNCT }; static const char *optionv[] = { "filename", "pipecommand", NULL }; static struct cmd cmds[] = { { "S", "set", "set [option=value]", "Set an option or show option values", cmd_set }, { "D", "define", "define name=value", "Define a macro", cmd_define }, { "t", "status", "status", "Redisplay an error message of the previous " "command and unsaved changes", cmd_status }, { "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 }, { "gp", "generatepassword", "generatepassword [id] [len=n] [chars=n:chars] " "[charclass=n:class] ...", "Randomly generate a password", cmd_generatepassword }, { "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 [password]", "changepassword", "Change password", cmd_changepassword }, { "h", "help", "help [command]", "Show help text", cmd_help }, { "w", "write", "write", "Write the database", cmd_write }, { "q", "quit", "quit", "Quit", cmd_quit }, { "Q", "Quit", "Quit", "Quit without checking", cmd_quit }, { 0 } }; static int parse_arg(char *arg, const char *namev[], int sep, char **valuep) { char *p; size_t i; if ((sep != '\0') && ((p = strchr(arg, sep)) != NULL)) { *p++ = '\0'; } if (valuep != NULL) { *valuep = p; } for (i = 0; namev[i] != NULL; i++) { if (strcmp(namev[i], arg) == 0) { return (i); } } return (-1); } 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 inline int filter_matches(regex_t *filter_re, const char *s) { if (filter_re == NULL) { return (1); } if (s == NULL) { return (0); } return (regexec(filter_re, s, 0, NULL, 0) == 0); } static enum cmd_return cmd_set(struct pwm_ctx *ctx, int argc, char *argv[]) { enum option_type type; char *value; if (argc == 1) { /* show options */ if ((io_printf("%s: %s\n", optionv[OPTION_FILENAME], ctx->filename) == IO_SIGNAL) || (io_printf("%s: %s\n", optionv[OPTION_PIPECOMMAND], (ctx->pipecmd != NULL) ? ctx->pipecmd : "") == IO_SIGNAL)) { return (CMD_SIGNAL); } return (CMD_OK); } else if (argc != 2) { return (CMD_USAGE); } type = parse_arg(argv[1], optionv, '=', &value); if ((type >= 0) && (value == NULL)) { pwm_err(ctx, "missing value for \"%s\"", argv[1]); return (CMD_ERR); } switch (type) { case OPTION_FILENAME: free(ctx->filename); ctx->filename = (value[0] != '\0') ? xstrdup(value) : xasprintf(&ctx->filename, "%s/pwm.psafe3", ctx->dirname); break; case OPTION_PIPECOMMAND: free(ctx->pipecmd); ctx->pipecmd = (value[0] != '\0') ? xstrdup(value) : NULL; break; default: pwm_err(ctx, "unknown option \"%s\"", argv[1]); return (CMD_ERR); } return (CMD_OK); } static enum cmd_return cmd_define(struct pwm_ctx *ctx, int argc, char *argv[]) { int retval = CMD_ERR; const char *value; char *name = NULL; size_t tokenc = 0; union tok **tokenv = NULL; struct macro_entry *macro_entry; if (argc != 2) { return (CMD_USAGE); } /* split into name and value */ value = strchr(argv[1], '='); if (value == NULL) { pwm_err(ctx, "bad macro definition \"%s\"", argv[1]); goto out; } xasprintf(&name, "%.*s", value - argv[1], argv[1]); value++; /* tokenize macro value */ switch (tok_tokenize(value, &tokenc, &tokenv)) { case TOK_ERR_UNTERMINATED_QUOTE: pwm_err(ctx, "unterminated quote in macro"); goto out; case TOK_ERR_TRAILING_BACKSLASH: pwm_err(ctx, "trailing backslash in macro"); goto out; case TOK_ERR_INVALID_MACRO_NAME: pwm_err(ctx, "invalid macro name referenced in macro"); goto out; case TOK_ERR_OK: break; } /* parse macro definition */ switch (macro_parse(name, tokenc, tokenv, ctx->macro_head, ¯o_entry)) { case MACRO_ERR_INVALID_NAME: pwm_err(ctx, "invalid macro name \"%s\"", name); goto out; case MACRO_ERR_UNDEFINED_MACRO: pwm_err(ctx, "macro definition references undefined macro"); goto out; case MACRO_ERR_RECURSIVE: pwm_err(ctx, "macro definition must not be recursive"); goto out; case MACRO_ERR_OK: break; } macro_add(ctx->macro_head, macro_entry); retval = CMD_OK; out: tok_free(tokenv); free(name); return (retval); } static enum cmd_return cmd_status(struct pwm_ctx *ctx, int argc, char *argv[]) { if (argc != 1) { return (CMD_USAGE); } if (ctx->errmsg != NULL) { if (io_printf("%s\n", ctx->errmsg) == IO_SIGNAL) { return (CMD_SIGNAL); } } if (ctx->is_readonly) { if (io_printf("Read-only mode\n") == IO_SIGNAL) { return (CMD_SIGNAL); } } else { if (io_printf("There are%sunsaved changes\n", ctx->unsaved_changes ? " " : " no ") == IO_SIGNAL) { return (CMD_SIGNAL); } } return (CMD_STATUS); } static enum cmd_return cmd_info(struct pwm_ctx *ctx, int argc, char *argv[]) { enum cmd_return retval; struct metadata *metadata; struct pager *pager; struct tm *tm; char timebuf[TIME_SIZE]; if (argc != 1) { return (CMD_USAGE); } metadata = pwfile_get_metadata(ctx); pager = pager_create(STDOUT_FILENO); pager_printf(pager, "Format: 0x%04x\n", metadata->version); if (metadata->user != NULL) { pager_printf(pager, "User: %s\n", metadata->user); } if (metadata->user != NULL) { pager_printf(pager, "Host: %s\n", metadata->host); } if (metadata->user != NULL) { pager_printf(pager, "Application: %s\n", metadata->application); } tm = gmtime(&metadata->timestamp); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); pager_printf(pager, "Last Saved: %s\n", timebuf); retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL; pager_destroy(pager); pwfile_destroy_metadata(metadata); return (retval); } 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; int is_filtered; struct pager *pager = NULL; union list_item **list = NULL; size_t j; const char *group = NULL; struct record *record; for (i = 1; i < argc; i++) { type = parse_arg(argv[i], field_namev, '~', &value); if ((type >= 0) && ((value == NULL) || (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: pwm_err(ctx, "unknown field name \"%s\"", 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); pwm_err(ctx, "bad regular expression \"%s\"", errbuf); free(errbuf); free(*repp); *repp = NULL; goto out; } } is_filtered = ((group_re != NULL) || (title_re != NULL) || (username_re != NULL) || (notes_re != NULL) || (url_re != NULL)); pager = pager_create(STDOUT_FILENO); list = pwfile_create_list(ctx); for (j = 0; list[j] != NULL; j++) { if (list[j]->any.type == ITEM_TYPE_GROUP) { group = list[j]->group.group; if (!is_filtered) { pager_printf(pager, "[%s]\n", group); } } else if (is_filtered) { record = pwfile_get_record(ctx, list[j]->record.id); if (filter_matches(group_re, record->group) && filter_matches(title_re, record->title) && filter_matches(username_re, record->username) && filter_matches(notes_re, record->notes) && filter_matches(url_re, record->url)) { if (group != NULL) { pager_printf(pager, "[%s]\n", group); group = NULL; } pager_printf(pager, "%4u %s\n", list[j]->record.id, (list[j]->record.title != NULL) ? list[j]->record.title : ""); } pwfile_destroy_record(record); } else { pager_printf(pager, "%4u %s\n", list[j]->record.id, (list[j]->record.title != NULL) ? list[j]->record.title : ""); } } retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL; out: pager_destroy(pager); 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 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; default: break; } } 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 = NULL; enum field_type type; char *value; if (!ctx->is_interactive && (argc < 2)) { retval = CMD_USAGE; goto out; } if (ctx->is_readonly) { pwm_err(ctx, "cannot create new entries in read-only mode"); goto out; } record = pwfile_create_record(); for (i = 1; i < argc; i++) { type = parse_arg(argv[i], field_namev, '=', &value); if ((type >= 0) && ((value == NULL) || (value[0] == '\0'))) { /* skip empty assignments */ continue; } switch (type) { case FIELD_GROUP: free(record->group); record->group = xstrdup(value); break; case FIELD_TITLE: free(record->title); record->title = xstrdup(value); break; case FIELD_USERNAME: free(record->username); record->username = xstrdup(value); break; case FIELD_PASSWORD: free(record->password); record->password = xstrdup(value); break; case FIELD_NOTES: free(record->notes); record->notes = xstrdup(value); break; case FIELD_URL: free(record->url); record->url = xstrdup(value); break; default: pwm_err(ctx, "unknown field name \"%s\"", argv[i]); goto out; } } if (ctx->is_interactive && (argc < 2)) { if (read_record_fields(ctx, record) != 0) { goto out; } } 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 = NULL; enum field_type type; char *value; 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]); goto out; } if (ctx->is_readonly) { pwm_err(ctx, "cannot modify entries in read-only mode"); goto out; } record = pwfile_get_record(ctx, id); for (i = 2; i < argc; i++) { type = parse_arg(argv[i], field_namev, '=', &value); if ((type >= 0) && ((value == NULL) || (value[0] == '\0'))) { /* skip empty assignments */ continue; } switch (type) { case FIELD_GROUP: free(record->group); record->group = xstrdup(value); break; case FIELD_TITLE: free(record->title); record->title = xstrdup(value); break; case FIELD_USERNAME: free(record->username); record->username = xstrdup(value); break; case FIELD_PASSWORD: free(record->password); record->password = xstrdup(value); break; case FIELD_NOTES: free(record->notes); record->notes = xstrdup(value); break; case FIELD_URL: free(record->url); record->url = xstrdup(value); break; default: pwm_err(ctx, "unknown field name \"%s\"", argv[i]); goto out; } } if (ctx->is_interactive && (argc < 3)) { if (read_record_fields(ctx, record) != 0) { goto out; } } pwfile_modify_pws_record(ctx, id, record); retval = CMD_OK; out: pwfile_destroy_record(record); return (retval); } static enum cmd_return cmd_generatepassword(struct pwm_ctx *ctx, int argc, char *argv[]) { enum cmd_return retval = CMD_ERR; unsigned int id = 0; int i = 1; enum cmd_generatepassword_arg_type type; char *value = NULL; long x; char *p; size_t password_len = 16; size_t chars_min; const char *chars; struct pw_char_group *char_groupv = NULL; size_t char_groupv_len = 0; int charclass; size_t j; char password[PWS3_MAX_PASSWORD_LEN + 1] = { 0 }; /* check if first argument is an id */ if ((argc > 1) && (parse_id(argv[1], &id) == 0)) { i++; if (ctx->is_readonly) { pwm_err(ctx, "cannot modify entries in read-only mode"); goto out; } } for (; i < argc; i++) { type = parse_arg(argv[i], cmd_generatepassword_argv, '=', &value); if ((type >= 0) && ((value == NULL) || (value[0] == '\0'))) { pwm_err(ctx, "invalid value for \"%s\"", argv[i]); retval = CMD_USAGE; goto out; } switch (type) { case CMD_GP_ARG_LEN: errno = 0; x = strtol(value, &p, 10); if ((errno != 0) || (*value == '\0') || (*p != '\0') || (x > PWS3_MAX_PASSWORD_LEN) || (x <= 0)) { pwm_err(ctx, "invalid password length \"%s\"", argv[i]); goto out; } password_len = x; break; case CMD_GP_ARG_CHARS: errno = 0; x = strtol(value, &p, 10); if ((errno != 0) || (*value == '\0') || (*p != ':') || (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) { pwm_err(ctx, "invalid minimum number of " "characters \"%s\"", argv[i]); goto out; } chars_min = x; chars = ++p; while (*p != '\0') { if (!isascii(*p) || !isprint(*p)) { pwm_err(ctx, "invalid character in " "character group \"%s\"", argv[i]); goto out; } p++; } char_groupv = xrealloc(char_groupv, sizeof (struct pw_char_group) * (char_groupv_len + 1)); char_groupv[char_groupv_len].chars = chars; char_groupv[char_groupv_len].chars_min = chars_min; char_groupv_len++; break; case CMD_GP_ARG_CHARCLASS: errno = 0; x = strtol(value, &p, 10); if ((errno != 0) || (*value == '\0') || (*p != ':') || (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) { pwm_err(ctx, "invalid minimum number of " "characters \"%s\"", argv[i]); goto out; } chars_min = x; charclass = parse_arg(++p, charclass_namev, '\0', NULL); if (charclass < 0) { pwm_err(ctx, "unknown character class \"%s\"", argv[i]); goto out; } chars = charclass_values[charclass]; char_groupv = xrealloc(char_groupv, sizeof (struct pw_char_group) * (char_groupv_len + 1)); char_groupv[char_groupv_len].chars = chars; char_groupv[char_groupv_len].chars_min = chars_min; char_groupv_len++; break; default: pwm_err(ctx, "invalid argument \"%s\"", argv[i]); retval = CMD_USAGE; goto out; } } for (j = 0; j < char_groupv_len; j++) { if (char_groupv[j].chars_min > password_len) { pwm_err(ctx, "invalid minimum number of characters " "\"%zu:%s\"", char_groupv[j].chars_min, char_groupv[j].chars); goto out; } } if (char_groupv_len == 0) { /* use defaults */ char_groupv = xmalloc(sizeof (struct pw_char_group)); char_groupv[0].chars = charclass_values[CHARCLASS_GRAPH]; char_groupv[0].chars_min = 0; char_groupv_len++; } if (pw_genrandom(char_groupv, char_groupv_len, password, password_len) != 0) { pwm_err(ctx, "failed to generate password that meets the given " "constraints"); goto out; } if (id != 0) { if (pwfile_modify_pws_record(ctx, id, &(struct record){ .password = password }) != 0) { pwm_err(ctx, "record %u does not exist", id); goto out; } retval = CMD_OK; } else { retval = io_printf("%s\n", password); } out: free(char_groupv); return (retval); } 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) { pwm_err(ctx, "invalid id %s", argv[1]); return (CMD_ERR); } if (ctx->is_readonly) { pwm_err(ctx, "cannot remove entries in read-only mode"); return (CMD_ERR); } if (pwfile_remove_pws_record(ctx, id) != 0) { pwm_err(ctx, "failed to remove record %u", id); return (CMD_ERR); } return (CMD_OK); } static int print_record(struct record *record, int fields[], int show_labels, int fd) { struct pager *pager; struct tm *tm; char timebuf[TIME_SIZE]; int retval; pager = pager_create(fd); if (fields[FIELD_TITLE]) { pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_TITLE] : "", (record->title != NULL) ? record->title : ""); } if (fields[FIELD_GROUP]) { pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_GROUP] : "", (record->group != NULL) ? record->group : ""); } if (fields[FIELD_USERNAME]) { pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_USERNAME] : "", (record->username != NULL) ? record->username : ""); } if (fields[FIELD_PASSWORD]) { pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_PASSWORD] : "", (record->password != NULL) ? record->password : ""); } if (fields[FIELD_NOTES]) { pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_NOTES] : "", (record->notes != NULL) ? record->notes : ""); } if (fields[FIELD_URL]) { pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_URL] : "", (record->url != NULL) ? record->url : ""); } if (fields[FIELD_CTIME]) { tm = gmtime(&record->ctime); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_CTIME] : "", timebuf); } if (fields[FIELD_MTIME]) { tm = gmtime(&record->mtime); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); pager_printf(pager, "%s%s\n", show_labels ? field_labels[FIELD_MTIME] : "", timebuf); } retval = pager_show(pager); pager_destroy(pager); return (retval); } static enum cmd_return cmd_show(struct pwm_ctx *ctx, int argc, char *argv[]) { enum cmd_return retval; unsigned int id; struct record *record; int i; enum field_type type; int fields[COUNTOF(field_namev) - 1] = { [FIELD_GROUP] = 1, [FIELD_TITLE] = 1, [FIELD_USERNAME] = 1, [FIELD_PASSWORD] = 0, [FIELD_NOTES] = 1, [FIELD_URL] = 1, [FIELD_MTIME] = 1, [FIELD_CTIME] = 1 }; if (argc < 2) { return (CMD_USAGE); } if (parse_id(argv[1], &id) != 0) { pwm_err(ctx, "invalid id %s", argv[1]); return (CMD_ERR); } if (argc > 2) { /* show only explicitly given field names */ memset(fields, 0, sizeof (fields)); } for (i = 2; i < argc; i++) { type = parse_arg(argv[i], field_namev, '\0', NULL); if (type < 0) { pwm_err(ctx, "unknown field name \"%s\"", argv[i]); return (CMD_ERR); } fields[type] = 1; } record = pwfile_get_record(ctx, id); if (record == NULL) { pwm_err(ctx, "record %u does not exist", id); return (CMD_ERR); } retval = (print_record(record, fields, 1, STDOUT_FILENO) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL; pwfile_destroy_record(record); return (retval); } 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_namev) - 1] = { 0 }; char *cmd; struct proc proc = { 0 }; /* if pipecommand is set, the last argument is optional */ if (((ctx->pipecmd == NULL) && (argc != 4)) || ((ctx->pipecmd != NULL) && ((argc < 3) || (argc > 4)))) { return (CMD_USAGE); } if (parse_id(argv[1], &id) != 0) { pwm_err(ctx, "invalid id %s", argv[1]); return (CMD_ERR); } type = parse_arg(argv[2], field_namev, '\0', NULL); if (type < 0) { pwm_err(ctx, "unknown field name \"%s\"", argv[2]); return (CMD_ERR); } fields[type] = 1; cmd = (argc == 4) ? argv[3] : ctx->pipecmd; if (proc_open(&proc, cmd, "w") != IO_OK) { goto out; } record = pwfile_get_record(ctx, id); if (record == NULL) { pwm_err(ctx, "record %u does not exist", id); goto out; } retval = (print_record(record, fields, 0, proc.fd) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL; out: pwfile_destroy_record(record); if (proc.pid != 0) { if (proc_close(&proc) == IO_SIGNAL) { retval = CMD_SIGNAL; } } return (retval); } static enum cmd_return cmd_creategroup(struct pwm_ctx *ctx, int argc, char *argv[]) { char group_buf[PWM_LINE_MAX] = { '\0' }; if (!ctx->is_interactive && (argc != 2)) { return (CMD_USAGE); } if (ctx->is_readonly) { pwm_err(ctx, "cannot create groups in read-only mode"); 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); } static enum cmd_return cmd_removegroup(struct pwm_ctx *ctx, int argc, char *argv[]) { if (argc != 2) { return (CMD_USAGE); } if (ctx->is_readonly) { pwm_err(ctx, "cannot remove groups in read-only mode"); return (CMD_ERR); } if (pwfile_remove_group(ctx, argv[1]) != 0) { pwm_err(ctx, "empty group \"%s\" does not exist", argv[1]); return (CMD_ERR); } return (CMD_OK); } static enum cmd_return cmd_changepassword(struct pwm_ctx *ctx, int argc, char *argv[]) { size_t len; if (argc > 2) { return (CMD_USAGE); } if (ctx->is_readonly) { pwm_err(ctx, "cannot modify entries in read-only mode"); return (CMD_ERR); } if (argc == 2) { len = strlen(argv[1]); if (len == 0) { pwm_err(ctx, "password must not be empty"); return (CMD_ERR); } else if (len + 1 > sizeof (ctx->password)) { pwm_err(ctx, "password too long"); return (CMD_ERR); } memcpy(ctx->password, argv[1], len + 1); } else { if (pwm_read_password(ctx, 1) != 0) { return (CMD_ERR); } } return (CMD_OK); } static enum cmd_return cmd_help(struct pwm_ctx *ctx, int argc, char *argv[]) { enum cmd_return retval = CMD_OK; struct pager *pager; struct cmd *cmd; if (argc > 2) { return (CMD_USAGE); } if (argc == 2) { for (cmd = cmds; cmd->cmd_func != NULL; cmd++) { if ((strcmp(argv[1], cmd->abbrev_cmd) == 0) || (strcmp(argv[1], cmd->full_cmd) == 0)) { if (io_printf("usage: %s\n", cmd->usage) == IO_SIGNAL) { retval = CMD_SIGNAL; } break; } } } else { pager = pager_create(STDOUT_FILENO); pager_printf(pager, "Commands:\n"); for (cmd = cmds; cmd->cmd_func != NULL; cmd++) { pager_printf(pager, "%-2s %-16s %s\n", cmd->abbrev_cmd, cmd->full_cmd, cmd->description); } retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL; pager_destroy(pager); } return (retval); } static enum cmd_return cmd_write(struct pwm_ctx *ctx, int argc, char *argv[]) { if (argc != 1) { return (CMD_USAGE); } if (ctx->is_readonly) { pwm_err(ctx, "cannot write changes in read-only mode"); return (CMD_ERR); } 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); } if ((argv[0][0] == 'q') && ctx->unsaved_changes && (ctx->prev_cmd != NULL) && (strcmp(ctx->prev_cmd, "quit") != 0)) { printf("Warning: There are unsaved changes\n"); return (CMD_OK); } 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); }