changeset 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
files Makefile cmd.c pw.c pw.h pwm.1.xml rand-arc4random.c rand-dev-random.c rand-getrandom.c rand.h
diffstat 9 files changed, 676 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Mon Jul 31 09:20:21 2017 +0200
+++ b/Makefile	Thu Aug 03 10:22:07 2017 +0200
@@ -77,49 +77,64 @@
 OS_RELEASE :=	$(shell uname -r)
 
 ifeq ($(OS_NAME),Linux)
+  HAVE_ARC4RANDOM ?=	0
   HAVE_ASPRINTF ?=	1
   HAVE_ERR_H ?=		1
+  HAVE_GETRANDOM ?=	0
   HAVE_READPASSPHRASE_H ?= 0
   HAVE_SETPROGNAME ?=	0
   HAVE_SYS_TREE_H ?=	0
 else ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly),)
+  HAVE_ARC4RANDOM ?=	1
   HAVE_ASPRINTF ?=	1
   HAVE_ERR_H ?=		1
+  HAVE_GETRANDOM ?=	0
   HAVE_READPASSPHRASE_H ?= 1
   HAVE_SETPROGNAME ?=	1
   HAVE_SYS_TREE_H ?=	1
 else ifeq ($(OS_NAME),NetBSD)
+  HAVE_ARC4RANDOM ?=	1
   HAVE_ASPRINTF ?=	1
   HAVE_ERR_H ?=		1
+  HAVE_GETRANDOM ?=	0
   HAVE_READPASSPHRASE_H ?= 0
   HAVE_SYS_TREE_H ?=	1
   HAVE_SETPROGNAME ?=	1
 else ifeq ($(OS_NAME),OpenBSD)
+  HAVE_ARC4RANDOM ?=	1
   HAVE_ASPRINTF ?=	1
   HAVE_ERR_H ?=		1
+  HAVE_GETRANDOM ?=	0
   HAVE_READPASSPHRASE_H ?= 1
   HAVE_SYS_TREE_H ?=	1
   HAVE_SETPROGNAME ?=	1
 else ifeq ($(OS_NAME),SunOS)
   ifeq ($(OS_RELEASE),5.10)
+    HAVE_ARC4RANDOM ?=	0
     HAVE_ASPRINTF ?=	0
     HAVE_ERR_H ?=	0
+    HAVE_GETRANDOM ?=	0
   else
+    HAVE_ARC4RANDOM ?=	1
     HAVE_ASPRINTF ?=	1
     HAVE_ERR_H ?=	1
+    HAVE_GETRANDOM ?=	1
   endif
   HAVE_READPASSPHRASE_H ?= 0
   HAVE_SYS_TREE_H ?=	0
   HAVE_SETPROGNAME ?=	0
 else
+  HAVE_ARC4RANDOM ?=	0
   HAVE_ASPRINTF ?=	0
   HAVE_ERR_H ?=		0
+  HAVE_GETRANDOM ?=	0
   HAVE_READPASSPHRASE_H ?= 0
   HAVE_SETPROGNAME ?=	0
   HAVE_SYS_TREE_H ?=	0
 endif
 
 OBJS =	cmd.o \
+	pw.o \
 	pwfile.o \
 	pwm.o \
 	tok.o \
@@ -141,6 +156,15 @@
 else
   OBJS +=	compat/asprintf.o
 endif
+ifeq ($(HAVE_ARC4RANDOM),1)
+  XCPPFLAGS +=	-DHAVE_ARC4RANDOM
+  OBJS +=	rand-arc4random.o
+else ifeq ($(HAVE_GETRANDOM),1)
+  XCPPFLAGS +=	-DHAVE_GETRANDOM
+  OBJS +=	rand-getrandom.o
+else
+  OBJS +=	rand-dev-random.o
+endif
 ifeq ($(HAVE_ERR_H),1)
   XCPPFLAGS +=	-DHAVE_ERR_H
 else
