projects/pwm

changeset 11:85bce13237cf

Add filter expressions to list command

Refactor field parsing so it can be used for parsing fields, field assignments
and filter expressions.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Mon Jul 31 09:20:21 2017 +0200 (2017-07-31)
parents 17fb30016e64
children 8768fbd09bc5
files cmd.c pwm.1.xml
line diff
     1.1 --- a/cmd.c	Fri Jul 28 15:53:57 2017 +0200
     1.2 +++ b/cmd.c	Mon Jul 31 09:20:21 2017 +0200
     1.3 @@ -31,6 +31,7 @@
     1.4  #ifdef	HAVE_READPASSPHRASE_H
     1.5  #include <readpassphrase.h>
     1.6  #endif /* READPASSPHRASE_H */
     1.7 +#include <regex.h>
     1.8  #include <stdlib.h>
     1.9  #include <string.h>
    1.10  #include <time.h>
    1.11 @@ -94,7 +95,7 @@
    1.12  static struct cmd cmds[] = {
    1.13      { "i", "info", "info", "Show metadata information about the current file",
    1.14      cmd_info },
    1.15 -    { "ls", "list", "list", "List entries", cmd_list },
    1.16 +    { "ls", "list", "list [field~regex ...]", "List entries", cmd_list },
    1.17      { "c", "create", "create field=value ...", "Create entry", cmd_create },
    1.18      { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify },
    1.19      { "rm", "remove", "remove id", "Delete entry", cmd_remove },
    1.20 @@ -114,56 +115,17 @@
    1.21  };
    1.22  
    1.23  static enum field_type
    1.24 -parse_field_name(const char *name)
    1.25 -{
    1.26 -	int	i;
    1.27 -
    1.28 -	for (i = 0; i < (int)COUNTOF(field_names); i++) {
    1.29 -		if (strcmp(field_names[i], name) == 0) {
    1.30 -			return (i);
    1.31 -		}
    1.32 -	}
    1.33 -
    1.34 -	return (FIELD_UNKNOWN);
    1.35 -}
    1.36 -
    1.37 -static enum field_type
    1.38 -parse_field_assignment(char *arg, struct record *record)
    1.39 +parse_field(char *field_arg, int sep, char **valuep)
    1.40  {
    1.41  	int	i;
    1.42  	size_t	field_name_len;
    1.43 -	char	*value;
    1.44  
    1.45  	for (i = 0; i < (int)COUNTOF(field_names); i++) {
    1.46  		field_name_len = strlen(field_names[i]);
    1.47 -		if ((strncmp(field_names[i], arg, field_name_len) == 0) &&
    1.48 -		    (arg[field_name_len] == '=')){
    1.49 -			value = arg + field_name_len + 1;
    1.50 -			if (*value == '\0') {
    1.51 -				/* skip empty assignments */
    1.52 -				return (i);
    1.53 -			}
    1.54 -			switch (i) {
    1.55 -			case FIELD_GROUP:
    1.56 -				record->group = value;
    1.57 -				break;
    1.58 -			case FIELD_TITLE:
    1.59 -				record->title = value;
    1.60 -				break;
    1.61 -			case FIELD_USERNAME:
    1.62 -				record->username = value;
    1.63 -				break;
    1.64 -			case FIELD_PASSWORD:
    1.65 -				record->password = value;
    1.66 -				break;
    1.67 -			case FIELD_NOTES:
    1.68 -				record->notes = value;
    1.69 -				break;
    1.70 -			case FIELD_URL:
    1.71 -				record->url = value;
    1.72 -				break;
    1.73 -			default:
    1.74 -				return (FIELD_UNKNOWN);
    1.75 +		if ((strncmp(field_names[i], field_arg, field_name_len) == 0) &&
    1.76 +		    (field_arg[field_name_len] == sep)) {
    1.77 +			if (valuep != NULL) {
    1.78 +				*valuep = field_arg + field_name_len + 1;
    1.79  			}
    1.80  			return (i);
    1.81  		}
    1.82 @@ -223,26 +185,125 @@
    1.83  static enum cmd_return
    1.84  cmd_list(struct pwm_ctx *ctx, int argc, char *argv[])
    1.85  {
    1.86 -	union list_item	**list;
    1.87 -	size_t		i;
    1.88 +	int		retval = CMD_ERR;
    1.89 +	int		i;
    1.90 +	regex_t		*group_re = NULL;
    1.91 +	regex_t		*title_re = NULL;
    1.92 +	regex_t		*username_re = NULL;
    1.93 +	regex_t		*notes_re = NULL;
    1.94 +	regex_t		*url_re = NULL;
    1.95 +	enum field_type	type;
    1.96 +	char		*value;
    1.97 +	regex_t		**repp;
    1.98 +	int		errcode;
    1.99 +	char		*errbuf;
   1.100 +	size_t		errbuf_size;
   1.101 +	union list_item	**list = NULL;
   1.102 +	size_t		j;
   1.103 +	struct record	*record;
   1.104  
   1.105 -	if (argc != 1) {
   1.106 -		return (CMD_USAGE);
   1.107 +	for (i = 1; i < argc; i++) {
   1.108 +		type = parse_field(argv[i], '~', &value);
   1.109 +		if (type == FIELD_UNKNOWN) {
   1.110 +			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
   1.111 +			goto out;
   1.112 +		}
   1.113 +		if (value[0] == '\0') {
   1.114 +			/* skip empty expressions */
   1.115 +			continue;
   1.116 +		}
   1.117 +		switch (type) {
   1.118 +		case FIELD_GROUP:
   1.119 +			repp = &group_re;
   1.120 +			break;
   1.121 +		case FIELD_TITLE:
   1.122 +			repp = &title_re;
   1.123 +			break;
   1.124 +		case FIELD_USERNAME:
   1.125 +			repp = &username_re;
   1.126 +			break;
   1.127 +		case FIELD_NOTES:
   1.128 +			repp = &notes_re;
   1.129 +			break;
   1.130 +		case FIELD_URL:
   1.131 +			repp = &url_re;
   1.132 +			break;
   1.133 +		default:
   1.134 +			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
   1.135 +			goto out;
   1.136 +		}
   1.137 +
   1.138 +		if (*repp == NULL) {
   1.139 +			*repp = xmalloc(sizeof (regex_t));
   1.140 +		} else {
   1.141 +			regfree(*repp);
   1.142 +		}
   1.143 +		errcode = regcomp(*repp, value, REG_EXTENDED | REG_NOSUB);
   1.144 +		if (errcode != 0) {
   1.145 +			errbuf_size = regerror(errcode, *repp, "", 0);
   1.146 +			errbuf = xmalloc(errbuf_size);
   1.147 +			regerror(errcode, *repp, errbuf, errbuf_size);
   1.148 +			fprintf(stderr, "bad regular expression \"%s\"\n",
   1.149 +			    errbuf);
   1.150 +			free(errbuf);
   1.151 +
   1.152 +			free(*repp);
   1.153 +			*repp = NULL;
   1.154 +
   1.155 +			goto out;
   1.156 +		}
   1.157  	}
   1.158  
   1.159  	list = pwfile_create_list(ctx);
   1.160 -	for (i = 0; list[i] != NULL; i++) {
   1.161 -		if (list[i]->any.type == ITEM_TYPE_GROUP) {
   1.162 -			printf("[%s]\n", list[i]->group.group);
   1.163 +	for (j = 0; list[j] != NULL; j++) {
   1.164 +		if (list[j]->any.type == ITEM_TYPE_GROUP) {
   1.165 +			printf("[%s]\n", list[j]->group.group);
   1.166  		} else {
   1.167 -			printf("%4u %s\n", list[i]->record.id,
   1.168 -			    (list[i]->record.title != NULL) ?
   1.169 -			    list[i]->record.title : "");
   1.170 +			record = pwfile_get_record(ctx, list[j]->record.id);
   1.171 +			if (((group_re == NULL) || (regexec(group_re,
   1.172 +			    record->group, 0, NULL, 0) == 0)) &&
   1.173 +			    ((title_re == NULL) || (regexec(title_re,
   1.174 +			    record->title, 0, NULL, 0) == 0)) &&
   1.175 +			    ((username_re == NULL) || (regexec(username_re,
   1.176 +			    record->username, 0, NULL, 0) == 0)) &&
   1.177 +			    ((notes_re == NULL) || (regexec(notes_re,
   1.178 +			    record->notes, 0, NULL, 0) == 0)) &&
   1.179 +			    ((url_re == NULL) || (regexec(url_re,
   1.180 +			    record->url, 0, NULL, 0) == 0))) {
   1.181 +				printf("%4u %s\n", list[j]->record.id,
   1.182 +				    (list[j]->record.title != NULL) ?
   1.183 +				    list[j]->record.title : "");
   1.184 +			}
   1.185 +			pwfile_destroy_record(record);
   1.186  		}
   1.187  	}
   1.188 +	retval = CMD_OK;
   1.189 +
   1.190 +out:
   1.191 +	if (group_re != NULL) {
   1.192 +		regfree(group_re);
   1.193 +		free(group_re);
   1.194 +	}
   1.195 +	if (title_re != NULL) {
   1.196 +		regfree(title_re);
   1.197 +		free(title_re);
   1.198 +	}
   1.199 +	if (username_re != NULL) {
   1.200 +		regfree(username_re);
   1.201 +		free(username_re);
   1.202 +	}
   1.203 +	if (notes_re != NULL) {
   1.204 +		regfree(notes_re);
   1.205 +		free(notes_re);
   1.206 +	}
   1.207 +	if (url_re != NULL) {
   1.208 +		regfree(url_re);
   1.209 +		free(url_re);
   1.210 +	}
   1.211 +
   1.212  	pwfile_destroy_list(list);
   1.213  
   1.214 -	return (CMD_OK);
   1.215 +	return (retval);
   1.216  }
   1.217  
   1.218  static enum cmd_return
   1.219 @@ -250,15 +311,44 @@
   1.220  {
   1.221  	int		i;
   1.222  	struct record record = { 0 };
   1.223 +	enum field_type	type;
   1.224 +	char		*value;
   1.225  
   1.226  	if (argc < 2) {
   1.227  		return (CMD_USAGE);
   1.228  	}
   1.229  
   1.230  	for (i = 1; i < argc; i++) {
   1.231 -		if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) {
   1.232 +		type = parse_field(argv[i], '=', &value);
   1.233 +		if (type == FIELD_UNKNOWN) {
   1.234  			fprintf(stderr, "bad field assignment \"%s\"\n",
   1.235  			    argv[i]);
   1.236 +		}
   1.237 +		if (value[0] == '\0') {
   1.238 +			/* skip empty assignments */
   1.239 +			continue;
   1.240 +		}
   1.241 +		switch (type) {
   1.242 +		case FIELD_GROUP:
   1.243 +			record.group = value;
   1.244 +			break;
   1.245 +		case FIELD_TITLE:
   1.246 +			record.title = value;
   1.247 +			break;
   1.248 +		case FIELD_USERNAME:
   1.249 +			record.username = value;
   1.250 +			break;
   1.251 +		case FIELD_PASSWORD:
   1.252 +			record.password = value;
   1.253 +			break;
   1.254 +		case FIELD_NOTES:
   1.255 +			record.notes = value;
   1.256 +			break;
   1.257 +		case FIELD_URL:
   1.258 +			record.url = value;
   1.259 +			break;
   1.260 +		default:
   1.261 +			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
   1.262  			return (CMD_ERR);
   1.263  		}
   1.264  	}
   1.265 @@ -273,7 +363,9 @@
   1.266  {
   1.267  	unsigned int	id;
   1.268  	int		i;
   1.269 -	struct record record = { 0 };
   1.270 +	struct record	record = { 0 };
   1.271 +	enum field_type	type;
   1.272 +	char		*value;
   1.273  
   1.274  	if (argc < 2) {
   1.275  		return (CMD_USAGE);
   1.276 @@ -285,11 +377,39 @@
   1.277  	}
   1.278  
   1.279  	for (i = 2; i < argc; i++) {
   1.280 -		if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) {
   1.281 +		type = parse_field(argv[i], '=', &value);
   1.282 +		if (type == FIELD_UNKNOWN) {
   1.283  			fprintf(stderr, "bad field assignment \"%s\"\n",
   1.284  			    argv[i]);
   1.285  			return (CMD_ERR);
   1.286  		}
   1.287 +		if (value[0] == '\0') {
   1.288 +			/* skip empty assignments */
   1.289 +			continue;
   1.290 +		}
   1.291 +		switch (type) {
   1.292 +		case FIELD_GROUP:
   1.293 +			record.group = value;
   1.294 +			break;
   1.295 +		case FIELD_TITLE:
   1.296 +			record.title = value;
   1.297 +			break;
   1.298 +		case FIELD_USERNAME:
   1.299 +			record.username = value;
   1.300 +			break;
   1.301 +		case FIELD_PASSWORD:
   1.302 +			record.password = value;
   1.303 +			break;
   1.304 +		case FIELD_NOTES:
   1.305 +			record.notes = value;
   1.306 +			break;
   1.307 +		case FIELD_URL:
   1.308 +			record.url = value;
   1.309 +			break;
   1.310 +		default:
   1.311 +			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
   1.312 +			return (CMD_ERR);
   1.313 +		}
   1.314  	}
   1.315  
   1.316  	pwfile_modify_record(ctx, id, &record);
   1.317 @@ -410,7 +530,7 @@
   1.318  	}
   1.319  
   1.320  	for (i = 2; i < argc; i++) {
   1.321 -		type = parse_field_name(argv[i]);
   1.322 +		type = parse_field(argv[i], '\0', NULL);
   1.323  		if (type < 0) {
   1.324  			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
   1.325  			return (CMD_ERR);
   1.326 @@ -448,7 +568,7 @@
   1.327  		return (CMD_ERR);
   1.328  	}
   1.329  
   1.330 -	type = parse_field_name(argv[2]);
   1.331 +	type = parse_field(argv[2], '\0', NULL);
   1.332  	if (type < 0) {
   1.333  		fprintf(stderr, "bad field name \"%s\"\n", argv[2]);
   1.334  		return (CMD_ERR);
     2.1 --- a/pwm.1.xml	Fri Jul 28 15:53:57 2017 +0200
     2.2 +++ b/pwm.1.xml	Mon Jul 31 09:20:21 2017 +0200
     2.3 @@ -34,7 +34,7 @@
     2.4        <email>guido+pwm@berhoerster.name</email>
     2.5        <personblurb/>
     2.6      </author>
     2.7 -    <date>28 July, 2017</date>
     2.8 +    <date>31 July, 2017</date>
     2.9    </info>
    2.10    <refmeta>
    2.11      <refentrytitle>pwm</refentrytitle>
    2.12 @@ -201,12 +201,21 @@
    2.13            <listitem>
    2.14              <cmdsynopsis>
    2.15                <command>list</command>
    2.16 +              <arg choice="opt" rep="repeat">
    2.17 +                <replaceable>field</replaceable>~<replaceable>regex</replaceable>
    2.18 +              </arg>
    2.19              </cmdsynopsis>
    2.20              <cmdsynopsis>
    2.21                <command>ls</command>
    2.22 +              <arg choice="opt" rep="repeat">
    2.23 +                <replaceable>field</replaceable>~<replaceable>regex</replaceable>
    2.24 +              </arg>
    2.25                <sbr/>
    2.26              </cmdsynopsis>
    2.27 -            <para>List all password database entries.</para>
    2.28 +            <para>List password database entries. If one or more filter
    2.29 +            expressions are specified, limit the displayed entries to those
    2.30 +            whose <replaceable>field</replaceable> content matches the extended
    2.31 +            regular expression <replaceable>regex</replaceable>.</para>
    2.32            </listitem>
    2.33          </varlistentry>
    2.34          <varlistentry>
    2.35 @@ -529,6 +538,8 @@
    2.36        <manvolnum>1</manvolnum></citerefentry>,
    2.37        <citerefentry><refentrytitle>locale</refentrytitle>
    2.38        <manvolnum>5</manvolnum></citerefentry>,
    2.39 +      <citerefentry><refentrytitle>regex</refentrytitle>
    2.40 +      <manvolnum>5</manvolnum></citerefentry>,
    2.41        <link xlink:href="https://pwsafe.org/"/></para>
    2.42    </refsect1>
    2.43  </refentry>