Mercurial > projects > pwm
diff cmd.c @ 0:a7e41e1a79c8
Initial revision
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Thu, 19 Jan 2017 22:39:51 +0100 |
parents | |
children | b5c4267a7182 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd.c Thu Jan 19 22:39:51 2017 +0100 @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2016 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 <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "cmd.h" +#include "pwfile.h" +#include "util.h" + +enum field_type { + FIELD_UNKNOWN = -1, + FIELD_GROUP, + FIELD_TITLE, + FIELD_USERNAME, + FIELD_PASSWORD, + FIELD_NOTES, + FIELD_URL +}; + +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" +}; + +static const char *field_labels[] = { + "Group: ", + "Title: ", + "Username: ", + "Password: ", + "Notes: ", + "URL: " +}; + +static struct cmd cmds[] = { + { "ls", "list", "list", "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_name(const char *name) +{ + int i; + + for (i = 0; i < (int)COUNTOF(field_names); i++) { + if (strcmp(field_names[i], name) == 0) { + return (i); + } + } + + return (FIELD_UNKNOWN); +} + +static enum field_type +parse_field_assignment(char *arg, struct record *record) +{ + int i; + size_t field_name_len; + char *value; + + for (i = 0; i < (int)COUNTOF(field_names); i++) { + field_name_len = strlen(field_names[i]); + if ((strncmp(field_names[i], arg, field_name_len) == 0) && + (arg[field_name_len] == '=')){ + value = arg + field_name_len + 1; + if (*value == '\0') { + /* skip empty assignments */ + return (i); + } + switch (i) { + 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: + break; + } + 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_list(struct pwm_ctx *ctx, int argc, char *argv[]) +{ + union list_item **list; + size_t i; + + if (argc != 1) { + return (CMD_USAGE); + } + + list = pwfile_create_list(ctx); + for (i = 0; list[i] != NULL; i++) { + if (list[i]->any.type == ITEM_TYPE_GROUP) { + printf("[%s]\n", list[i]->group.group); + } else { + printf("%4u %s\n", list[i]->record.id, + (list[i]->record.title != NULL) ? + list[i]->record.title : ""); + } + } + pwfile_destroy_list(list); + + return (CMD_OK); +} + +static enum cmd_return +cmd_create(struct pwm_ctx *ctx, int argc, char *argv[]) +{ + int i; + struct record record = { 0 }; + + if (argc < 2) { + return (CMD_USAGE); + } + + for (i = 1; i < argc; i++) { + if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) { + fprintf(stderr, "bad field assignment \"%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 }; + + 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++) { + if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) { + fprintf(stderr, "bad field assignment \"%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) +{ + 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; + } + } +} + +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_name(argv[i]); + 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_name(argv[2]); + 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, "group \"%s\" does not exist\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); +}