changeset 22:ec01c579024a

Add fully interactive mode
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Thu, 07 Sep 2017 12:40:50 +0200
parents ee4d36c85287
children 1b89066d992c
files cmd.c io.c io.h pwfile.c pwfile.h pwm.1.xml pwm.c pwm.h
diffstat 8 files changed, 222 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- 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);
--- 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)
--- 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 *);
--- 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;
--- 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 *);
 
--- 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 @@
       <email>guido+pwm@berhoerster.name</email>
       <personblurb/>
     </author>
-    <date>6 September, 2017</date>
+    <date>7 September, 2017</date>
   </info>
   <refmeta>
     <refentrytitle>pwm</refentrytitle>
@@ -240,6 +240,9 @@
             <para>Create a new entry assigning each given
             <replaceable>field</replaceable> to the corresponsing
             <replaceable>value</replaceable>.</para>
+            <para>If no fields are specified in interactive mode,
+            <command>pwm</command> will prompt the user for the content of
+            each field.</para>
           </listitem>
         </varlistentry>
         <varlistentry>
@@ -268,6 +271,10 @@
             <replaceable>id</replaceable> assigning each given
             <replaceable>field</replaceable> to the corresponsing
             <replaceable>value</replaceable>.</para>
+            <para>If no fields are specified and <command>pwm</command> is
+            running in interactive mode, it will prompt the user for the
+            content of each field, allowing him to edit any previous
+            content.</para>
           </listitem>
         </varlistentry>
         <varlistentry>
@@ -374,6 +381,9 @@
             </cmdsynopsis>
             <para>Create a new empty group named
             <replaceable>name</replaceable>.</para>
+            <para>In interactive-mode the <replaceable>name</replaceable>
+            argument is optional, if it is not specified <command>pwm</command>
+            will prompt the user for it.</para>
           </listitem>
         </varlistentry>
         <varlistentry>
--- 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);
--- 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;