projects/pwm

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 Aug 03 10:22:07 2017 +0200 (2017-08-03)
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
line diff
     1.1 --- a/Makefile	Mon Jul 31 09:20:21 2017 +0200
     1.2 +++ b/Makefile	Thu Aug 03 10:22:07 2017 +0200
     1.3 @@ -77,49 +77,64 @@
     1.4  OS_RELEASE :=	$(shell uname -r)
     1.5  
     1.6  ifeq ($(OS_NAME),Linux)
     1.7 +  HAVE_ARC4RANDOM ?=	0
     1.8    HAVE_ASPRINTF ?=	1
     1.9    HAVE_ERR_H ?=		1
    1.10 +  HAVE_GETRANDOM ?=	0
    1.11    HAVE_READPASSPHRASE_H ?= 0
    1.12    HAVE_SETPROGNAME ?=	0
    1.13    HAVE_SYS_TREE_H ?=	0
    1.14  else ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly),)
    1.15 +  HAVE_ARC4RANDOM ?=	1
    1.16    HAVE_ASPRINTF ?=	1
    1.17    HAVE_ERR_H ?=		1
    1.18 +  HAVE_GETRANDOM ?=	0
    1.19    HAVE_READPASSPHRASE_H ?= 1
    1.20    HAVE_SETPROGNAME ?=	1
    1.21    HAVE_SYS_TREE_H ?=	1
    1.22  else ifeq ($(OS_NAME),NetBSD)
    1.23 +  HAVE_ARC4RANDOM ?=	1
    1.24    HAVE_ASPRINTF ?=	1
    1.25    HAVE_ERR_H ?=		1
    1.26 +  HAVE_GETRANDOM ?=	0
    1.27    HAVE_READPASSPHRASE_H ?= 0
    1.28    HAVE_SYS_TREE_H ?=	1
    1.29    HAVE_SETPROGNAME ?=	1
    1.30  else ifeq ($(OS_NAME),OpenBSD)
    1.31 +  HAVE_ARC4RANDOM ?=	1
    1.32    HAVE_ASPRINTF ?=	1
    1.33    HAVE_ERR_H ?=		1
    1.34 +  HAVE_GETRANDOM ?=	0
    1.35    HAVE_READPASSPHRASE_H ?= 1
    1.36    HAVE_SYS_TREE_H ?=	1
    1.37    HAVE_SETPROGNAME ?=	1
    1.38  else ifeq ($(OS_NAME),SunOS)
    1.39    ifeq ($(OS_RELEASE),5.10)
    1.40 +    HAVE_ARC4RANDOM ?=	0
    1.41      HAVE_ASPRINTF ?=	0
    1.42      HAVE_ERR_H ?=	0
    1.43 +    HAVE_GETRANDOM ?=	0
    1.44    else
    1.45 +    HAVE_ARC4RANDOM ?=	1
    1.46      HAVE_ASPRINTF ?=	1
    1.47      HAVE_ERR_H ?=	1
    1.48 +    HAVE_GETRANDOM ?=	1
    1.49    endif
    1.50    HAVE_READPASSPHRASE_H ?= 0
    1.51    HAVE_SYS_TREE_H ?=	0
    1.52    HAVE_SETPROGNAME ?=	0
    1.53  else
    1.54 +  HAVE_ARC4RANDOM ?=	0
    1.55    HAVE_ASPRINTF ?=	0
    1.56    HAVE_ERR_H ?=		0
    1.57 +  HAVE_GETRANDOM ?=	0
    1.58    HAVE_READPASSPHRASE_H ?= 0
    1.59    HAVE_SETPROGNAME ?=	0
    1.60    HAVE_SYS_TREE_H ?=	0
    1.61  endif
    1.62  
    1.63  OBJS =	cmd.o \
    1.64 +	pw.o \
    1.65  	pwfile.o \
    1.66  	pwm.o \
    1.67  	tok.o \
    1.68 @@ -141,6 +156,15 @@
    1.69  else
    1.70    OBJS +=	compat/asprintf.o
    1.71  endif
    1.72 +ifeq ($(HAVE_ARC4RANDOM),1)
    1.73 +  XCPPFLAGS +=	-DHAVE_ARC4RANDOM
    1.74 +  OBJS +=	rand-arc4random.o
    1.75 +else ifeq ($(HAVE_GETRANDOM),1)
    1.76 +  XCPPFLAGS +=	-DHAVE_GETRANDOM
    1.77 +  OBJS +=	rand-getrandom.o
    1.78 +else
    1.79 +  OBJS +=	rand-dev-random.o
    1.80 +endif
    1.81  ifeq ($(HAVE_ERR_H),1)
    1.82    XCPPFLAGS +=	-DHAVE_ERR_H
    1.83  else
     2.1 --- a/cmd.c	Mon Jul 31 09:20:21 2017 +0200
     2.2 +++ b/cmd.c	Thu Aug 03 10:22:07 2017 +0200
     2.3 @@ -23,6 +23,7 @@
     2.4  
     2.5  #include "compat.h"
     2.6  
     2.7 +#include <ctype.h>
     2.8  #ifdef	HAVE_ERR_H
     2.9  #include <err.h>
    2.10  #endif /* HAVE_ERR_H */
    2.11 @@ -38,12 +39,18 @@
    2.12  #include <unistd.h>
    2.13  
    2.14  #include "cmd.h"
    2.15 +#include "pw.h"
    2.16  #include "pwfile.h"
    2.17  #include "util.h"
    2.18  
    2.19  #define	TIME_FORMAT	"%Y-%m-%dT%TZ"
    2.20  #define	TIME_SIZE	(4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1)
    2.21  
    2.22 +#define	CHARS_DIGIT	"0123456789"
    2.23 +#define	CHARS_UPPER	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    2.24 +#define	CHARS_LOWER	"abcdefghijklmnopqrstuvwxyz"
    2.25 +#define	CHARS_PUNCT	"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
    2.26 +
    2.27  enum field_type {
    2.28  	FIELD_UNKNOWN = -1,
    2.29  	FIELD_GROUP,
    2.30 @@ -56,10 +63,30 @@
    2.31  	FIELD_CTIME
    2.32  };
    2.33  
    2.34 +enum cmd_generatepassword_arg_type {
    2.35 +	CMD_GP_ARG_UNKNOWN = -1,
    2.36 +	CMD_GP_ARG_LEN,
    2.37 +	CMD_GP_ARG_CHARS,
    2.38 +	CMD_GP_ARG_CHARCLASS
    2.39 +};
    2.40 +
    2.41 +enum charclass_type {
    2.42 +	CHARCLASS_UNKNOWN = -1,
    2.43 +	CHARCLASS_DIGIT,
    2.44 +	CHARCLASS_UPPER,
    2.45 +	CHARCLASS_LOWER,
    2.46 +	CHARCLASS_PUNCT,
    2.47 +	CHARCLASS_ALPHA,
    2.48 +	CHARCLASS_ALNUM,
    2.49 +	CHARCLASS_XDIGIT,
    2.50 +	CHARCLASS_GRAPH
    2.51 +};
    2.52 +
    2.53  static enum cmd_return	cmd_info(struct pwm_ctx *, int, char *[]);
    2.54  static enum cmd_return	cmd_list(struct pwm_ctx *, int, char *[]);
    2.55  static enum cmd_return	cmd_create(struct pwm_ctx *, int, char *[]);
    2.56  static enum cmd_return	cmd_modify(struct pwm_ctx *, int, char *[]);
    2.57 +static enum cmd_return	cmd_generatepassword(struct pwm_ctx *, int, char *[]);
    2.58  static enum cmd_return	cmd_remove(struct pwm_ctx *, int, char *[]);
    2.59  static enum cmd_return	cmd_show(struct pwm_ctx *, int, char *[]);
    2.60  static enum cmd_return	cmd_pipe(struct pwm_ctx *, int, char *[]);
    2.61 @@ -70,7 +97,7 @@
    2.62  static enum cmd_return	cmd_write(struct pwm_ctx *, int, char *[]);
    2.63  static enum cmd_return	cmd_quit(struct pwm_ctx *, int, char *[]);
    2.64  
    2.65 -static const char *field_names[] = {
    2.66 +static const char *field_namev[] = {
    2.67      "group",
    2.68      "title",
    2.69      "username",
    2.70 @@ -78,7 +105,8 @@
    2.71      "notes",
    2.72      "url",
    2.73      "ctime",
    2.74 -    "mtime"
    2.75 +    "mtime",
    2.76 +    NULL
    2.77  };
    2.78  
    2.79  static const char *field_labels[] = {
    2.80 @@ -92,12 +120,45 @@
    2.81      "Modified: "
    2.82  };
    2.83  
    2.84 +static const char *cmd_generatepassword_argv[] = {
    2.85 +    "len",
    2.86 +    "char",
    2.87 +    "charclass",
    2.88 +    NULL
    2.89 +};
    2.90 +
    2.91 +static const char *charclass_namev[] = {
    2.92 +    "digit",
    2.93 +    "upper",
    2.94 +    "lower",
    2.95 +    "punct",
    2.96 +    "alpha",
    2.97 +    "alnum",
    2.98 +    "xdigit",
    2.99 +    "graph",
   2.100 +    NULL
   2.101 +};
   2.102 +
   2.103 +static const char *charclass_values[] = {
   2.104 +    CHARS_DIGIT,
   2.105 +    CHARS_UPPER,
   2.106 +    CHARS_LOWER,
   2.107 +    CHARS_PUNCT,
   2.108 +    CHARS_UPPER CHARS_LOWER,
   2.109 +    CHARS_DIGIT CHARS_UPPER CHARS_LOWER,
   2.110 +    CHARS_DIGIT "abcdef",
   2.111 +    CHARS_DIGIT CHARS_UPPER CHARS_LOWER CHARS_PUNCT
   2.112 +};
   2.113 +
   2.114  static struct cmd cmds[] = {
   2.115      { "i", "info", "info", "Show metadata information about the current file",
   2.116      cmd_info },
   2.117      { "ls", "list", "list [field~regex ...]", "List entries", cmd_list },
   2.118      { "c", "create", "create field=value ...", "Create entry", cmd_create },
   2.119      { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify },
   2.120 +    { "gp", "generatepassword", "generatepassword [id] [len=n] [chars=n:chars] "
   2.121 +    "[charclass=n:class] ...", "Randomly generate a password",
   2.122 +    cmd_generatepassword },
   2.123      { "rm", "remove", "remove id", "Delete entry", cmd_remove },
   2.124      { "s", "show", "show id field", "Show entry", cmd_show },
   2.125      { "p", "pipe", "pipe id field command", "Pipe entry to external command",
   2.126 @@ -114,24 +175,24 @@
   2.127      { 0 }
   2.128  };
   2.129  
   2.130 -static enum field_type
   2.131 -parse_field(char *field_arg, int sep, char **valuep)
   2.132 +static int
   2.133 +parse_arg(char *arg, const char *namev[], int sep, char **valuep)
   2.134  {
   2.135 -	int	i;
   2.136 -	size_t	field_name_len;
   2.137 +	size_t	i;
   2.138 +	size_t	name_len;
   2.139  
   2.140 -	for (i = 0; i < (int)COUNTOF(field_names); i++) {
   2.141 -		field_name_len = strlen(field_names[i]);
   2.142 -		if ((strncmp(field_names[i], field_arg, field_name_len) == 0) &&
   2.143 -		    (field_arg[field_name_len] == sep)) {
   2.144 +	for (i = 0; namev[i] != NULL; i++) {
   2.145 +		name_len = strlen(namev[i]);
   2.146 +		if ((strncmp(namev[i], arg, name_len) == 0) &&
   2.147 +		    (arg[name_len] == sep)) {
   2.148  			if (valuep != NULL) {
   2.149 -				*valuep = field_arg + field_name_len + 1;
   2.150 +				*valuep = arg + name_len + 1;
   2.151  			}
   2.152  			return (i);
   2.153  		}
   2.154  	}
   2.155  
   2.156 -	return (FIELD_UNKNOWN);
   2.157 +	return (-1);
   2.158  }
   2.159  
   2.160  static int
   2.161 @@ -203,7 +264,7 @@
   2.162  	struct record	*record;
   2.163  
   2.164  	for (i = 1; i < argc; i++) {
   2.165 -		type = parse_field(argv[i], '~', &value);
   2.166 +		type = parse_arg(argv[i], field_namev, '~', &value);
   2.167  		if (type == FIELD_UNKNOWN) {
   2.168  			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
   2.169  			goto out;
   2.170 @@ -319,7 +380,7 @@
   2.171  	}
   2.172  
   2.173  	for (i = 1; i < argc; i++) {
   2.174 -		type = parse_field(argv[i], '=', &value);
   2.175 +		type = parse_arg(argv[i], field_namev, '=', &value);
   2.176  		if (type == FIELD_UNKNOWN) {
   2.177  			fprintf(stderr, "bad field assignment \"%s\"\n",
   2.178  			    argv[i]);
   2.179 @@ -377,7 +438,7 @@
   2.180  	}
   2.181  
   2.182  	for (i = 2; i < argc; i++) {
   2.183 -		type = parse_field(argv[i], '=', &value);
   2.184 +		type = parse_arg(argv[i], field_namev, '=', &value);
   2.185  		if (type == FIELD_UNKNOWN) {
   2.186  			fprintf(stderr, "bad field assignment \"%s\"\n",
   2.187  			    argv[i]);
   2.188 @@ -418,6 +479,146 @@
   2.189  }
   2.190  
   2.191  static enum cmd_return
   2.192 +cmd_generatepassword(struct pwm_ctx *ctx, int argc, char *argv[])
   2.193 +{
   2.194 +	enum cmd_return	retval = CMD_ERR;
   2.195 +	unsigned int	id = 0;
   2.196 +	int		i = 1;
   2.197 +	char		*value = NULL;
   2.198 +	long		x;
   2.199 +	char		*p;
   2.200 +	size_t		password_len = 16;
   2.201 +	size_t		chars_min;
   2.202 +	const char	*chars;
   2.203 +	struct pw_char_group *char_groupv = NULL;
   2.204 +	size_t		char_groupv_len = 0;
   2.205 +	int		charclass;
   2.206 +	size_t		j;
   2.207 +	char		password[PWS3_MAX_PASSWORD_LEN + 1] = { 0 };
   2.208 +
   2.209 +	/* check if first argument is an id */
   2.210 +	if ((argc > 1) && (parse_id(argv[1], &id) == 0)) {
   2.211 +		i++;
   2.212 +	}
   2.213 +
   2.214 +	for (; i < argc; i++) {
   2.215 +		switch (parse_arg(argv[i], cmd_generatepassword_argv, '=',
   2.216 +		    &value)) {
   2.217 +		case CMD_GP_ARG_LEN:
   2.218 +			errno = 0;
   2.219 +			x = strtol(value, &p, 10);
   2.220 +			if ((errno != 0) || (*value == '\0') || (*p != '\0') ||
   2.221 +			    (x > PWS3_MAX_PASSWORD_LEN) || (x <= 0)) {
   2.222 +				fprintf(stderr, "invalid password length "
   2.223 +				    "\"%s\"\n", argv[i]);
   2.224 +				goto out;
   2.225 +			}
   2.226 +			password_len = x;
   2.227 +			break;
   2.228 +		case CMD_GP_ARG_CHARS:
   2.229 +			errno = 0;
   2.230 +			x = strtol(value, &p, 10);
   2.231 +			if ((errno != 0) || (*value == '\0') || (*p != ':') ||
   2.232 +			    (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) {
   2.233 +				fprintf(stderr, "invalid minimum number of "
   2.234 +				    "characters \"%s\"\n", argv[i]);
   2.235 +				goto out;
   2.236 +			}
   2.237 +			chars_min = x;
   2.238 +
   2.239 +			chars = ++p;
   2.240 +			while (*p != '\0') {
   2.241 +				if (!isascii(*p) || !isprint(*p)) {
   2.242 +					fprintf(stderr, "invalid character in "
   2.243 +					    "character group \"%s\"\n",
   2.244 +					    argv[i]);
   2.245 +					goto out;
   2.246 +				}
   2.247 +				p++;
   2.248 +			}
   2.249 +
   2.250 +			char_groupv = xrealloc(char_groupv,
   2.251 +			    sizeof (struct pw_char_group) * (char_groupv_len +
   2.252 +			    1));
   2.253 +			char_groupv[char_groupv_len].chars = chars;
   2.254 +			char_groupv[char_groupv_len].chars_min = chars_min;
   2.255 +			char_groupv_len++;
   2.256 +			break;
   2.257 +		case CMD_GP_ARG_CHARCLASS:
   2.258 +			errno = 0;
   2.259 +			x = strtol(value, &p, 10);
   2.260 +			if ((errno != 0) || (*value == '\0') || (*p != ':') ||
   2.261 +			    (x < 0) || (x > PWS3_MAX_PASSWORD_LEN)) {
   2.262 +				fprintf(stderr, "invalid minimum number of "
   2.263 +				    "characters \"%s\"\n", argv[i]);
   2.264 +				goto out;
   2.265 +			}
   2.266 +			chars_min = x;
   2.267 +
   2.268 +			charclass = parse_arg(++p, charclass_namev, '\0', NULL);
   2.269 +			if (charclass < 0) {
   2.270 +				fprintf(stderr, "unknown character class "
   2.271 +				    "\"%s\"\n", argv[i]);
   2.272 +				goto out;
   2.273 +			}
   2.274 +			chars = charclass_values[charclass];
   2.275 +			char_groupv = xrealloc(char_groupv,
   2.276 +			    sizeof (struct pw_char_group) * (char_groupv_len +
   2.277 +			    1));
   2.278 +			char_groupv[char_groupv_len].chars = chars;
   2.279 +			char_groupv[char_groupv_len].chars_min = chars_min;
   2.280 +			char_groupv_len++;
   2.281 +			break;
   2.282 +		default:
   2.283 +			fprintf(stderr, "invalid argument \"%s\"\n", argv[i]);
   2.284 +			retval = CMD_USAGE;
   2.285 +			goto out;
   2.286 +		}
   2.287 +	}
   2.288 +
   2.289 +	for (j = 0; j < char_groupv_len; j++) {
   2.290 +		if (char_groupv[j].chars_min > password_len) {
   2.291 +			fprintf(stderr, "invalid minimum number of "
   2.292 +			    "characters \"%zu:%s\"\n", char_groupv[j].chars_min,
   2.293 +			    char_groupv[j].chars);
   2.294 +			goto out;
   2.295 +		}
   2.296 +	}
   2.297 +
   2.298 +	if (char_groupv_len == 0) {
   2.299 +		/* use defaults */
   2.300 +		char_groupv = xmalloc(sizeof (struct pw_char_group));
   2.301 +		char_groupv[0].chars = charclass_values[CHARCLASS_GRAPH];
   2.302 +		char_groupv[0].chars_min = 0;
   2.303 +		char_groupv_len++;
   2.304 +	}
   2.305 +
   2.306 +	if (pw_genrandom(char_groupv, char_groupv_len, password,
   2.307 +	    password_len) != 0) {
   2.308 +		fprintf(stderr, "failed to generate password that meets the "
   2.309 +		    "given constraints\n");
   2.310 +		goto out;
   2.311 +	}
   2.312 +
   2.313 +	if (id != 0) {
   2.314 +		if (pwfile_modify_record(ctx, id,
   2.315 +		    &(struct record){ .password = password }) != 0) {
   2.316 +			fprintf(stderr, "record %u does not exist\n", id);
   2.317 +			goto out;
   2.318 +		}
   2.319 +	} else {
   2.320 +		printf("%s\n", password);
   2.321 +	}
   2.322 +
   2.323 +	retval = CMD_OK;
   2.324 +
   2.325 +out:
   2.326 +	free(char_groupv);
   2.327 +
   2.328 +	return (retval);
   2.329 +}
   2.330 +
   2.331 +static enum cmd_return
   2.332  cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[])
   2.333  {
   2.334  	unsigned int	id;
   2.335 @@ -518,7 +719,7 @@
   2.336  	struct record	*record;
   2.337  	int		i;
   2.338  	enum field_type	type;
   2.339 -	int		fields[COUNTOF(field_names)] = { 0 };
   2.340 +	int		fields[COUNTOF(field_namev) - 1] = { 0 };
   2.341  
   2.342  	if (argc < 2) {
   2.343  		return (CMD_USAGE);
   2.344 @@ -530,7 +731,7 @@
   2.345  	}
   2.346  
   2.347  	for (i = 2; i < argc; i++) {
   2.348 -		type = parse_field(argv[i], '\0', NULL);
   2.349 +		type = parse_arg(argv[i], field_namev, '\0', NULL);
   2.350  		if (type < 0) {
   2.351  			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
   2.352  			return (CMD_ERR);
   2.353 @@ -556,7 +757,7 @@
   2.354  	unsigned int	id;
   2.355  	struct record	*record = NULL;
   2.356  	enum field_type	type;
   2.357 -	int		fields[COUNTOF(field_names)] = { 0 };
   2.358 +	int		fields[COUNTOF(field_namev) - 1] = { 0 };
   2.359  	FILE		*fp = NULL;
   2.360  
   2.361  	if (argc != 4) {
   2.362 @@ -568,7 +769,7 @@
   2.363  		return (CMD_ERR);
   2.364  	}
   2.365  
   2.366 -	type = parse_field(argv[2], '\0', NULL);
   2.367 +	type = parse_arg(argv[2], field_namev, '\0', NULL);
   2.368  	if (type < 0) {
   2.369  		fprintf(stderr, "bad field name \"%s\"\n", argv[2]);
   2.370  		return (CMD_ERR);
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/pw.c	Thu Aug 03 10:22:07 2017 +0200
     3.3 @@ -0,0 +1,100 @@
     3.4 +/*
     3.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     3.6 + *
     3.7 + * Permission is hereby granted, free of charge, to any person obtaining
     3.8 + * a copy of this software and associated documentation files (the
     3.9 + * "Software"), to deal in the Software without restriction, including
    3.10 + * without limitation the rights to use, copy, modify, merge, publish,
    3.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    3.12 + * permit persons to whom the Software is furnished to do so, subject to
    3.13 + * the following conditions:
    3.14 + *
    3.15 + * The above copyright notice and this permission notice shall be included
    3.16 + * in all copies or substantial portions of the Software.
    3.17 + *
    3.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    3.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    3.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    3.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    3.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    3.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    3.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    3.25 + */
    3.26 +
    3.27 +#include "compat.h"
    3.28 +
    3.29 +#ifdef	HAVE_ERR_H
    3.30 +#include <err.h>
    3.31 +#endif /* HAVE_ERR_H */
    3.32 +#include <pws.h>
    3.33 +#include <stdint.h>
    3.34 +#include <string.h>
    3.35 +
    3.36 +#include "pw.h"
    3.37 +#include "rand.h"
    3.38 +#include "util.h"
    3.39 +
    3.40 +int
    3.41 +pw_genrandom(struct pw_char_group groups[], size_t groups_len, char *password,
    3.42 +    size_t password_len)
    3.43 +{
    3.44 +	int	retval = -1;
    3.45 +	char	*password_buf = NULL;
    3.46 +	size_t	*group_matches = NULL;
    3.47 +	size_t	i;
    3.48 +	size_t	chars_len = 0;
    3.49 +	char	*chars;
    3.50 +	size_t	j;
    3.51 +	uint32_t r;
    3.52 +	size_t	k;
    3.53 +
    3.54 +	password_buf = xmalloc(password_len + 1);
    3.55 +	password_buf[password_len] = '\0';
    3.56 +
    3.57 +	group_matches = xmalloc(groups_len * sizeof (size_t));
    3.58 +
    3.59 +	for (i = 0; i < groups_len; i++) {
    3.60 +		chars_len += strlen(groups[i].chars);
    3.61 +	}
    3.62 +
    3.63 +	chars = xmalloc(chars_len + 1);
    3.64 +	chars[0] = '\0';
    3.65 +	for (i = 0; i < groups_len; i++) {
    3.66 +		strcat(chars, groups[i].chars);
    3.67 +	}
    3.68 +
    3.69 +	for (k = 0; k < 100000; k++) {
    3.70 +		memset(group_matches, 0, groups_len * sizeof (size_t));
    3.71 +
    3.72 +		for (j = 0; j < password_len; j++) {
    3.73 +			r = rand_uniform(chars_len);
    3.74 +			password_buf[j] = chars[r];
    3.75 +
    3.76 +			for (i = 0; i < groups_len; i++) {
    3.77 +				if (strchr(groups[i].chars, chars[r]) != NULL) {
    3.78 +					group_matches[i]++;
    3.79 +					break;
    3.80 +				}
    3.81 +			}
    3.82 +		}
    3.83 +
    3.84 +		for (i = 0; i < groups_len; i++) {
    3.85 +			if (group_matches[i] < groups[i].chars_min) {
    3.86 +				/* try again */
    3.87 +				break;
    3.88 +			}
    3.89 +		}
    3.90 +		if (i == groups_len) {
    3.91 +			/* password meets all constraints */
    3.92 +			strcpy(password, password_buf);
    3.93 +			retval = 0;
    3.94 +			break;
    3.95 +		}
    3.96 +	}
    3.97 +
    3.98 +	free(chars);
    3.99 +	free(group_matches);
   3.100 +	free(password_buf);
   3.101 +
   3.102 +	return (retval);
   3.103 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/pw.h	Thu Aug 03 10:22:07 2017 +0200
     4.3 @@ -0,0 +1,34 @@
     4.4 +/*
     4.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     4.6 + *
     4.7 + * Permission is hereby granted, free of charge, to any person obtaining
     4.8 + * a copy of this software and associated documentation files (the
     4.9 + * "Software"), to deal in the Software without restriction, including
    4.10 + * without limitation the rights to use, copy, modify, merge, publish,
    4.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    4.12 + * permit persons to whom the Software is furnished to do so, subject to
    4.13 + * the following conditions:
    4.14 + *
    4.15 + * The above copyright notice and this permission notice shall be included
    4.16 + * in all copies or substantial portions of the Software.
    4.17 + *
    4.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    4.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    4.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    4.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    4.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    4.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    4.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    4.25 + */
    4.26 +
    4.27 +#ifndef	PW_H
    4.28 +#define	PW_H
    4.29 +
    4.30 +struct pw_char_group {
    4.31 +	const char	*chars;
    4.32 +	size_t		chars_min;
    4.33 +};
    4.34 +
    4.35 +int	pw_genrandom(struct pw_char_group [], size_t, char *, size_t);
    4.36 +
    4.37 +#endif /* !PW_H */
     5.1 --- a/pwm.1.xml	Mon Jul 31 09:20:21 2017 +0200
     5.2 +++ b/pwm.1.xml	Thu Aug 03 10:22:07 2017 +0200
     5.3 @@ -34,7 +34,7 @@
     5.4        <email>guido+pwm@berhoerster.name</email>
     5.5        <personblurb/>
     5.6      </author>
     5.7 -    <date>31 July, 2017</date>
     5.8 +    <date>3 August, 2017</date>
     5.9    </info>
    5.10    <refmeta>
    5.11      <refentrytitle>pwm</refentrytitle>
    5.12 @@ -393,6 +393,54 @@
    5.13            </listitem>
    5.14          </varlistentry>
    5.15          <varlistentry>
    5.16 +          <term>Generate a random password</term>
    5.17 +          <listitem>
    5.18 +            <cmdsynopsis>
    5.19 +              <command>generatepassword</command>
    5.20 +              <arg choice="opt">
    5.21 +                <replaceable>id</replaceable>
    5.22 +              </arg>
    5.23 +              <arg choice="opt">
    5.24 +                len=<replaceable>n</replaceable>
    5.25 +              </arg>
    5.26 +              <arg choice="opt" rep="repeat">
    5.27 +                chars=<replaceable>n</replaceable>:<replaceable>chars</replaceable>
    5.28 +              </arg>
    5.29 +              <arg choice="opt" rep="repeat">
    5.30 +                charclass=<replaceable>n</replaceable>:<replaceable>class</replaceable>
    5.31 +              </arg>
    5.32 +            </cmdsynopsis>
    5.33 +            <cmdsynopsis>
    5.34 +              <command>gp</command>
    5.35 +              <arg choice="opt">
    5.36 +                <replaceable>id</replaceable>
    5.37 +              </arg>
    5.38 +              <arg choice="opt">
    5.39 +                len=<replaceable>n</replaceable>
    5.40 +              </arg>
    5.41 +              <arg choice="opt" rep="repeat">
    5.42 +                chars=<replaceable>n</replaceable>:<replaceable>chars</replaceable>
    5.43 +              </arg>
    5.44 +              <arg choice="opt" rep="repeat">
    5.45 +                charclass=<replaceable>n</replaceable>:<replaceable>class</replaceable>
    5.46 +              </arg>
    5.47 +              <sbr/>
    5.48 +            </cmdsynopsis>
    5.49 +            <para>Randomly generate a new password according to the specified
    5.50 +            constraints. The <literal>len</literal> argument sets the length of
    5.51 +            the generated password to <replaceable>n</replaceable> characters.
    5.52 +            The <literal>chars</literal> argument constrains the password to
    5.53 +            <replaceable>n</replaceable> from the set of characters
    5.54 +            <replaceable>chars</replaceable>. Similarly, the
    5.55 +            <literal>charclass</literal> argument to
    5.56 +            <replaceable>n</replaceable> characters from the extended regular
    5.57 +            expression character class <replaceable>class</replaceable>.
    5.58 +            Multiple <literal>char</literal> and <literal>charclass</literal>
    5.59 +            arguments may be specified, in which case the generated passwords
    5.60 +            match all of them.</para>
    5.61 +          </listitem>
    5.62 +        </varlistentry>
    5.63 +        <varlistentry>
    5.64            <term>Change the master password</term>
    5.65            <listitem>
    5.66              <cmdsynopsis>
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/rand-arc4random.c	Thu Aug 03 10:22:07 2017 +0200
     6.3 @@ -0,0 +1,44 @@
     6.4 +/*
     6.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     6.6 + *
     6.7 + * Permission is hereby granted, free of charge, to any person obtaining
     6.8 + * a copy of this software and associated documentation files (the
     6.9 + * "Software"), to deal in the Software without restriction, including
    6.10 + * without limitation the rights to use, copy, modify, merge, publish,
    6.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    6.12 + * permit persons to whom the Software is furnished to do so, subject to
    6.13 + * the following conditions:
    6.14 + *
    6.15 + * The above copyright notice and this permission notice shall be included
    6.16 + * in all copies or substantial portions of the Software.
    6.17 + *
    6.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    6.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    6.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    6.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    6.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    6.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    6.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    6.25 + */
    6.26 +
    6.27 +#include <stdlib.h>
    6.28 +
    6.29 +#include "rand.h"
    6.30 +
    6.31 +void
    6.32 +rand_buf(void *buf, size_t buf_size)
    6.33 +{
    6.34 +	arc4random_buf(buf, buf_size);
    6.35 +}
    6.36 +
    6.37 +uint32_t
    6.38 +rand_random(void)
    6.39 +{
    6.40 +	return (arc4random());
    6.41 +}
    6.42 +
    6.43 +uint32_t
    6.44 +rand_uniform(uint32_t upper_bound)
    6.45 +{
    6.46 +	return (arc4random_uniform(upper_bound));
    6.47 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/rand-dev-random.c	Thu Aug 03 10:22:07 2017 +0200
     7.3 @@ -0,0 +1,95 @@
     7.4 +/*
     7.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     7.6 + *
     7.7 + * Permission is hereby granted, free of charge, to any person obtaining
     7.8 + * a copy of this software and associated documentation files (the
     7.9 + * "Software"), to deal in the Software without restriction, including
    7.10 + * without limitation the rights to use, copy, modify, merge, publish,
    7.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    7.12 + * permit persons to whom the Software is furnished to do so, subject to
    7.13 + * the following conditions:
    7.14 + *
    7.15 + * The above copyright notice and this permission notice shall be included
    7.16 + * in all copies or substantial portions of the Software.
    7.17 + *
    7.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    7.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    7.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    7.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    7.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    7.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    7.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    7.25 + */
    7.26 +
    7.27 +#include "compat.h"
    7.28 +
    7.29 +#ifdef	HAVE_ERR_H
    7.30 +#include <err.h>
    7.31 +#endif /* HAVE_ERR_H */
    7.32 +#include <errno.h>
    7.33 +#include <fcntl.h>
    7.34 +#include <sys/stat.h>
    7.35 +#include <unistd.h>
    7.36 +
    7.37 +#include "rand.h"
    7.38 +
    7.39 +#ifdef	__linux__
    7.40 +#define	PATH_DEV_RANDOM	"/dev/urandom"
    7.41 +#else
    7.42 +#define	PATH_DEV_RANDOM	"/dev/random"
    7.43 +#endif /* __linux__ */
    7.44 +
    7.45 +void
    7.46 +rand_buf(void *buf, size_t buf_size)
    7.47 +{
    7.48 +	unsigned char	*p = buf;
    7.49 +	int		fd;
    7.50 +	ssize_t		nread;
    7.51 +	size_t		nsize = buf_size;
    7.52 +
    7.53 +	do {
    7.54 +		fd = open(PATH_DEV_RANDOM, O_RDONLY);
    7.55 +	} while ((fd < 0) && (errno == EINTR));
    7.56 +	if (fd < 0) {
    7.57 +		err(1, "open");
    7.58 +	}
    7.59 +
    7.60 +	while (nsize > 0) {
    7.61 +		nread = read(fd, p, nsize);
    7.62 +		if (nread < 0) {
    7.63 +			if (errno == EINTR) {
    7.64 +				continue;
    7.65 +			}
    7.66 +			err(1, "read");
    7.67 +		}
    7.68 +		p += nread;
    7.69 +		nsize -= nread;
    7.70 +	}
    7.71 +
    7.72 +	while ((close(fd) < 0) && (errno == EINTR));
    7.73 +}
    7.74 +
    7.75 +uint32_t
    7.76 +rand_random(void)
    7.77 +{
    7.78 +	uint32_t	x;
    7.79 +
    7.80 +	rand_buf(&x, sizeof (x));
    7.81 +
    7.82 +	return (x);
    7.83 +}
    7.84 +
    7.85 +/* random number between 0 and upper_bound - 1 without modulo bias */
    7.86 +uint32_t
    7.87 +rand_uniform(uint32_t upper_bound)
    7.88 +{
    7.89 +	uint32_t	r;
    7.90 +	/* (2^32 - upper_bound) % upper_bound */
    7.91 +	uint32_t	threshold = -upper_bound % upper_bound;
    7.92 +
    7.93 +	do {
    7.94 +		r = rand_random();
    7.95 +	} while (r < threshold);
    7.96 +
    7.97 +	return (r % upper_bound);
    7.98 +}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/rand-getrandom.c	Thu Aug 03 10:22:07 2017 +0200
     8.3 @@ -0,0 +1,77 @@
     8.4 +/*
     8.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     8.6 + *
     8.7 + * Permission is hereby granted, free of charge, to any person obtaining
     8.8 + * a copy of this software and associated documentation files (the
     8.9 + * "Software"), to deal in the Software without restriction, including
    8.10 + * without limitation the rights to use, copy, modify, merge, publish,
    8.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    8.12 + * permit persons to whom the Software is furnished to do so, subject to
    8.13 + * the following conditions:
    8.14 + *
    8.15 + * The above copyright notice and this permission notice shall be included
    8.16 + * in all copies or substantial portions of the Software.
    8.17 + *
    8.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    8.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    8.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    8.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    8.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    8.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    8.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    8.25 + */
    8.26 +
    8.27 +#include "compat.h"
    8.28 +
    8.29 +#include <stdlib.h>
    8.30 +/* glibc and Solaris 11 */
    8.31 +#include <sys/random.h>
    8.32 +
    8.33 +#include "rand.h"
    8.34 +
    8.35 +#define	RANDOM_CHUNK	256U
    8.36 +
    8.37 +void
    8.38 +rand_buf(void *buf, size_t buf_size)
    8.39 +{
    8.40 +	unsigned char	*p = buf;
    8.41 +	ssize_t		nread;
    8.42 +	size_t		nsize = buf_size;
    8.43 +
    8.44 +	while (nsize > 0) {
    8.45 +		nread = getrandom(p, nsize, 0);
    8.46 +		if (nread < 0) {
    8.47 +			if (errno == EINTR) {
    8.48 +				continue;
    8.49 +			}
    8.50 +			err(1, "read");
    8.51 +		}
    8.52 +		nsize -= nread;
    8.53 +		p += nread;
    8.54 +	}
    8.55 +}
    8.56 +
    8.57 +uint32_t
    8.58 +rand_random(void)
    8.59 +{
    8.60 +	uint32_t	r;
    8.61 +
    8.62 +	getrandom(r, sizeof (uint32_t), 0);
    8.63 +
    8.64 +	return (r);
    8.65 +}
    8.66 +
    8.67 +/* random number between 0 and upper_bound - 1 without modulo bias */
    8.68 +uint32_t
    8.69 +rand_uniform(uint32_t upper_bound)
    8.70 +{
    8.71 +	uint32_t	r;
    8.72 +	/* (2^32 - upper_bound) % upper_bound */
    8.73 +	uint32_t	threshold = -upper_bound % upper_bound;
    8.74 +
    8.75 +	do {
    8.76 +		r = rand_random();
    8.77 +	} while (r < threshold);
    8.78 +
    8.79 +	return (r % upper_bound);
    8.80 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/rand.h	Thu Aug 03 10:22:07 2017 +0200
     9.3 @@ -0,0 +1,33 @@
     9.4 +/*
     9.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     9.6 + *
     9.7 + * Permission is hereby granted, free of charge, to any person obtaining
     9.8 + * a copy of this software and associated documentation files (the
     9.9 + * "Software"), to deal in the Software without restriction, including
    9.10 + * without limitation the rights to use, copy, modify, merge, publish,
    9.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    9.12 + * permit persons to whom the Software is furnished to do so, subject to
    9.13 + * the following conditions:
    9.14 + *
    9.15 + * The above copyright notice and this permission notice shall be included
    9.16 + * in all copies or substantial portions of the Software.
    9.17 + *
    9.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    9.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    9.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    9.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    9.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    9.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    9.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    9.25 + */
    9.26 +
    9.27 +#ifndef	RAND_H
    9.28 +#define	RAND_H
    9.29 +
    9.30 +#include <stdint.h>
    9.31 +
    9.32 +void		rand_buf(void *, size_t);
    9.33 +uint32_t	rand_random(void);
    9.34 +uint32_t	rand_uniform(uint32_t);
    9.35 +
    9.36 +#endif /* !RAND_H */