diff cmd.c @ 12:8768fbd09bc5

Add generatepassword command to generate random passwords Refactor and generalize handling of named arguments.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Thu, 03 Aug 2017 10:22:07 +0200
parents 85bce13237cf
children cf81eb0c2d5a
line wrap: on
line diff
--- a/cmd.c	Mon Jul 31 09:20:21 2017 +0200
+++ b/cmd.c	Thu Aug 03 10:22:07 2017 +0200
@@ -23,6 +23,7 @@
 
 #include "compat.h"
 
+#include <ctype.h>
 #ifdef	HAVE_ERR_H
 #include <err.h>
 #endif /* HAVE_ERR_H */
@@ -38,12 +39,18 @@
 #include <unistd.h>
 
 #include "cmd.h"
+#include "pw.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)
 
+#define	CHARS_DIGIT	"0123456789"
+#define	CHARS_UPPER	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define	CHARS_LOWER	"abcdefghijklmnopqrstuvwxyz"
+#define	CHARS_PUNCT	"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+
 enum field_type {
 	FIELD_UNKNOWN = -1,
 	FIELD_GROUP,
@@ -56,10 +63,30 @@
 	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
+};
+
 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 *[]);
@@ -70,7 +97,7 @@
 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[] = {
+static const char *field_namev[] = {
     "group",
     "title",
     "username",
@@ -78,7 +105,8 @@
     "notes",
     "url",
     "ctime",
-    "mtime"
+    "mtime",
+    NULL
 };
 
 static const char *field_labels[] = {
@@ -92,12 +120,45 @@
     "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 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 },
+    { "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",
@@ -114,24 +175,24 @@
     { 0 }
 };
 
-static enum field_type
-parse_field(char *field_arg, int sep, char **valuep)
+static int
+parse_arg(char *arg, const char *namev[], int sep, char **valuep)
 {
-	int	i;
-	size_t	field_name_len;
+	size_t	i;
+	size_t	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)) {
+	for (i = 0; namev[i] != NULL; i++) {
+		name_len = strlen(namev[i]);
+		if ((strncmp(namev[i], arg, name_len) == 0) &&
+		    (arg[name_len] == sep)) {
 			if (valuep != NULL) {
-				*valuep = field_arg + field_name_len + 1;
+				*valuep = arg + name_len + 1;
 			}
 			return (i);
 		}
 	}
 
-	return (FIELD_UNKNOWN);
+	return (-1);
 }
 
 static int
@@ -203,7 +264,7 @@
 	struct record	*record;
 
 	for (i = 1; i < argc; i++) {
-		type = parse_field(argv[i], '~', &value);
+		type = parse_arg(argv[i], field_namev, '~', &value);
 		if (type == FIELD_UNKNOWN) {
 			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
 			goto out;
@@ -319,7 +380,7 @@
 	}
 
 	for (i = 1; i < argc; i++) {
-		type = parse_field(argv[i], '=', &value);
+		type = parse_arg(argv[i], field_namev, '=', &value);
 		if (type == FIELD_UNKNOWN) {
 			fprintf(stderr, "bad field assignment \"%s\"\n",
 			    argv[i]);
@@ -377,7 +438,7 @@
 	}
 
 	for (i = 2; i < argc; i++) {
-		type = parse_field(argv[i], '=', &value);
+		type = parse_arg(argv[i], field_namev, '=', &value);
 		if (type == FIELD_UNKNOWN) {
 			fprintf(stderr, "bad field assignment \"%s\"\n",
 			    argv[i]);
@@ -418,6 +479,146 @@
 }
 
 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;
+	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++;
+	}
+
+	for (; i < argc; i++) {
+		switch (parse_arg(argv[i], cmd_generatepassword_argv, '=',
+		    &value)) {
+		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)) {
+				fprintf(stderr, "invalid password length "
+				    "\"%s\"\n", 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)) {
+				fprintf(stderr, "invalid minimum number of "
+				    "characters \"%s\"\n", argv[i]);
+				goto out;
+			}
+			chars_min = x;
+
+			chars = ++p;
+			while (*p != '\0') {
+				if (!isascii(*p) || !isprint(*p)) {
+					fprintf(stderr, "invalid character in "
+					    "character group \"%s\"\n",
+					    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)) {
+				fprintf(stderr, "invalid minimum number of "
+				    "characters \"%s\"\n", argv[i]);
+				goto out;
+			}
+			chars_min = x;
+
+			charclass = parse_arg(++p, charclass_namev, '\0', NULL);
+			if (charclass < 0) {
+				fprintf(stderr, "unknown character class "
+				    "\"%s\"\n", 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:
+			fprintf(stderr, "invalid argument \"%s\"\n", argv[i]);
+			retval = CMD_USAGE;
+			goto out;
+		}
+	}
+
+	for (j = 0; j < char_groupv_len; j++) {
+		if (char_groupv[j].chars_min > password_len) {
+			fprintf(stderr, "invalid minimum number of "
+			    "characters \"%zu:%s\"\n", 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) {
+		fprintf(stderr, "failed to generate password that meets the "
+		    "given constraints\n");
+		goto out;
+	}
+
+	if (id != 0) {
+		if (pwfile_modify_record(ctx, id,
+		    &(struct record){ .password = password }) != 0) {
+			fprintf(stderr, "record %u does not exist\n", id);
+			goto out;
+		}
+	} else {
+		printf("%s\n", password);
+	}
+
+	retval = CMD_OK;
+
+out:
+	free(char_groupv);
+
+	return (retval);
+}
+
+static enum cmd_return
 cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[])
 {
 	unsigned int	id;
@@ -518,7 +719,7 @@
 	struct record	*record;
 	int		i;
 	enum field_type	type;
-	int		fields[COUNTOF(field_names)] = { 0 };
+	int		fields[COUNTOF(field_namev) - 1] = { 0 };
 
 	if (argc < 2) {
 		return (CMD_USAGE);
@@ -530,7 +731,7 @@
 	}
 
 	for (i = 2; i < argc; i++) {
-		type = parse_field(argv[i], '\0', NULL);
+		type = parse_arg(argv[i], field_namev, '\0', NULL);
 		if (type < 0) {
 			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
 			return (CMD_ERR);
@@ -556,7 +757,7 @@
 	unsigned int	id;
 	struct record	*record = NULL;
 	enum field_type	type;
-	int		fields[COUNTOF(field_names)] = { 0 };
+	int		fields[COUNTOF(field_namev) - 1] = { 0 };
 	FILE		*fp = NULL;
 
 	if (argc != 4) {
@@ -568,7 +769,7 @@
 		return (CMD_ERR);
 	}
 
-	type = parse_field(argv[2], '\0', NULL);
+	type = parse_arg(argv[2], field_namev, '\0', NULL);
 	if (type < 0) {
 		fprintf(stderr, "bad field name \"%s\"\n", argv[2]);
 		return (CMD_ERR);