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);
+}