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, 28 Jul 2017 15:53:57 +0200
parents 60c8ab006e55
children 85bce13237cf
files cmd.c pwfile.c pwfile.h pwm.1.xml
diffstat 4 files changed, 259 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/cmd.c	Fri Jul 28 09:53:46 2017 +0200
+++ b/cmd.c	Fri Jul 28 15:53:57 2017 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
+ * 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
@@ -33,12 +33,16 @@
 #endif /* READPASSPHRASE_H */
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 #include <unistd.h>
 
 #include "cmd.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)
+
 enum field_type {
 	FIELD_UNKNOWN = -1,
 	FIELD_GROUP,
@@ -46,9 +50,12 @@
 	FIELD_USERNAME,
 	FIELD_PASSWORD,
 	FIELD_NOTES,
-	FIELD_URL
+	FIELD_URL,
+	FIELD_MTIME,
+	FIELD_CTIME
 };
 
+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 *[]);
@@ -68,7 +75,9 @@
     "username",
     "password",
     "notes",
-    "url"
+    "url",
+    "ctime",
+    "mtime"
 };
 
 static const char *field_labels[] = {
@@ -77,10 +86,14 @@
     "Username: ",
     "Password: ",
     "Notes:    ",
-    "URL:      "
+    "URL:      ",
+    "Created:  ",
+    "Modified: "
 };
 
 static struct cmd cmds[] = {
+    { "i", "info", "info", "Show metadata information about the current file",
+    cmd_info },
     { "ls", "list", "list", "List entries", cmd_list },
     { "c", "create", "create field=value ...", "Create entry", cmd_create },
     { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify },
@@ -150,7 +163,7 @@
 				record->url = value;
 				break;
 			default:
-				break;
+				return (FIELD_UNKNOWN);
 			}
 			return (i);
 		}
@@ -177,6 +190,37 @@
 }
 
 static enum cmd_return
+cmd_info(struct pwm_ctx *ctx, int argc, char *argv[])
+{
+	struct metadata	*metadata;
+	struct tm	*tm;
+	char		timebuf[TIME_SIZE];
+
+	if (argc != 1) {
+		return (CMD_USAGE);
+	}
+
+	metadata = pwfile_get_metadata(ctx);
+	printf("Format:      0x%04x\n", metadata->version);
+	if (metadata->user != NULL) {
+		printf("User:        %s\n", metadata->user);
+	}
+	if (metadata->user != NULL) {
+		printf("Host:        %s\n", metadata->host);
+	}
+	if (metadata->user != NULL) {
+		printf("Application: %s\n", metadata->application);
+	}
+	tm = gmtime(&metadata->timestamp);
+	strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
+	printf("Last Saved:  %s\n", timebuf);
+
+	pwfile_destroy_metadata(metadata);
+
+	return (CMD_OK);
+}
+
+static enum cmd_return
 cmd_list(struct pwm_ctx *ctx, int argc, char *argv[])
 {
 	union list_item	**list;
@@ -290,6 +334,9 @@
 static void
 print_record(struct record *record, int fields[], int show_labels, FILE *fp)
 {
+	struct tm	*tm;
+	char		timebuf[TIME_SIZE];
+
 	if (fields[FIELD_TITLE]) {
 		if (print_field(field_labels[FIELD_TITLE], record->title,
 		    show_labels, fp) != 0) {
@@ -326,6 +373,22 @@
 			return;
 		}
 	}
+	if (fields[FIELD_CTIME]) {
+		tm = gmtime(&record->ctime);
+		strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
+		if (print_field(field_labels[FIELD_CTIME], timebuf,
+		    show_labels, fp)) {
+			return;
+		}
+	}
+	if (fields[FIELD_MTIME]) {
+		tm = gmtime(&record->mtime);
+		strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
+		if (print_field(field_labels[FIELD_MTIME], timebuf,
+		    show_labels, fp)) {
+			return;
+		}
+	}
 }
 
 static enum cmd_return
--- a/pwfile.c	Fri Jul 28 09:53:46 2017 +0200
+++ b/pwfile.c	Fri Jul 28 15:53:57 2017 +0200
@@ -23,6 +23,7 @@
 
 #include "compat.h"
 
+#include <ctype.h>
 #ifdef	HAVE_ERR_H
 #include <err.h>
 #endif /* HAVE_ERR_H */
@@ -597,6 +598,94 @@
 	free(list);
 }
 
