projects/pwm

changeset 10:17fb30016e64

Enable access to record and file metadata

Add info command to show file metadata.
Enable display of creation and modification dates of records.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Fri Jul 28 15:53:57 2017 +0200 (2017-07-28)
parents 60c8ab006e55
children 85bce13237cf
files cmd.c pwfile.c pwfile.h pwm.1.xml
line diff
     1.1 --- a/cmd.c	Fri Jul 28 09:53:46 2017 +0200
     1.2 +++ b/cmd.c	Fri Jul 28 15:53:57 2017 +0200
     1.3 @@ -1,5 +1,5 @@
     1.4  /*
     1.5 - * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
     1.6 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     1.7   *
     1.8   * Permission is hereby granted, free of charge, to any person obtaining
     1.9   * a copy of this software and associated documentation files (the
    1.10 @@ -33,12 +33,16 @@
    1.11  #endif /* READPASSPHRASE_H */
    1.12  #include <stdlib.h>
    1.13  #include <string.h>
    1.14 +#include <time.h>
    1.15  #include <unistd.h>
    1.16  
    1.17  #include "cmd.h"
    1.18  #include "pwfile.h"
    1.19  #include "util.h"
    1.20  
    1.21 +#define	TIME_FORMAT	"%Y-%m-%dT%TZ"
    1.22 +#define	TIME_SIZE	(4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1)
    1.23 +
    1.24  enum field_type {
    1.25  	FIELD_UNKNOWN = -1,
    1.26  	FIELD_GROUP,
    1.27 @@ -46,9 +50,12 @@
    1.28  	FIELD_USERNAME,
    1.29  	FIELD_PASSWORD,
    1.30  	FIELD_NOTES,
    1.31 -	FIELD_URL
    1.32 +	FIELD_URL,
    1.33 +	FIELD_MTIME,
    1.34 +	FIELD_CTIME
    1.35  };
    1.36  
    1.37 +static enum cmd_return	cmd_info(struct pwm_ctx *, int, char *[]);
    1.38  static enum cmd_return	cmd_list(struct pwm_ctx *, int, char *[]);
    1.39  static enum cmd_return	cmd_create(struct pwm_ctx *, int, char *[]);
    1.40  static enum cmd_return	cmd_modify(struct pwm_ctx *, int, char *[]);
    1.41 @@ -68,7 +75,9 @@
    1.42      "username",
    1.43      "password",
    1.44      "notes",
    1.45 -    "url"
    1.46 +    "url",
    1.47 +    "ctime",
    1.48 +    "mtime"
    1.49  };
    1.50  
    1.51  static const char *field_labels[] = {
    1.52 @@ -77,10 +86,14 @@
    1.53      "Username: ",
    1.54      "Password: ",
    1.55      "Notes:    ",
    1.56 -    "URL:      "
    1.57 +    "URL:      ",
    1.58 +    "Created:  ",
    1.59 +    "Modified: "
    1.60  };
    1.61  
    1.62  static struct cmd cmds[] = {
    1.63 +    { "i", "info", "info", "Show metadata information about the current file",
    1.64 +    cmd_info },
    1.65      { "ls", "list", "list", "List entries", cmd_list },
    1.66      { "c", "create", "create field=value ...", "Create entry", cmd_create },
    1.67      { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify },
    1.68 @@ -150,7 +163,7 @@
    1.69  				record->url = value;
    1.70  				break;
    1.71  			default:
    1.72 -				break;
    1.73 +				return (FIELD_UNKNOWN);
    1.74  			}
    1.75  			return (i);
    1.76  		}
    1.77 @@ -177,6 +190,37 @@
    1.78  }
    1.79  
    1.80  static enum cmd_return
    1.81 +cmd_info(struct pwm_ctx *ctx, int argc, char *argv[])
    1.82 +{
    1.83 +	struct metadata	*metadata;
    1.84 +	struct tm	*tm;
    1.85 +	char		timebuf[TIME_SIZE];
    1.86 +
    1.87 +	if (argc != 1) {
    1.88 +		return (CMD_USAGE);
    1.89 +	}
    1.90 +
    1.91 +	metadata = pwfile_get_metadata(ctx);
    1.92 +	printf("Format:      0x%04x\n", metadata->version);
    1.93 +	if (metadata->user != NULL) {
    1.94 +		printf("User:        %s\n", metadata->user);
    1.95 +	}
    1.96 +	if (metadata->user != NULL) {
    1.97 +		printf("Host:        %s\n", metadata->host);
    1.98 +	}
    1.99 +	if (metadata->user != NULL) {
   1.100 +		printf("Application: %s\n", metadata->application);
   1.101 +	}
   1.102 +	tm = gmtime(&metadata->timestamp);
   1.103 +	strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
   1.104 +	printf("Last Saved:  %s\n", timebuf);
   1.105 +
   1.106 +	pwfile_destroy_metadata(metadata);
   1.107 +
   1.108 +	return (CMD_OK);
   1.109 +}
   1.110 +
   1.111 +static enum cmd_return
   1.112  cmd_list(struct pwm_ctx *ctx, int argc, char *argv[])
   1.113  {
   1.114  	union list_item	**list;
   1.115 @@ -290,6 +334,9 @@
   1.116  static void
   1.117  print_record(struct record *record, int fields[], int show_labels, FILE *fp)
   1.118  {
   1.119 +	struct tm	*tm;
   1.120 +	char		timebuf[TIME_SIZE];
   1.121 +
   1.122  	if (fields[FIELD_TITLE]) {
   1.123  		if (print_field(field_labels[FIELD_TITLE], record->title,
   1.124  		    show_labels, fp) != 0) {
   1.125 @@ -326,6 +373,22 @@
   1.126  			return;
   1.127  		}
   1.128  	}
   1.129 +	if (fields[FIELD_CTIME]) {
   1.130 +		tm = gmtime(&record->ctime);
   1.131 +		strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
   1.132 +		if (print_field(field_labels[FIELD_CTIME], timebuf,
   1.133 +		    show_labels, fp)) {
   1.134 +			return;
   1.135 +		}
   1.136 +	}
   1.137 +	if (fields[FIELD_MTIME]) {
   1.138 +		tm = gmtime(&record->mtime);
   1.139 +		strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
   1.140 +		if (print_field(field_labels[FIELD_MTIME], timebuf,
   1.141 +		    show_labels, fp)) {
   1.142 +			return;
   1.143 +		}
   1.144 +	}
   1.145  }
   1.146  
   1.147  static enum cmd_return
     2.1 --- a/pwfile.c	Fri Jul 28 09:53:46 2017 +0200
     2.2 +++ b/pwfile.c	Fri Jul 28 15:53:57 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 @@ -597,6 +598,94 @@
    2.12  	free(list);
    2.13  }
    2.14  
    2.15 +static int
    2.16 +parse_user_host(const char *user_host, char **userp, char **hostp)
    2.17 +{
    2.18 +	size_t		user_host_len;
    2.19 +	size_t		i;
    2.20 +	unsigned int	user_len;
    2.21 +
    2.22 +	user_host_len = strlen(user_host);
    2.23 +	if (user_host_len < 4) {
    2.24 +		return (-1);
    2.25 +	}
    2.26 +	for (i = 0; i < 4; i++) {
    2.27 +		if (!isxdigit(user_host[i])) {
    2.28 +			return (-1);
    2.29 +		}
    2.30 +	}
    2.31 +	if (sscanf(user_host, "%04x", &user_len) != 1) {
    2.32 +		return (-1);
    2.33 +	}
    2.34 +	if (4 + (size_t)user_len > user_host_len) {
    2.35 +		return (-1);
    2.36 +	}
    2.37 +
    2.38 +	xasprintf(userp, "%.*s", (int)user_len, user_host + 4);
    2.39 +	xasprintf(hostp, "%s", user_host + 4 + user_len);
    2.40 +
    2.41 +	return (0);
    2.42 +}
    2.43 +
    2.44 +struct metadata *
    2.45 +pwfile_get_metadata(struct pwm_ctx *ctx)
    2.46 +{
    2.47 +	struct metadata	*metadata;
    2.48 +	struct pws3_field *version_field;
    2.49 +	struct pws3_field *save_app_field;
    2.50 +	struct pws3_field *save_timestamp_field;
    2.51 +	struct pws3_field *save_user_field;
    2.52 +	struct pws3_field *save_host_field;
    2.53 +	struct pws3_field *save_user_host_field;
    2.54 +
    2.55 +	metadata = xmalloc(sizeof (struct metadata));
    2.56 +
    2.57 +	version_field = pws3_file_get_header_field(ctx->file,
    2.58 +	    PWS3_HEADER_FIELD_VERSION);
    2.59 +	metadata->version = pws3_field_get_uint16(version_field);
    2.60 +
    2.61 +	save_app_field = pws3_file_get_header_field(ctx->file,
    2.62 +	    PWS3_HEADER_FIELD_SAVE_APPLICATION);
    2.63 +	metadata->application = (save_app_field != NULL) ?
    2.64 +	    xstrdup(pws3_field_get_text(save_app_field)) : NULL;
    2.65 +
    2.66 +	save_timestamp_field = pws3_file_get_header_field(ctx->file,
    2.67 +	    PWS3_HEADER_FIELD_SAVE_TIMESTAMP);
    2.68 +	metadata->timestamp = (save_timestamp_field != NULL) ?
    2.69 +	    pws3_field_get_time(save_timestamp_field) : 0;
    2.70 +
    2.71 +	save_user_field = pws3_file_get_header_field(ctx->file,
    2.72 +	    PWS3_HEADER_FIELD_SAVE_USER);
    2.73 +	save_host_field = pws3_file_get_header_field(ctx->file,
    2.74 +	    PWS3_HEADER_FIELD_SAVE_HOST);
    2.75 +	save_user_host_field = pws3_file_get_header_field(ctx->file,
    2.76 +	    PWS3_HEADER_FIELD_SAVE_USER_HOST);
    2.77 +	metadata->user = NULL;
    2.78 +	metadata->host = NULL;
    2.79 +	if ((save_user_field != NULL) && (save_host_field != NULL)) {
    2.80 +		metadata->user = xstrdup(pws3_field_get_text(save_user_field));
    2.81 +		metadata->host = xstrdup(pws3_field_get_text(save_host_field));
    2.82 +	} else if (save_user_host_field != NULL) {
    2.83 +		parse_user_host(pws3_field_get_text(save_user_host_field),
    2.84 +		    &metadata->user, &metadata->host);
    2.85 +	}
    2.86 +
    2.87 +	return (metadata);
    2.88 +}
    2.89 +
    2.90 +void
    2.91 +pwfile_destroy_metadata(struct metadata *metadata)
    2.92 +{
    2.93 +	if (metadata == NULL) {
    2.94 +		return;
    2.95 +	}
    2.96 +
    2.97 +	free(metadata->user);
    2.98 +	free(metadata->host);
    2.99 +	free(metadata->application);
   2.100 +	free(metadata);
   2.101 +}
   2.102 +
   2.103  static void
   2.104  update_record(struct pws3_record *pws3_record, struct record *record)
   2.105  {
   2.106 @@ -820,6 +909,8 @@
   2.107  	struct record	*record;
   2.108  	const unsigned char *uuid;
   2.109  	struct pws3_record *pws3_record;
   2.110 +	struct pws3_field *ctime_field;
   2.111 +	struct pws3_field *mtime_field;
   2.112  	struct pws3_field *title_field;
   2.113  	struct pws3_field *group_field;
   2.114  	struct pws3_field *username_field;
   2.115 @@ -835,6 +926,16 @@
   2.116  
   2.117  	record = xmalloc(sizeof (struct record));
   2.118  
   2.119 +	ctime_field = pws3_record_get_field(pws3_record,
   2.120 +	    PWS3_RECORD_FIELD_CREATION_TIME);
   2.121 +	record->ctime = (ctime_field != NULL) ?
   2.122 +	    pws3_field_get_time(ctime_field) : (time_t)0;
   2.123 +
   2.124 +	mtime_field = pws3_record_get_field(pws3_record,
   2.125 +	    PWS3_RECORD_FIELD_MODIFICATION_TIME);
   2.126 +	record->mtime = (mtime_field != NULL) ?
   2.127 +	    pws3_field_get_time(mtime_field) : (time_t)0;
   2.128 +
   2.129  	title_field = pws3_record_get_field(pws3_record,
   2.130  	    PWS3_RECORD_FIELD_TITLE);
   2.131  	record->title = (title_field != NULL) ?
     3.1 --- a/pwfile.h	Fri Jul 28 09:53:46 2017 +0200
     3.2 +++ b/pwfile.h	Fri Jul 28 15:53:57 2017 +0200
     3.3 @@ -1,5 +1,5 @@
     3.4  /*
     3.5 - * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
     3.6 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     3.7   *
     3.8   * Permission is hereby granted, free of charge, to any person obtaining
     3.9   * a copy of this software and associated documentation files (the
    3.10 @@ -51,6 +51,14 @@
    3.11  
    3.12  struct record_id_tree;
    3.13  
    3.14 +struct metadata {
    3.15 +	int	version;
    3.16 +	char	*user;
    3.17 +	char	*host;
    3.18 +	char	*application;
    3.19 +	time_t	timestamp;
    3.20 +};
    3.21 +
    3.22  struct record {
    3.23  	char	*title;
    3.24  	char	*group;
    3.25 @@ -58,6 +66,8 @@
    3.26  	char	*password;
    3.27  	char	*notes;
    3.28  	char	*url;
    3.29 +	time_t	ctime;
    3.30 +	time_t	mtime;
    3.31  };
    3.32  
    3.33  void		pwfile_init(struct pwm_ctx *ctx);
    3.34 @@ -66,6 +76,8 @@
    3.35  int		pwfile_write_file(struct pwm_ctx *);
    3.36  union list_item ** pwfile_create_list(struct pwm_ctx *);
    3.37  void		pwfile_destroy_list(union list_item **);
    3.38 +struct metadata * pwfile_get_metadata(struct pwm_ctx *);
    3.39 +void		pwfile_destroy_metadata(struct metadata *);
    3.40  int		pwfile_create_record(struct pwm_ctx *, struct record *);
    3.41  int		pwfile_modify_record(struct pwm_ctx *, unsigned int,
    3.42      struct record *);
     4.1 --- a/pwm.1.xml	Fri Jul 28 09:53:46 2017 +0200
     4.2 +++ b/pwm.1.xml	Fri Jul 28 15:53:57 2017 +0200
     4.3 @@ -34,7 +34,7 @@
     4.4        <email>guido+pwm@berhoerster.name</email>
     4.5        <personblurb/>
     4.6      </author>
     4.7 -    <date>3 February, 2017</date>
     4.8 +    <date>28 July, 2017</date>
     4.9    </info>
    4.10    <refmeta>
    4.11      <refentrytitle>pwm</refentrytitle>
    4.12 @@ -80,11 +80,11 @@
    4.13      character encoding.</para>
    4.14      <refsect2>
    4.15        <title>Output format</title>
    4.16 -      <para>The <command>show</command> command displays selected fields by
    4.17 -      printing the field name followed by a colon, one or more space characters
    4.18 -      and the field's verbatim content to the standard output stream. Field
    4.19 -      values may contain newlines, non-printable and/or control
    4.20 -      characters.</para>
    4.21 +      <para>The <command>show</command> and <command>info</command> commands
    4.22 +      display fields by printing the field name followed by a colon, one or
    4.23 +      more space characters and the field's verbatim content to the standard
    4.24 +      output stream. Field content may contain newlines, non-printable and/or
    4.25 +      control characters.</para>
    4.26        <para>The <command>pipe</command> prints the verbatim field content to the
    4.27        standard input stream of the given command.</para>
    4.28        <para>Error messages are printed to the standard error stream.</para>
    4.29 @@ -128,16 +128,52 @@
    4.30      </refsect2>
    4.31      <refsect2>
    4.32        <title>Fields</title>
    4.33 -      <para>The following entry fields are supported:
    4.34 -      <simplelist type="vert">
    4.35 -        <member>group</member>
    4.36 -        <member>title</member>
    4.37 -        <member>username</member>
    4.38 -        <member>password</member>
    4.39 -        <member>notes</member>
    4.40 -        <member>url</member>
    4.41 -      </simplelist>
    4.42 -      </para>
    4.43 +      <para>The following entry fields are supported:</para>
    4.44 +      <table xml:id="field-table">
    4.45 +        <title>Fields and their identifiers</title>
    4.46 +        <tgroup cols="2" align="left" colsep="1" rowsep="1">
    4.47 +          <thead>
    4.48 +            <row>
    4.49 +              <entry>Field</entry>
    4.50 +              <entry>Field Identifier</entry>
    4.51 +            </row>
    4.52 +          </thead>
    4.53 +          <tbody>
    4.54 +            <row>
    4.55 +              <entry>Group</entry>
    4.56 +              <entry>group</entry>
    4.57 +            </row>
    4.58 +            <row>
    4.59 +              <entry>Title</entry>
    4.60 +              <entry>title</entry>
    4.61 +            </row>
    4.62 +            <row>
    4.63 +              <entry>Username</entry>
    4.64 +              <entry>username</entry>
    4.65 +            </row>
    4.66 +            <row>
    4.67 +              <entry>Password</entry>
    4.68 +              <entry>password</entry>
    4.69 +            </row>
    4.70 +            <row>
    4.71 +              <entry>Notes</entry>
    4.72 +              <entry>notes</entry>
    4.73 +            </row>
    4.74 +            <row>
    4.75 +              <entry>URL</entry>
    4.76 +              <entry>url</entry>
    4.77 +            </row>
    4.78 +            <row>
    4.79 +              <entry>Creation Time</entry>
    4.80 +              <entry>ctime</entry>
    4.81 +            </row>
    4.82 +            <row>
    4.83 +              <entry>Modification Time</entry>
    4.84 +              <entry>mtime</entry>
    4.85 +            </row>
    4.86 +          </tbody>
    4.87 +        </tgroup>
    4.88 +      </table>
    4.89        <para>Other, existing fields specified by the PasswordSafe file format
    4.90        will be preserved but cannot be displayed or modified.</para>
    4.91      </refsect2>
    4.92 @@ -374,6 +410,22 @@
    4.93            </listitem>
    4.94          </varlistentry>
    4.95          <varlistentry>
    4.96 +          <term>Show metadata information</term>
    4.97 +          <listitem>
    4.98 +            <cmdsynopsis>
    4.99 +              <command>info</command>
   4.100 +            </cmdsynopsis>
   4.101 +            <cmdsynopsis>
   4.102 +              <command>i</command>
   4.103 +              <sbr/>
   4.104 +            </cmdsynopsis>
   4.105 +            <para>Display metadata information such as the user who last wrote
   4.106 +            to the database, the time when the database was last written to,
   4.107 +            and the host on which the password database was last written
   4.108 +            to.</para>
   4.109 +          </listitem>
   4.110 +        </varlistentry>
   4.111 +        <varlistentry>
   4.112            <term>Write database</term>
   4.113            <listitem>
   4.114              <cmdsynopsis>
   4.115 @@ -423,6 +475,15 @@
   4.116            <manvolnum>5</manvolnum></citerefentry></para>
   4.117          </listitem>
   4.118        </varlistentry>
   4.119 +      <varlistentry>
   4.120 +        <term>
   4.121 +          <literal>LOGNAME</literal>
   4.122 +        </term>
   4.123 +        <listitem>
   4.124 +          <para>The name of the logged in user which is recorded when writing
   4.125 +          the password database</para>
   4.126 +        </listitem>
   4.127 +      </varlistentry>
   4.128      </variablelist>
   4.129    </refsect1>
   4.130    <refsect1>