--- 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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pw.c	Thu Aug 03 10:22:07 2017 +0200
@@ -0,0 +1,100 @@
+/*
+ * 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"
+
+#ifdef	HAVE_ERR_H
+#include <err.h>
+#endif /* HAVE_ERR_H */
+#include <pws.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "pw.h"
+#include "rand.h"
+#include "util.h"
+
+int
+pw_genrandom(struct pw_char_group groups[], size_t groups_len, char *password,
+    size_t password_len)
+{
+	int	retval = -1;
+	char	*password_buf = NULL;
+	size_t	*group_matches = NULL;
+	size_t	i;
+	size_t	chars_len = 0;
+	char	*chars;
+	size_t	j;
+	uint32_t r;
+	size_t	k;
+
+	password_buf = xmalloc(password_len + 1);
+	password_buf[password_len] = '\0';
+
+	group_matches = xmalloc(groups_len * sizeof (size_t));
+
+	for (i = 0; i < groups_len; i++) {
+		chars_len += strlen(groups[i].chars);
+	}
+
+	chars = xmalloc(chars_len + 1);
+	chars[0] = '\0';
+	for (i = 0; i < groups_len; i++) {
+		strcat(chars, groups[i].chars);
+	}
+
+	for (k = 0; k < 100000; k++) {
+		memset(group_matches, 0, groups_len * sizeof (size_t));
+
+		for (j = 0; j < password_len; j++) {
+			r = rand_uniform(chars_len);
+			password_buf[j] = chars[r];
+
+			for (i = 0; i < groups_len; i++) {
+				if (strchr(groups[i].chars, chars[r]) != NULL) {
+					group_matches[i]++;
+					break;
+				}
+			}
+		}
+
+		for (i = 0; i < groups_len; i++) {
+			if (group_matches[i] < groups[i].chars_min) {
+				/* try again */
+				break;
+			}
+		}
+		if (i == groups_len) {
+			/* password meets all constraints */
+			strcpy(password, password_buf);
+			retval = 0;
+			break;
+		}
+	}
+
+	free(chars);
+	free(group_matches);
+	free(password_buf);
+
+	return (retval);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pw.h	Thu Aug 03 10:22:07 2017 +0200
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef	PW_H
+#define	PW_H
+
+struct pw_char_group {
+	const char	*chars;
+	size_t		chars_min;
+};
+
+int	pw_genrandom(struct pw_char_group [], size_t, char *, size_t);
+
+#endif /* !PW_H */
--- a/pwm.1.xml	Mon Jul 31 09:20:21 2017 +0200
+++ b/pwm.1.xml	Thu Aug 03 10:22:07 2017 +0200
@@ -34,7 +34,7 @@
       <email>guido+pwm@berhoerster.name</email>
       <personblurb/>
     </author>
-    <date>31 July, 2017</date>
+    <date>3 August, 2017</date>
   </info>
   <refmeta>
     <refentrytitle>pwm</refentrytitle>
@@ -393,6 +393,54 @@
           </listitem>
         </varlistentry>
         <varlistentry>
+          <term>Generate a random password</term>
+          <listitem>
+            <cmdsynopsis>
+              <command>generatepassword</command>
+              <arg choice="opt">
+                <replaceable>id</replaceable>
+              </arg>
+              <arg choice="opt">
+                len=<replaceable>n</replaceable>
+              </arg>
+              <arg choice="opt" rep="repeat">
+                chars=<replaceable>n</replaceable>:<replaceable>chars</replaceable>
+              </arg>
+              <arg choice="opt" rep="repeat">
+                charclass=<replaceable>n</replaceable>:<replaceable>class</replaceable>
+              </arg>
+            </cmdsynopsis>
+            <cmdsynopsis>
+              <command>gp</command>
+              <arg choice="opt">
+                <replaceable>id</replaceable>
+              </arg>
+              <arg choice="opt">
+                len=<replaceable>n</replaceable>
+              </arg>
+              <arg choice="opt" rep="repeat">
+                chars=<replaceable>n</replaceable>:<replaceable>chars</replaceable>
+              </arg>
+              <arg choice="opt" rep="repeat">
+                charclass=<replaceable>n</replaceable>:<replaceable>class</replaceable>
+              </arg>
+              <sbr/>
+            </cmdsynopsis>
+            <para>Randomly generate a new password according to the specified
+            constraints. The <literal>len</literal> argument sets the length of
+            the generated password to <replaceable>n</replaceable> characters.
+            The <literal>chars</literal> argument constrains the password to
+            <replaceable>n</replaceable> from the set of characters
+            <replaceable>chars</replaceable>. Similarly, the
+            <literal>charclass</literal> argument to
+            <replaceable>n</replaceable> characters from the extended regular
+            expression character class <replaceable>class</replaceable>.
+            Multiple <literal>char</literal> and <literal>charclass</literal>
+            arguments may be specified, in which case the generated passwords
+            match all of them.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
           <term>Change the master password</term>
           <listitem>
             <cmdsynopsis>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rand-arc4random.c	Thu Aug 03 10:22:07 2017 +0200
@@ -0,0 +1,44 @@
+/*
+ * 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 <stdlib.h>
+
+#include "rand.h"
+
+void
+rand_buf(void *buf, size_t buf_size)
+{
+	arc4random_buf(buf, buf_size);
+}
+
+uint32_t
+rand_random(void)
+{
+	return (arc4random());
+}
+
+uint32_t
+rand_uniform(uint32_t upper_bound)
+{
+	return (arc4random_uniform(upper_bound));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rand-dev-random.c	Thu Aug 03 10:22:07 2017 +0200
@@ -0,0 +1,95 @@
+/*
+ * 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"
+
+#ifdef	HAVE_ERR_H
+#include <err.h>
+#endif /* HAVE_ERR_H */
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "rand.h"
+
+#ifdef	__linux__
+#define	PATH_DEV_RANDOM	"/dev/urandom"
+#else
+#define	PATH_DEV_RANDOM	"/dev/random"
+#endif /* __linux__ */
+
+void
+rand_buf(void *buf, size_t buf_size)
+{
+	unsigned char	*p = buf;
+	int		fd;
+	ssize_t		nread;
+	size_t		nsize = buf_size;
+
+	do {
+		fd = open(PATH_DEV_RANDOM, O_RDONLY);
+	} while ((fd < 0) && (errno == EINTR));
+	if (fd < 0) {
+		err(1, "open");
+	}
+
+	while (nsize > 0) {
+		nread = read(fd, p, nsize);
+		if (nread < 0) {
+			if (errno == EINTR) {
+				continue;
+			}
+			err(1, "read");
+		}
+		p += nread;
+		nsize -= nread;
+	}
+
+	while ((close(fd) < 0) && (errno == EINTR));
+}
+
+uint32_t
+rand_random(void)
+{
+	uint32_t	x;
+
+	rand_buf(&x, sizeof (x));
+
+	return (x);
+}
+
+/* random number between 0 and upper_bound - 1 without modulo bias */
+uint32_t
+rand_uniform(uint32_t upper_bound)
+{
+	uint32_t	r;
+	/* (2^32 - upper_bound) % upper_bound */
+	uint32_t	threshold = -upper_bound % upper_bound;
+
+	do {
+		r = rand_random();
+	} while (r < threshold);
+
+	return (r % upper_bound);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rand-getrandom.c	Thu Aug 03 10:22:07 2017 +0200
@@ -0,0 +1,77 @@
+/*
+ * 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 <stdlib.h>
+/* glibc and Solaris 11 */
+#include <sys/random.h>
+
+#include "rand.h"
+
+#define	RANDOM_CHUNK	256U
+
+void
+rand_buf(void *buf, size_t buf_size)
+{
+	unsigned char	*p = buf;
+	ssize_t		nread;
+	size_t		nsize = buf_size;
+
+	while (nsize > 0) {
+		nread = getrandom(p, nsize, 0);
+		if (nread < 0) {
+			if (errno == EINTR) {
+				continue;
+			}
+			err(1, "read");
+		}
+		nsize -= nread;
+		p += nread;
+	}
+}
+
+uint32_t
+rand_random(void)
+{
+	uint32_t	r;
+
+	getrandom(r, sizeof (uint32_t), 0);
+
+	return (r);
+}
+
+/* random number between 0 and upper_bound - 1 without modulo bias */
+uint32_t
+rand_uniform(uint32_t upper_bound)
+{
+	uint32_t	r;
+	/* (2^32 - upper_bound) % upper_bound */
+	uint32_t	threshold = -upper_bound % upper_bound;
+
+	do {
+		r = rand_random();
+	} while (r < threshold);
+
+	return (r % upper_bound);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rand.h	Thu Aug 03 10:22:07 2017 +0200
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef	RAND_H
+#define	RAND_H
+
+#include <stdint.h>
+
+void		rand_buf(void *, size_t);
+uint32_t	rand_random(void);
+uint32_t	rand_uniform(uint32_t);
+
+#endif /* !RAND_H */