+static int
+parse_user_host(const char *user_host, char **userp, char **hostp)
+{
+	size_t		user_host_len;
+	size_t		i;
+	unsigned int	user_len;
+
+	user_host_len = strlen(user_host);
+	if (user_host_len < 4) {
+		return (-1);
+	}
+	for (i = 0; i < 4; i++) {
+		if (!isxdigit(user_host[i])) {
+			return (-1);
+		}
+	}
+	if (sscanf(user_host, "%04x", &user_len) != 1) {
+		return (-1);
+	}
+	if (4 + (size_t)user_len > user_host_len) {
+		return (-1);
+	}
+
+	xasprintf(userp, "%.*s", (int)user_len, user_host + 4);
+	xasprintf(hostp, "%s", user_host + 4 + user_len);
+
+	return (0);
+}
+
+struct metadata *
+pwfile_get_metadata(struct pwm_ctx *ctx)
+{
+	struct metadata	*metadata;
+	struct pws3_field *version_field;
+	struct pws3_field *save_app_field;
+	struct pws3_field *save_timestamp_field;
+	struct pws3_field *save_user_field;
+	struct pws3_field *save_host_field;
+	struct pws3_field *save_user_host_field;
+
+	metadata = xmalloc(sizeof (struct metadata));
+
+	version_field = pws3_file_get_header_field(ctx->file,
+	    PWS3_HEADER_FIELD_VERSION);
+	metadata->version = pws3_field_get_uint16(version_field);
+
+	save_app_field = pws3_file_get_header_field(ctx->file,
+	    PWS3_HEADER_FIELD_SAVE_APPLICATION);
+	metadata->application = (save_app_field != NULL) ?
+	    xstrdup(pws3_field_get_text(save_app_field)) : NULL;
+
+	save_timestamp_field = pws3_file_get_header_field(ctx->file,
+	    PWS3_HEADER_FIELD_SAVE_TIMESTAMP);
+	metadata->timestamp = (save_timestamp_field != NULL) ?
+	    pws3_field_get_time(save_timestamp_field) : 0;
+
+	save_user_field = pws3_file_get_header_field(ctx->file,
+	    PWS3_HEADER_FIELD_SAVE_USER);
+	save_host_field = pws3_file_get_header_field(ctx->file,
+	    PWS3_HEADER_FIELD_SAVE_HOST);
+	save_user_host_field = pws3_file_get_header_field(ctx->file,
+	    PWS3_HEADER_FIELD_SAVE_USER_HOST);
+	metadata->user = NULL;
+	metadata->host = NULL;
+	if ((save_user_field != NULL) && (save_host_field != NULL)) {
+		metadata->user = xstrdup(pws3_field_get_text(save_user_field));
+		metadata->host = xstrdup(pws3_field_get_text(save_host_field));
+	} else if (save_user_host_field != NULL) {
+		parse_user_host(pws3_field_get_text(save_user_host_field),
+		    &metadata->user, &metadata->host);
+	}
+
+	return (metadata);
+}
+
+void
+pwfile_destroy_metadata(struct metadata *metadata)
+{
+	if (metadata == NULL) {
+		return;
+	}
+
+	free(metadata->user);
+	free(metadata->host);
+	free(metadata->application);
+	free(metadata);
+}
+
 static void
 update_record(struct pws3_record *pws3_record, struct record *record)
 {
@@ -820,6 +909,8 @@
 	struct record	*record;
 	const unsigned char *uuid;
 	struct pws3_record *pws3_record;
+	struct pws3_field *ctime_field;
+	struct pws3_field *mtime_field;
 	struct pws3_field *title_field;
 	struct pws3_field *group_field;
 	struct pws3_field *username_field;
@@ -835,6 +926,16 @@
 
 	record = xmalloc(sizeof (struct record));
 
+	ctime_field = pws3_record_get_field(pws3_record,
+	    PWS3_RECORD_FIELD_CREATION_TIME);
+	record->ctime = (ctime_field != NULL) ?
+	    pws3_field_get_time(ctime_field) : (time_t)0;
+
+	mtime_field = pws3_record_get_field(pws3_record,
+	    PWS3_RECORD_FIELD_MODIFICATION_TIME);
+	record->mtime = (mtime_field != NULL) ?
+	    pws3_field_get_time(mtime_field) : (time_t)0;
+
 	title_field = pws3_record_get_field(pws3_record,
 	    PWS3_RECORD_FIELD_TITLE);
 	record->title = (title_field != NULL) ?
--- a/pwfile.h	Fri Jul 28 09:53:46 2017 +0200
+++ b/pwfile.h	Fri Jul 28 15:53:57 2017 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
+ * 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
@@ -51,6 +51,14 @@
 
 struct record_id_tree;
 
+struct metadata {
+	int	version;
+	char	*user;
+	char	*host;
+	char	*application;
+	time_t	timestamp;
+};
+
 struct record {
 	char	*title;
 	char	*group;
@@ -58,6 +66,8 @@
 	char	*password;
 	char	*notes;
 	char	*url;
+	time_t	ctime;
+	time_t	mtime;
 };
 
 void		pwfile_init(struct pwm_ctx *ctx);
@@ -66,6 +76,8 @@
 int		pwfile_write_file(struct pwm_ctx *);
 union list_item ** pwfile_create_list(struct pwm_ctx *);
 void		pwfile_destroy_list(union list_item **);
+struct metadata * pwfile_get_metadata(struct pwm_ctx *);
+void		pwfile_destroy_metadata(struct metadata *);
 int		pwfile_create_record(struct pwm_ctx *, struct record *);
 int		pwfile_modify_record(struct pwm_ctx *, unsigned int,
     struct record *);
--- a/pwm.1.xml	Fri Jul 28 09:53:46 2017 +0200
+++ b/pwm.1.xml	Fri Jul 28 15:53:57 2017 +0200
@@ -34,7 +34,7 @@
       <email>guido+pwm@berhoerster.name</email>
       <personblurb/>
     </author>
-    <date>3 February, 2017</date>
+    <date>28 July, 2017</date>
   </info>
   <refmeta>
     <refentrytitle>pwm</refentrytitle>
@@ -80,11 +80,11 @@
     character encoding.</para>
     <refsect2>
       <title>Output format</title>
-      <para>The <command>show</command> command displays selected fields by
-      printing the field name followed by a colon, one or more space characters
-      and the field's verbatim content to the standard output stream. Field
-      values may contain newlines, non-printable and/or control
-      characters.</para>
+      <para>The <command>show</command> and <command>info</command> commands
+      display fields by printing the field name followed by a colon, one or
+      more space characters and the field's verbatim content to the standard
+      output stream. Field content may contain newlines, non-printable and/or
+      control characters.</para>
       <para>The <command>pipe</command> prints the verbatim field content to the
       standard input stream of the given command.</para>
       <para>Error messages are printed to the standard error stream.</para>
@@ -128,16 +128,52 @@
     </refsect2>
     <refsect2>
       <title>Fields</title>
-      <para>The following entry fields are supported:
-      <simplelist type="vert">
-        <member>group</member>
-        <member>title</member>
-        <member>username</member>
-        <member>password</member>
-        <member>notes</member>
-        <member>url</member>
-      </simplelist>
-      </para>
+      <para>The following entry fields are supported:</para>
+      <table xml:id="field-table">
+        <title>Fields and their identifiers</title>
+        <tgroup cols="2" align="left" colsep="1" rowsep="1">
+          <thead>
+            <row>
+              <entry>Field</entry>
+              <entry>Field Identifier</entry>
+            </row>
+          </thead>
+          <tbody>
+            <row>
+              <entry>Group</entry>
+              <entry>group</entry>
+            </row>
+            <row>
+              <entry>Title</entry>
+              <entry>title</entry>
+            </row>
+            <row>
+              <entry>Username</entry>
+              <entry>username</entry>
+            </row>
+            <row>
+              <entry>Password</entry>
+              <entry>password</entry>
+            </row>
+            <row>
+              <entry>Notes</entry>
+              <entry>notes</entry>
+            </row>
+            <row>
+              <entry>URL</entry>
+              <entry>url</entry>
+            </row>
+            <row>
+              <entry>Creation Time</entry>
+              <entry>ctime</entry>
+            </row>
+            <row>
+              <entry>Modification Time</entry>
+              <entry>mtime</entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </table>
       <para>Other, existing fields specified by the PasswordSafe file format
       will be preserved but cannot be displayed or modified.</para>
     </refsect2>
@@ -374,6 +410,22 @@
           </listitem>
         </varlistentry>
         <varlistentry>
+          <term>Show metadata information</term>
+          <listitem>
+            <cmdsynopsis>
+              <command>info</command>
+            </cmdsynopsis>
+            <cmdsynopsis>
+              <command>i</command>
+              <sbr/>
+            </cmdsynopsis>
+            <para>Display metadata information such as the user who last wrote
+            to the database, the time when the database was last written to,
+            and the host on which the password database was last written
+            to.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
           <term>Write database</term>
           <listitem>
             <cmdsynopsis>
@@ -423,6 +475,15 @@
           <manvolnum>5</manvolnum></citerefentry></para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>
+          <literal>LOGNAME</literal>
+        </term>
+        <listitem>
+          <para>The name of the logged in user which is recorded when writing
+          the password database</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
   <refsect1>