Hello community,

here is the log from the commit of package lastpass-cli for openSUSE:Factory 
checked in at 2017-06-09 15:57:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/lastpass-cli (Old)
 and      /work/SRC/openSUSE:Factory/.lastpass-cli.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "lastpass-cli"

Fri Jun  9 15:57:22 2017 rev:4 rq:502375 version:1.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/lastpass-cli/lastpass-cli.changes        
2017-02-21 13:49:03.603249418 +0100
+++ /work/SRC/openSUSE:Factory/.lastpass-cli.new/lastpass-cli.changes   
2017-06-09 15:57:23.319827802 +0200
@@ -1,0 +2,19 @@
+Thu Jun  8 12:24:19 UTC 2017 - [email protected]
+
+- Update to version 1.2.0:
+  * lpass show now supports new-style multiline ssh keys
+  * lpass export now supports --fields=FIELDLIST argument to
+  * control output, with patches from Kyle Burton
+  * lpass ls now always shows empty shared folders
+  * lpass edit can now set the 'master password reprompt' field in
+    sites
+  * lpass share create now shows the created share name
+  * Bugfix: crash in lpass show fixed by Kyle Burton
+  * build fixes for termux and documentation updates, from
+    Christian Rondeau
+  * documentation updates for Ubuntu from Craig Menning and Glenn
+    Oppegard
+  * Test suite now included covering basic operations
+- Enable tests
+
+-------------------------------------------------------------------

Old:
----
  lastpass-cli-1.1.2.tar.gz

New:
----
  lastpass-cli-1.2.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ lastpass-cli.spec ++++++
--- /var/tmp/diff_new_pack.qALdkO/_old  2017-06-09 15:57:24.083719985 +0200
+++ /var/tmp/diff_new_pack.qALdkO/_new  2017-06-09 15:57:24.083719985 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           lastpass-cli
-Version:        1.1.2
+Version:        1.2.0
 Release:        0
 Summary:        LastPass command line interface tool
 License:        GPL-2.0
@@ -46,12 +46,16 @@
 %setup -q
 
 %build
-%cmake
+%cmake \
+  -DTEST_BUILD=ON
 make %{?_smp_mflags} all doc-man
 
 %install
 %cmake_install install-doc
 
+%check
+make %{?_smp_mflags} test
+
 %files
 %defattr(-,root,root)
 %doc CHANGELOG.md README.md COPYING

++++++ lastpass-cli-1.1.2.tar.gz -> lastpass-cli-1.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/.travis.yml 
new/lastpass-cli-1.2.0/.travis.yml
--- old/lastpass-cli-1.1.2/.travis.yml  2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/.travis.yml  2017-06-07 21:41:30.000000000 +0200
@@ -18,4 +18,4 @@
 - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update; fi
 - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y 
libxml2-dev; fi
 
-script: mkdir build && cd build && cmake .. && make
+script: make && make test
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/CHANGELOG.md 
new/lastpass-cli-1.2.0/CHANGELOG.md
--- old/lastpass-cli-1.1.2/CHANGELOG.md 2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/CHANGELOG.md 2017-06-07 21:41:30.000000000 +0200
@@ -1,3 +1,15 @@
+# Version 1.2.0
+ * ```lpass show``` now supports new-style multiline ssh keys
+ * ```lpass export``` now supports --fields=FIELDLIST argument to
+   control output, with patches from Kyle Burton
+ * ```lpass ls``` now always shows empty shared folders
+ * ```lpass edit``` can now set the 'master password reprompt' field in sites
+ * ```lpass share create``` now shows the created share name
+ * Bugfix: crash in `lpass show` fixed by Kyle Burton
+ * build fixes for termux and documentation updates, from Christian Rondeau
+ * documentation updates for Ubuntu from Craig Menning and Glenn Oppegard
+ * Test suite now included covering basic operations
+
 # Version 1.1.2
  * Bugfix: crash with ```lpass logout --color=never``` fixed
  * Bugfix: ```lpass add``` with secure notes works again
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/CMakeLists.txt 
new/lastpass-cli-1.2.0/CMakeLists.txt
--- old/lastpass-cli-1.1.2/CMakeLists.txt       2017-02-03 21:39:33.000000000 
+0100
+++ new/lastpass-cli-1.2.0/CMakeLists.txt       2017-06-07 21:41:30.000000000 
+0200
@@ -50,12 +50,14 @@
   set(PROJECT_FLAGS "${PROJECT_FLAGS} -Wno-deprecated-declarations")
 endif()
 
+# Main lpass executable
 add_executable(${PROJECT_NAME} ${PROJECT_HEADERS} ${PROJECT_SOURCES})
 set_target_properties(${PROJECT_NAME} PROPERTIES
   C_STANDARD 99
   COMPILE_FLAGS ${PROJECT_FLAGS}
   COMPILE_DEFINITIONS ${PROJECT_DEFINITIONS}
 )
+
 target_link_libraries(${PROJECT_NAME} ${LIBXML2_LIBRARIES} 
${OPENSSL_LIBRARIES} ${CURL_LIBRARIES})
 if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
   target_link_libraries(${PROJECT_NAME} "-lkvm")
@@ -71,6 +73,33 @@
 install(FILES contrib/lpass_bash_completion DESTINATION 
${BASH_COMPLETION_COMPLETIONSDIR} RENAME lpass)
 endif()
 
+# Test lpass executable with mock server, link against test versions first
+file(GLOB LPTEST_SOURCES test/*.c *.c)
+add_executable(lpass-test EXCLUDE_FROM_ALL ${PROJECT_HEADERS} 
${LPTEST_SOURCES})
+set_target_properties(lpass-test PROPERTIES
+  C_STANDARD 99
+  COMPILE_FLAGS "${PROJECT_FLAGS} -DTEST_BUILD"
+  COMPILE_DEFINITIONS ${PROJECT_DEFINITIONS}
+)
+target_link_libraries(lpass-test ${LIBXML2_LIBRARIES} ${OPENSSL_LIBRARIES} 
${CURL_LIBRARIES})
+enable_testing()
+add_test(test_login ${CMAKE_SOURCE_DIR}/test/tests test_login)
+add_test(test_login_wrong_pw_should_fail ${CMAKE_SOURCE_DIR}/test/tests 
test_login_wrong_pw_should_fail)
+add_test(test_add_account ${CMAKE_SOURCE_DIR}/test/tests test_add_account)
+add_test(test_add_note ${CMAKE_SOURCE_DIR}/test/tests test_add_note)
+add_test(test_add_ssn_name ${CMAKE_SOURCE_DIR}/test/tests test_add_ssn_name)
+add_test(test_add_ssh_key ${CMAKE_SOURCE_DIR}/test/tests test_add_ssh_key)
+add_test(test_edit_username ${CMAKE_SOURCE_DIR}/test/tests test_edit_username)
+add_test(test_edit_field ${CMAKE_SOURCE_DIR}/test/tests test_edit_field)
+add_test(test_edit_reprompt ${CMAKE_SOURCE_DIR}/test/tests test_edit_reprompt)
+add_test(test_duplicate ${CMAKE_SOURCE_DIR}/test/tests test_duplicate)
+add_test(test_generate ${CMAKE_SOURCE_DIR}/test/tests test_generate)
+add_test(test_show ${CMAKE_SOURCE_DIR}/test/tests test_show)
+add_test(test_show_reprompt ${CMAKE_SOURCE_DIR}/test/tests test_show_reprompt)
+add_test(test_ls ${CMAKE_SOURCE_DIR}/test/tests test_ls)
+add_test(test_export ${CMAKE_SOURCE_DIR}/test/tests test_export)
+add_test(test_export_extended ${CMAKE_SOURCE_DIR}/test/tests 
test_export_extended)
+
 add_custom_target(doc-man DEPENDS lpass.1)
 add_custom_target(doc-html DEPENDS lpass.1.html)
 # See https://cmake.org/pipermail/cmake/2009-January/026520.html
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/Makefile 
new/lastpass-cli-1.2.0/Makefile
--- old/lastpass-cli-1.1.2/Makefile     2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/Makefile     2017-06-07 21:41:30.000000000 +0200
@@ -23,6 +23,9 @@
 install: $(CMAKEMAKE)
        $(MAKE) -C $(BUILDDIR) install
 
+test: $(CMAKEMAKE)
+       $(MAKE) -C $(BUILDDIR) lpass-test && $(MAKE) -C $(BUILDDIR) test
+
 uninstall: $(CMAKEMAKE)
        $(MAKE) -C $(BUILDDIR) uninstall
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/README.md 
new/lastpass-cli-1.2.0/README.md
--- old/lastpass-cli-1.1.2/README.md    2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/README.md    2017-06-07 21:41:30.000000000 +0200
@@ -57,7 +57,7 @@
 * For Ubuntu:
 
 ```
-sudo apt-get install openssl libcurl4-openssl-dev libxml2 libssl-dev 
libxml2-dev pinentry-curses xclip
+sudo apt-get install openssl libcurl4-openssl-dev libxml2 libssl-dev 
libxml2-dev pinentry-curses xclip cmake build-essential pkg-config
 ```
 
 #### Gentoo
@@ -136,12 +136,15 @@
   instructions in the 'Building' section.
 
 ```
-apt-cyg install wget make gcc-core openssl-devel libcurl-devel libxml2-devel 
libiconv-devel cygutils-extra
+apt-cyg install wget make cmake gcc-core gcc-g++ openssl-devel libcurl-devel 
libxml2-devel libiconv-devel cygutils-extra
 ```
 
 ## Building
 
-    $ cmake . && make
+    $ make
+
+Under the covers, make invokes cmake in a build directory; you may also use
+cmake directly if you need more control over the build process.
 
 ## Installing
 
@@ -161,8 +164,11 @@
 
 ## Documentation
 
+Install `asciidoc` and `xsltproc` if they are not already installed.
+
+    $ sudo apt-get install asciidoc xsltproc
+
 The `install-doc` target builds and installs the documentation.
-It requires AsciiDoc as a prerequisite.
 
     $ sudo make install-doc
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/agent.c 
new/lastpass-cli-1.2.0/agent.c
--- old/lastpass-cli-1.1.2/agent.c      2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/agent.c      2017-06-07 21:41:30.000000000 +0200
@@ -53,6 +53,11 @@
 #include <sys/param.h>
 #endif
 
+#if !defined(SUN_LEN)
+#define SUN_LEN(su) \
+        (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+#endif
+
 #if !defined(__linux__) && !defined(__CYGWIN__)
 #define SOCKET_SEND_PID 1
 struct ucred {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/blob.c 
new/lastpass-cli-1.2.0/blob.c
--- old/lastpass-cli-1.1.2/blob.c       2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/blob.c       2017-06-07 21:41:30.000000000 +0200
@@ -675,6 +675,13 @@
        return NULL;
 }
 
+void buffer_init(struct buffer *buf)
+{
+       buf->len = 0;
+       buf->max = 80;
+       buf->bytes = xcalloc(buf->max, 1);
+}
+
 void buffer_append(struct buffer *buffer, void *bytes, size_t len)
 {
        if (buffer->len + len > buffer->max) {
@@ -1129,7 +1136,7 @@
 struct account *notes_expand(struct account *acc)
 {
        struct account *expand;
-       struct field *field;
+       struct field *field = NULL;
        char *start, *lf, *colon, *name, *value;
        struct attach *attach, *tmp;
        char *line = NULL;
@@ -1150,19 +1157,48 @@
        if (strncmp(acc->note, "NoteType:", 9))
                return NULL;
 
+       enum note_type note_type = NOTE_TYPE_NONE;
+       lf = strchr(acc->note + 9, '\n');
+       if (lf) {
+               _cleanup_free_ char *type = xstrndup(acc->note + 9, lf - 
(acc->note + 9));
+               note_type = notes_get_type_by_name(type);
+       }
+
        for (start = acc->note; ; ) {
-               lf = strchrnul(start, '\n');
-               if (lf - start < 0)
-                       lf = NULL;
-               if (lf - start <= 0)
+               name = value = NULL;
+               lf = strchr(start, '\n');
+               if (!lf || lf == start)
                        goto skip;
+
                line = xstrndup(start, lf - start);
                colon = strchr(line, ':');
-               if (!colon)
+               if (colon) {
+                       name = xstrndup(line, colon - line);
+                       value = xstrdup(colon + 1);
+               }
+
+               /*
+                * Append non-keyed strings to existing field.
+                * If no field, skip.
+                */
+               if (!name) {
+                       if (field)
+                               xstrappendf(&field->value, "\n%s", line);
                        goto skip;
-               *colon = '\0';
-               name = line;
-               value = colon + 1;
+               }
+
+               /*
+                * If this is a known notetype, append any non-existent
+                * keys to the existing field.  For example, Proc-Type
+                * in the ssh private key field goes into private key,
+                * not a Proc-Type field.
+                */
+               if (note_type != NOTE_TYPE_NONE &&
+                   !note_has_field(note_type, name) && field) {
+                       xstrappendf(&field->value, "\n%s", line);
+                       goto skip;
+               }
+
                if (!strcmp(name, "Username"))
                        expand->username = xstrdup(value);
                else if (!strcmp(name, "Password"))
@@ -1183,6 +1219,8 @@
                        list_add(&field->list, &expand->field_head);
                }
 skip:
+               free(value);
+               free(name);
                free(line);
                line = NULL;
                if (!lf || !*lf)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/blob.h 
new/lastpass-cli-1.2.0/blob.h
--- old/lastpass-cli-1.1.2/blob.h       2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/blob.h       2017-06-07 21:41:30.000000000 +0200
@@ -176,6 +176,7 @@
 struct account *notes_collapse(struct account *acc);
 void share_free(struct share *share);
 struct share *find_unique_share(struct blob *blob, const char *name);
+void buffer_init(struct buffer *buf);
 void buffer_append(struct buffer *buffer, void *bytes, size_t len);
 void buffer_append_char(struct buffer *buf, char c);
 void buffer_append_str(struct buffer *buf, char *str);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/cmd-export.c 
new/lastpass-cli-1.2.0/cmd-export.c
--- old/lastpass-cli-1.1.2/cmd-export.c 2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/cmd-export.c 2017-06-07 21:41:30.000000000 +0200
@@ -46,12 +46,28 @@
 #include <unistd.h>
 #include <string.h>
 
+struct field_selection {
+       const char *name;
+       struct list_head list;
+};
+
+static void parse_field_arg(char *arg, struct list_head *head)
+{
+       char *token;
+       for (token = strtok(arg, ","); token; token = strtok(NULL, ",")) {
+               struct field_selection *sel = new0(struct field_selection, 1);
+               sel->name = token;
+               list_add_tail(&sel->list, head);
+       }
+}
 
 static void print_csv_cell(const char *cell, bool is_last)
 {
        const char *ptr;
        bool needs_quote = false;
 
+       cell = cell == NULL ? "" : cell;
+
        /* decide if we need quoting */
        for (ptr = cell; *ptr; ptr++) {
                if (*ptr == '"' || *ptr == ',' || *ptr == '\n' || *ptr == '\r') 
{
@@ -78,19 +94,72 @@
                printf(",");
 }
 
+void print_csv_field(struct account *account, const char *field_name,
+                    bool is_last)
+{
+       _cleanup_free_ char *share_group = NULL;
+       char *groupname = account->group;
+
+#define OUTPUT_FIELD(name, value, is_last) \
+       do { \
+               if (!strcmp(field_name, name)) { \
+                       print_csv_cell(value, is_last); \
+                       return; \
+               } \
+       } while(0)
+
+       OUTPUT_FIELD("url", account->url, is_last);
+       OUTPUT_FIELD("username", account->username, is_last);
+       OUTPUT_FIELD("password", account->password, is_last);
+       OUTPUT_FIELD("extra", account->note, is_last);
+       OUTPUT_FIELD("name", account->name, is_last);
+       OUTPUT_FIELD("fav", bool_str(account->fav), is_last);
+       OUTPUT_FIELD("id", account->id, is_last);
+       OUTPUT_FIELD("group", account->group, is_last);
+       OUTPUT_FIELD("fullname", account->fullname, is_last);
+       OUTPUT_FIELD("last_touch", account->last_touch, is_last);
+       OUTPUT_FIELD("last_modified_gmt", account->last_modified_gmt, is_last);
+       OUTPUT_FIELD("attachpresent", bool_str(account->attachpresent), 
is_last);
+
+       if (!strcmp(field_name, "grouping")) {
+               if (account->share) {
+                       xasprintf(&share_group, "%s\\%s",
+                                 account->share->name, account->group);
+
+                       /* trim trailing backslash if no subfolder */
+                       if (!strlen(account->group))
+                               share_group[strlen(share_group)-1] = '\0';
+
+                       groupname = share_group;
+               }
+               print_csv_cell(groupname, is_last);
+               return;
+       }
+
+       /* unknown field, just return empty string */
+       print_csv_cell("", is_last);
+}
+
 int cmd_export(int argc, char **argv)
 {
        static struct option long_options[] = {
                {"sync", required_argument, NULL, 'S'},
                {"color", required_argument, NULL, 'C'},
+               {"fields", required_argument, NULL, 'f'},
                {0, 0, 0, 0}
        };
        int option;
        int option_index;
        enum blobsync sync = BLOB_SYNC_AUTO;
        struct account *account;
+       const char *default_fields[] = {
+               "url", "username", "password", "extra",
+               "name", "grouping", "fav"
+       };
+
+       LIST_HEAD(field_list);
 
-       while ((option = getopt_long(argc, argv, "c", long_options, 
&option_index)) != -1) {
+       while ((option = getopt_long(argc, argv, "", long_options, 
&option_index)) != -1) {
                switch (option) {
                        case 'S':
                                sync = parse_sync_string(optarg);
@@ -99,15 +168,28 @@
                                terminal_set_color_mode(
                                        parse_color_mode_string(optarg));
                                break;
+                       case 'f':
+                               parse_field_arg(optarg, &field_list);
+                               break;
                        case '?':
                        default:
                                die_usage(cmd_export_usage);
                }
        }
 
+       if (list_empty(&field_list)) {
+               for (unsigned int i = 0; i < ARRAY_SIZE(default_fields); i++) {
+                       struct field_selection *sel = new0(struct 
field_selection, 1);
+                       sel->name = default_fields[i];
+                       list_add_tail(&sel->list, &field_list);
+               }
+       }
+
        unsigned char key[KDF_HASH_LEN];
        struct session *session = NULL;
        struct blob *blob = NULL;
+       struct field_selection *field_sel, *tmp;
+
        init_all(sync, key, &session, &blob);
 
        /* reprompt once if any one account is password protected */
@@ -122,36 +204,30 @@
                }
        }
 
-       printf("url,username,password,extra,name,grouping,fav\r\n");
+       struct field_selection *last_entry =
+               list_last_entry_or_null(&field_list, struct field_selection, 
list);
 
-       list_for_each_entry(account, &blob->account_head, list) {
+       /* header */
+       list_for_each_entry(field_sel, &field_list, list) {
+               print_csv_cell(field_sel->name, field_sel == last_entry);
+       }
 
-               _cleanup_free_ char *share_group = NULL;
-               char *groupname = account->group;
+       /* entries */
+       list_for_each_entry(account, &blob->account_head, list) {
 
                /* skip groups */
                if (!strcmp(account->url, "http://group";))
                        continue;
 
-               if (account->share) {
-                       xasprintf(&share_group, "%s\\%s",
-                                 account->share->name, account->group);
-
-                       /* trim trailing backslash if no subfolder */
-                       if (!strlen(account->group))
-                               share_group[strlen(share_group)-1] = '\0';
-
-                       groupname = share_group;
+               list_for_each_entry(field_sel, &field_list, list) {
+                       print_csv_field(account, field_sel->name,
+                                       field_sel == last_entry);
                }
-
                lastpass_log_access(sync, session, key, account);
-               print_csv_cell(account->url, false);
-               print_csv_cell(account->username, false);
-               print_csv_cell(account->password, false);
-               print_csv_cell(account->note, false);
-               print_csv_cell(account->name, false);
-               print_csv_cell(groupname, false);
-               print_csv_cell(bool_str(account->fav), true);
+       }
+
+       list_for_each_entry_safe(field_sel, tmp, &field_list, list) {
+               free(field_sel);
        }
 
        session_free(session);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/cmd-ls.c 
new/lastpass-cli-1.2.0/cmd-ls.c
--- old/lastpass-cli-1.1.2/cmd-ls.c     2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/cmd-ls.c     2017-06-07 21:41:30.000000000 +0200
@@ -194,7 +194,7 @@
                        if (node->account) {
                                struct buffer buf;
 
-                               memset(&buf, 0, sizeof(buf));
+                               buffer_init(&buf);
                                format_account(&buf, fmt_str, node->account);
                                terminal_printf("%s\n", buf.bytes);
                                free(buf.bytes);
@@ -245,6 +245,8 @@
        int i, num_accounts;
        _cleanup_free_ char *fmt_str = NULL;
 
+       struct share *share;
+
        while ((option = getopt_long(argc, argv, "lmu", long_options, 
&option_index)) != -1) {
                switch (option) {
                        case 'S':
@@ -298,11 +300,28 @@
        list_for_each_entry(account, &blob->account_head, list) {
                num_accounts++;
        }
+       list_for_each_entry(share, &blob->share_head, list) {
+               num_accounts++;
+       }
+
        i=0;
        account_array = xcalloc(num_accounts, sizeof(struct account *));
        list_for_each_entry(account, &blob->account_head, list) {
                account_array[i++] = account;
        }
+       /* fake accounts for shares, so that empty shared folders are shown. */
+       list_for_each_entry(share, &blob->share_head, list) {
+               struct account *account = new_account();
+               char *tmpname = NULL;
+
+               xasprintf(&tmpname, "%s/", share->name);
+               account->share = share;
+               account->id = share->id;
+               account_set_name(account, xstrdup(""), key);
+               account_set_fullname(account, tmpname, key);
+               account_set_url(account, "http://group";, key);
+               account_array[i++] = account;
+       }
        qsort(account_array, num_accounts, sizeof(struct account *),
              compare_account);
 
@@ -329,7 +348,9 @@
                                continue;
                        group_len = strlen(group);
                        sub += group_len;
-                       if (group[group_len - 1] != '/' && sub[0] != '\0' && 
sub[0] != '/')
+                       if (group_len &&
+                           group[group_len - 1] != '/' &&
+                           sub[0] != '\0' && sub[0] != '/')
                                continue;
                }
 
@@ -340,7 +361,7 @@
                else {
                        struct buffer buf;
 
-                       memset(&buf, 0, sizeof(buf));
+                       buffer_init(&buf);
                        format_account(&buf, fmt_str, account);
                        terminal_printf("%s\n", buf.bytes);
                        free(buf.bytes);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/cmd-share.c 
new/lastpass-cli-1.2.0/cmd-share.c
--- old/lastpass-cli-1.1.2/cmd-share.c  2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/cmd-share.c  2017-06-07 21:41:30.000000000 +0200
@@ -368,12 +368,22 @@
 static int share_create(struct share_command *cmd, int argc, char **argv,
                        struct share_args *args)
 {
+       int ret;
+       bool prepend_share;
+
        if (argc != 0)
                die_share_usage(cmd);
 
        UNUSED(argv);
 
-       lastpass_share_create(args->session, args->sharename);
+       ret = lastpass_share_create(args->session, args->sharename);
+       if (ret)
+               die("No permission to create share");
+
+       prepend_share = strncmp(args->sharename, "Shared-", 7);
+       terminal_printf("Folder %s%s created.\n",
+                       (prepend_share) ? "Shared-" : "",
+                       args->sharename);
        return 0;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/cmd-show.c 
new/lastpass-cli-1.2.0/cmd-show.c
--- old/lastpass-cli-1.1.2/cmd-show.c   2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/cmd-show.c   2017-06-07 21:41:30.000000000 +0200
@@ -189,7 +189,7 @@
 {
        struct buffer buf;
 
-       memset(&buf, 0, sizeof(buf));
+       buffer_init(&buf);
        format_account(&buf, title_format, found);
        terminal_printf("%s\n", buf.bytes);
        free(buf.bytes);
@@ -199,7 +199,8 @@
                        char *name, char *value)
 {
        struct buffer buf;
-       memset(&buf, 0, sizeof(buf));
+
+       buffer_init(&buf);
        format_field(&buf, field_format, account, name, value);
        terminal_printf("%s\n", buf.bytes);
        free(buf.bytes);
@@ -483,6 +484,8 @@
                        list_for_each_entry(attach, &found->attach_head, list) {
                                print_attachment(field_format, found, attach);
                        }
+                       if (found->pwprotect)
+                               print_field(field_format, found, "Reprompt", 
"Yes");
                        if (strlen(found->note))
                                print_field(field_format, found, "Notes", 
found->note);
                } else if (choice != ATTACH) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/cmd.h new/lastpass-cli-1.2.0/cmd.h
--- old/lastpass-cli-1.1.2/cmd.h        2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/cmd.h        2017-06-07 21:41:30.000000000 +0200
@@ -108,7 +108,7 @@
 #define cmd_sync_usage "sync [--background, -b] " color_usage
 
 int cmd_export(int argc, char **argv);
-#define cmd_export_usage "export [--sync=auto|now|no] " color_usage
+#define cmd_export_usage "export [--sync=auto|now|no] " color_usage " 
[--fields=FIELDLIST]"
 
 int cmd_share(int argc, char **argv);
 #define cmd_share_usage "share subcommand sharename ..."
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/debian/changelog 
new/lastpass-cli-1.2.0/debian/changelog
--- old/lastpass-cli-1.1.2/debian/changelog     2017-02-03 21:39:33.000000000 
+0100
+++ new/lastpass-cli-1.2.0/debian/changelog     2017-06-07 21:41:30.000000000 
+0200
@@ -1,3 +1,9 @@
+lastpass-cli (1.2.0) unstable; urgency=medium
+
+  * New upstream 1.2.0
+
+ -- Bob Copeland <[email protected]>  Wed, 07 Jun 2017 15:15:56 -0400
+
 lastpass-cli (1.1.2) unstable; urgency=medium
 
   * New upstream 1.1.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/edit.c 
new/lastpass-cli-1.2.0/edit.c
--- old/lastpass-cli-1.1.2/edit.c       2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/edit.c       2017-06-07 21:41:30.000000000 +0200
@@ -44,6 +44,8 @@
 #include <errno.h>
 #include "util.h"
 
+#define MAX_NOTE_LEN (unsigned long) 45000
+
 #if defined(__linux__) || defined(__CYGWIN__)
 static char *shared_memory_dir(void)
 {
@@ -155,6 +157,11 @@
        assign_if("Password", password);
        assign_if("Application", appname);
 
+       if (!strcmp(label, "Reprompt")) {
+               account->pwprotect = !strcmp(trim(value), "Yes");
+               return;
+       }
+
        /* if we got here maybe it's a secure note field */
        list_for_each_entry(editable_field, &account->field_head, list) {
                if (!strcmp(label, editable_field->name)) {
@@ -252,6 +259,10 @@
        if (ret)
                return;
 
+       if (len > MAX_NOTE_LEN) {
+               die("Maximum note length is %lu bytes (was %lu)",
+                   MAX_NOTE_LEN, len);
+       }
        account_set_note(account, value, key);
 }
 
@@ -353,6 +364,10 @@
                write_field(editable_field->name, editable_field->value);
        }
 
+       if (account->pwprotect) {
+               write_field("Reprompt", "Yes");
+       }
+
        if (fprintf(fp, "Notes:    # Add notes below this line.\n%s", 
account->note) < 0)
                return -errno;
 
@@ -506,6 +521,7 @@
                account_free(notes_expansion);
                account_set_note(editable, xstrdup(notes_collapsed->note), key);
                account_set_fullname(editable, 
xstrdup(notes_collapsed->fullname), key);
+               editable->pwprotect = notes_collapsed->pwprotect;
                account_free(notes_collapsed);
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/endpoints-login.c 
new/lastpass-cli-1.2.0/endpoints-login.c
--- old/lastpass-cli-1.1.2/endpoints-login.c    2017-02-03 21:39:33.000000000 
+0100
+++ new/lastpass-cli-1.2.0/endpoints-login.c    2017-06-07 21:41:30.000000000 
+0200
@@ -342,9 +342,10 @@
                append_post(args, "trustlabel", trusted_label);
        }
 
-       if (!strcmp(cause, "outofbandrequired") && oob_login(login_server, key, 
args, error_message, &reply, &otp_name, &session)) {
+       if (cause && !strcmp(cause, "outofbandrequired") &&
+           oob_login(login_server, key, args, error_message, &reply, 
&otp_name, &session)) {
                if (trust)
-                       http_post_lastpass("trust.php", session, NULL, "uuid", 
trusted_id, "trustlabel", trusted_label, NULL);
+                       http_post_lastpass("trust.php", session, NULL, "token", 
session->token, "uuid", trusted_id, "trustlabel", trusted_label, NULL);
                return session;
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/endpoints-share.c 
new/lastpass-cli-1.2.0/endpoints-share.c
--- old/lastpass-cli-1.1.2/endpoints-share.c    2017-02-03 21:39:33.000000000 
+0100
+++ new/lastpass-cli-1.2.0/endpoints-share.c    2017-06-07 21:41:30.000000000 
+0200
@@ -192,7 +192,7 @@
 int lastpass_share_user_del(const struct session *session, const char *shareid,
                            struct share_user *user)
 {
-       _cleanup_free_ char *reply = NULL;
+       char *reply = NULL;
        size_t len;
 
        reply = http_post_lastpass("share.php", session, &len,
@@ -202,6 +202,8 @@
                                   "delete", "1",
                                   "uid", user->uid,
                                   "xmlr", "1", NULL);
+
+       free(reply);
        return 0;
 }
 
@@ -286,7 +288,7 @@
 
 int lastpass_share_delete(const struct session *session, struct share *share)
 {
-       _cleanup_free_ char *reply = NULL;
+       char *reply = NULL;
        size_t len;
 
        reply = http_post_lastpass("share.php", session, &len,
@@ -294,6 +296,7 @@
                                   "id", share->id,
                                   "delete", "1",
                                   "xmlr", "1", NULL);
+       free(reply);
        return 0;
 }
 
@@ -382,7 +385,7 @@
                              struct share_user *user,
                              struct share_limit *limit)
 {
-       _cleanup_free_ char *reply = NULL;
+       char *reply = NULL;
        _cleanup_free_ char *aid_buf = NULL;
        char numaids_str[30] = {0};
        struct share_limit_aid *aid;
@@ -416,5 +419,6 @@
                                   "aids", aid_buf,
                                   "xmlr", "1", NULL);
 
+       free(reply);
        return 0;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/endpoints.c 
new/lastpass-cli-1.2.0/endpoints.c
--- old/lastpass-cli-1.1.2/endpoints.c  2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/endpoints.c  2017-06-07 21:41:30.000000000 +0200
@@ -90,58 +90,6 @@
        upload_queue_enqueue(sync, key, session, "show_website.php", &params);
 }
 
-static char *stringify_field(const struct field *field)
-{
-       char *str, *name, *type, *value, *intermediate;
-       CURL *curl;
-
-       curl = curl_easy_init();
-       if (!curl)
-               return xstrdup("");
-
-       name = curl_easy_escape(curl, field->name, 0);
-       type = curl_easy_escape(curl, field->type, 0);
-       if (field->value_encrypted)
-               value = curl_easy_escape(curl, field->value_encrypted, 0);
-       else if (!strcmp(field->type, "checkbox") || !strcmp(field->type, 
"radio")) {
-               xasprintf(&intermediate, "%s-%c", field->value, field->checked 
? '1' : '0');
-               value = curl_easy_escape(curl, intermediate, 0);
-               free(intermediate);
-       } else
-               value = curl_easy_escape(curl, field->value, 0);
-
-       xasprintf(&str, "0\t%s\t%s\t%s\n", name, value, type);
-
-       curl_free(name);
-       curl_free(type);
-       curl_free(value);
-       curl_easy_cleanup(curl);
-
-       return str;
-}
-
-static char *stringify_fields(const struct list_head *field_head)
-{
-       char *field_str, *fields = NULL;
-       struct field *field;
-
-       list_for_each_entry(field, field_head, list) {
-               field_str = stringify_field(field);
-               xstrappend(&fields, field_str);
-               free(field_str);
-       }
-       if (fields)
-               xstrappend(&fields, 
"0\taction\t\taction\n0\tmethod\t\tmethod\n");
-       else
-               fields = xstrdup("");
-
-       field_str = NULL;
-       bytes_to_hex((unsigned char *) fields, &field_str, strlen(fields));
-       free(fields);
-
-       return field_str;
-}
-
 static void add_app_fields(const struct account *account,
                           struct http_param_set *params)
 {
@@ -172,10 +120,8 @@
        };
 
        _cleanup_free_ char *url = NULL;
-       _cleanup_free_ char *fields = NULL;
 
        bytes_to_hex((unsigned char *) account->url, &url, 
strlen(account->url));
-       fields = stringify_fields(&account->field_head);
 
        ++blob->version;
 
@@ -248,6 +194,8 @@
                http_post_add_params(&params, "sharedfolderid", 
account->share->id, NULL);
 
        upload_queue_enqueue(sync, key, session, "loglogin.php", &params);
+
+       free(params.argv);
 }
 
 
@@ -426,7 +374,7 @@
                    starts_with(params.argv[i], "extra")) {
                        free(params.argv[i]);
                }
-               if (starts_with(params.argv[i], "url")) {
+               else if (starts_with(params.argv[i], "url")) {
                        free(params.argv[i]);
                        if (i < params.n_alloced) {
                                free(params.argv[i+1]);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/format.c 
new/lastpass-cli-1.2.0/format.c
--- old/lastpass-cli-1.1.2/format.c     2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/format.c     2017-06-07 21:41:30.000000000 +0200
@@ -82,7 +82,7 @@
 static
 void append_str(struct buffer *buf, char *str, bool add_slash)
 {
-       if (!strlen(str))
+       if (!str || !strlen(str))
                return;
 
        buffer_append_str(buf, str);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/http.c 
new/lastpass-cli-1.2.0/http.c
--- old/lastpass-cli-1.1.2/http.c       2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/http.c       2017-06-07 21:41:30.000000000 +0200
@@ -52,6 +52,7 @@
        size_t len;
 };
 
+#ifndef TEST_BUILD
 static bool interrupted = false;
 static sig_t previous_handler = SIG_DFL;
 static void interruption_detected(int signal)
@@ -166,6 +167,7 @@
                           verify_callback);
        return CURLE_OK;
 }
+#endif
 
 static
 void vhttp_post_add_params(struct http_param_set *param_set, va_list args)
@@ -226,6 +228,7 @@
        return result;
 }
 
+#ifndef TEST_BUILD
 char *http_post_lastpass_v_noexit(const char *server, const char *page, const 
struct session *session, size_t *final_len, char **argv, int *curl_ret, long 
*http_code)
 {
        _cleanup_free_ char *url = NULL;
@@ -322,6 +325,7 @@
 
        return result.ptr;
 }
+#endif
 
 char *http_post_lastpass_v(const char *server, const char *page, const struct 
session *session, size_t *final_len, char **argv)
 {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/list.h 
new/lastpass-cli-1.2.0/list.h
--- old/lastpass-cli-1.1.2/list.h       2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/list.h       2017-06-07 21:41:30.000000000 +0200
@@ -163,6 +163,17 @@
        (!list_empty(ptr) ? list_first_entry(ptr, type, member) : NULL)
 
 /**
+ * list_last_entry_or_null - get the last element from a list
+ * @ptr:       the list head to take the element from.
+ * @type:      the type of the struct this is embedded in.
+ * @member:    the name of the list_struct within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_last_entry_or_null(ptr, type, member) \
+       (!list_empty(ptr) ? list_last_entry(ptr, type, member) : NULL)
+
+/**
  * list_next_entry - get the next element in list
  * @pos:       the type * to cursor
  * @member:    the name of the list_struct within the struct.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/lpass.1.txt 
new/lastpass-cli-1.2.0/lpass.1.txt
--- old/lastpass-cli-1.1.2/lpass.1.txt  2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/lpass.1.txt  2017-06-07 21:41:30.000000000 +0200
@@ -35,7 +35,7 @@
  lpass *status* [--quiet, -q] [--color=auto|never|always]
  lpass *sync* [--background, -b] [--color=auto|never|always]
  lpass *import* [--sync=auto|now|no] [FILENAME]
- lpass *export* [--sync=auto|now|no] [--color=auto|never|always]
+ lpass *export* [--sync=auto|now|no] [--color=auto|never|always] 
[--fields=FIELDLIST]
  lpass *share* *userls* SHARE
  lpass *share* *useradd* [--read-only=[true|false]] [--hidden=[true|false]] 
[--admin=[true|false]] SHARE USERNAME
  lpass *share* *usermod* [--read-only=[true|false]] [--hidden=[true|false]] 
[--admin=[true|false]] SHARE USERNAME
@@ -185,7 +185,12 @@
 Backup
 ~~~~~~
 The 'export' subcommand will dump all account information including
-passwords to stdout (unencrypted) in CSV format.
+passwords to stdout (unencrypted) in CSV format.  The optional
+'--fields=FIELDLIST' argument may contain a comma-separated subset of the
+following fields:
+
+  id, url, username, password, extra, name, fav, id, grouping, group,
+  fullname, last_touch, last_modified_gmt, attachpresent
 
 The 'import' subcommand does the reverse: accounts from an unencrypted
 CSV file are uploaded to the server.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/notes.c 
new/lastpass-cli-1.2.0/notes.c
--- old/lastpass-cli-1.1.2/notes.c      2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/notes.c      2017-06-07 21:41:30.000000000 +0200
@@ -121,6 +121,21 @@
        return note_templates[note_type].name;
 }
 
+bool note_has_field(enum note_type note_type, const char *field)
+{
+       const char **p;
+       if (note_type <= NOTE_TYPE_NONE || note_type >= NUM_NOTE_TYPES)
+               return true;
+
+       p = note_templates[note_type].fields;
+       while (*p) {
+               if (!strcmp(field, *p))
+                       return true;
+               p++;
+       }
+       return false;
+}
+
 enum note_type notes_get_type_by_shortname(const char *type_str)
 {
        BUILD_BUG_ON(ARRAY_SIZE(note_templates) != NUM_NOTE_TYPES);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/notes.h 
new/lastpass-cli-1.2.0/notes.h
--- old/lastpass-cli-1.1.2/notes.h      2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/notes.h      2017-06-07 21:41:30.000000000 +0200
@@ -1,6 +1,8 @@
 #ifndef NOTE_TYPES
 #define NOTE_TYPES
 
+#include <stdbool.h>
+
 enum note_type {
        NOTE_TYPE_NONE = -1,
        NOTE_TYPE_AMEX,
@@ -34,6 +36,7 @@
 extern struct note_template note_templates[];
 
 const char *notes_get_name(enum note_type note_type);
+bool note_has_field(enum note_type note_type, const char *field);
 enum note_type notes_get_type_by_shortname(const char *shortname);
 enum note_type notes_get_type_by_name(const char *type_str);
 char *note_type_usage();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/test/askpass-wrong.sh 
new/lastpass-cli-1.2.0/test/askpass-wrong.sh
--- old/lastpass-cli-1.1.2/test/askpass-wrong.sh        1970-01-01 
01:00:00.000000000 +0100
+++ new/lastpass-cli-1.2.0/test/askpass-wrong.sh        2017-06-07 
21:41:30.000000000 +0200
@@ -0,0 +1,9 @@
+#!/bin/bash
+. $(dirname $0)/include.sh
+
+# this lockfile keeps login from getting stuck in a loop
+if [[ -e .askpass.lock ]]; then
+       exit 1
+fi
+touch .askpass.lock
+echo $TEST_WRONG_PASS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/test/askpass.sh 
new/lastpass-cli-1.2.0/test/askpass.sh
--- old/lastpass-cli-1.1.2/test/askpass.sh      1970-01-01 01:00:00.000000000 
+0100
+++ new/lastpass-cli-1.2.0/test/askpass.sh      2017-06-07 21:41:30.000000000 
+0200
@@ -0,0 +1,4 @@
+#!/bin/bash
+. $(dirname $0)/include.sh
+
+echo $TEST_PASS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/test/assert.sh 
new/lastpass-cli-1.2.0/test/assert.sh
--- old/lastpass-cli-1.1.2/test/assert.sh       1970-01-01 01:00:00.000000000 
+0100
+++ new/lastpass-cli-1.2.0/test/assert.sh       2017-06-07 21:41:30.000000000 
+0200
@@ -0,0 +1,45 @@
+#! /bin/bash
+#
+# various assert functions
+#
+function assert_ne
+{
+       if [[ $1 -eq $2 ]]; then
+               echo "FAIL: \"$1 != $2\" $3"
+               return 1
+       fi
+}
+
+function assert_eq
+{
+       if [[ $1 -ne $2 ]]; then
+               echo "FAIL: \"$1 == $2\" $3"
+               return 1
+       fi
+}
+
+function assert_str_neq
+{
+       if [[ "$1" == "$2" ]]; then
+               echo "FAIL: \"$1 != $2\" $3"
+               return 1
+       fi
+}
+
+function assert_str_eq
+{
+       if [[ "$1" != "$2" ]]; then
+               echo "FAIL: \"$1 == $2\" $3"
+               return 1
+       fi
+}
+
+function assertz
+{
+       assert_eq $1 0 $2
+}
+
+function assert
+{
+       assert_ne $1 0 $2
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/test/http_mock.c 
new/lastpass-cli-1.2.0/test/http_mock.c
--- old/lastpass-cli-1.1.2/test/http_mock.c     1970-01-01 01:00:00.000000000 
+0100
+++ new/lastpass-cli-1.2.0/test/http_mock.c     2017-06-07 21:41:30.000000000 
+0200
@@ -0,0 +1,258 @@
+/*
+ * mock http server for testing
+ *
+ * Copyright (C) 2014-2017 LastPass.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ *
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ * See LICENSE.OpenSSL for more details regarding this exception.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "../util.h"
+#include "../blob.h"
+
+#define TEST_USER "[email protected]"
+#define TEST_PASS "123456"
+#define TEST_UID "57747756"
+
+struct test_data
+{
+       char *username;
+       char *password;
+       char *uid;
+       int iterations;
+       unsigned char key[KDF_HASH_LEN];
+       char login_hash[KDF_HEX_LEN];
+       struct blob blob;
+};
+
+struct test_data test_data;
+
+static void init_test_data()
+{
+       static bool is_initialized;
+       struct account *account;
+       unsigned char *key;
+
+       if (is_initialized)
+               return;
+
+       test_data.username = xstrdup(TEST_USER);
+       test_data.password = xstrdup(TEST_PASS);
+       test_data.uid = xstrdup(TEST_UID);
+       test_data.iterations = 1000;
+
+       kdf_login_key(test_data.username, test_data.password, 
test_data.iterations, test_data.login_hash);
+       kdf_decryption_key(test_data.username, test_data.password, 
test_data.iterations, test_data.key);
+
+       test_data.blob.version = 1;
+       test_data.blob.local_version = false;
+       INIT_LIST_HEAD(&test_data.blob.account_head);
+       INIT_LIST_HEAD(&test_data.blob.share_head);
+
+       // existing server side accounts used by some tests
+       key = test_data.key;
+       account = new_account();
+       account->id = xstrdup("0001");
+       account_set_name(account, "test-account", key);
+       account_set_group(account, "test-group", key);
+       account_set_username(account, "[email protected]", key);
+       account_set_password(account, "test-account-password", key);
+       account_set_url(account, "https://test-url.example.com/";, key);
+       account_set_note(account, "", key);
+       list_add_tail(&account->list, &test_data.blob.account_head);
+
+       account = new_account();
+       account->id = xstrdup("0002");
+       account_set_name(account, "test-note", key);
+       account_set_group(account, "test-group", key);
+       account_set_username(account, xstrdup(""), key);
+       account_set_password(account, xstrdup(""), key);
+       account_set_url(account, "http://sn";, key);
+       account_set_note(account,
+               "NoteType: Server\n"
+               "Hostname: foo.example.com\n"
+               "Username: test-note-user\n"
+               "Password: test-note-password", key);
+       list_add_tail(&account->list, &test_data.blob.account_head);
+
+       account = new_account();
+       account->id = xstrdup("0003");
+       account_set_name(account, "test-reprompt-account", key);
+       account_set_group(account, "test-group", key);
+       account_set_username(account, "[email protected]", key);
+       account_set_password(account, "test-account-password", key);
+       account_set_url(account, "https://test-url.example.com/";, key);
+       account_set_note(account, "", key);
+       account->pwprotect = true;
+       list_add_tail(&account->list, &test_data.blob.account_head);
+
+       account = new_account();
+       account->id = xstrdup("0004");
+       account_set_name(account, "test-reprompt-note", key);
+       account_set_group(account, "test-group", key);
+       account_set_username(account, xstrdup(""), key);
+       account_set_password(account, xstrdup(""), key);
+       account_set_url(account, "http://sn";, key);
+       account_set_note(account,
+               "NoteType: Server\n"
+               "Hostname: foo.example.com\n"
+               "Username: test-note-user\n"
+               "Password: test-note-password", key);
+       account->pwprotect = true;
+       list_add_tail(&account->list, &test_data.blob.account_head);
+
+       is_initialized = true;
+}
+
+static char *get_param(char **argv, char *name)
+{
+       int i;
+       for (i=0; argv[i]; i += 2) {
+               if (!strcmp(argv[i], name))
+                       return argv[i + 1];
+       }
+       return NULL;
+}
+
+static char *getaccts(char **argv, size_t *len)
+{
+       UNUSED(argv);
+       char *data = NULL;
+
+       if (len)
+               *len = blob_write(&test_data.blob, NULL, &data);
+       return data;
+}
+
+static char *iterations(char **argv, size_t *len)
+{
+       UNUSED(argv);
+       char *response = NULL;
+
+       response = xultostr(test_data.iterations);
+       if (len)
+               *len = strlen(response);
+       return response;
+}
+
+static char *show_website(char **argv, size_t *len)
+{
+       UNUSED(argv);
+       if (len)
+               *len = 0;
+       return xstrdup("");
+}
+
+static char *login(char **argv, size_t *len)
+{
+       char *username = get_param(argv, "username");
+       char *hash = get_param(argv, "hash");
+       char *response;
+
+       if (strcmp(username, test_data.username) ||
+           strcmp(hash, test_data.login_hash)) {
+               response = xstrdup("<response>"
+                       "<error message=\"invalid password\"/>"
+                       "</response>");
+       } else {
+               response = xstrdup("<response>"
+                       "<ok "
+                       "uid=\"" TEST_UID "\" "
+                       "sessionid=\"1234\" "
+                       "token=\"abcd\"/>"
+                       "</response>");
+       }
+       if (len)
+               *len = strlen(response);
+       return response;
+}
+
+static char *login_check(char **argv, size_t *len)
+{
+       UNUSED(argv);
+       char *response = xstrdup("<response>"
+                       "<ok "
+                       "uid=\"" TEST_UID "\" "
+                       "sessionid=\"1234\" "
+                       "token=\"abcd\" "
+                       "accts_version=\"123\"/>"
+                       "</response>");
+       if (len)
+               *len = strlen(response);
+       return response;
+}
+
+struct page_entry {
+       char *name;
+       char *(*fn)(char **, size_t *);
+};
+
+#define PAGE(x) { .name = #x ".php", .fn = x }
+struct page_entry page_table[] = {
+       PAGE(getaccts),
+       PAGE(iterations),
+       PAGE(login),
+       PAGE(login_check),
+       PAGE(show_website),
+};
+
+struct session;
+
+/*
+ * This implements a mock server for lpass for unit testing the client,
+ * overriding the function of the same name from http.c.
+ */
+char *http_post_lastpass_v_noexit(const char *server, const char *page,
+                                  const struct session *session,
+                                  size_t *final_len, char **argv,
+                                  int *curl_ret, long *http_code)
+{
+       unsigned int i;
+       UNUSED(server);
+       UNUSED(session);
+
+       init_test_data();
+
+       *curl_ret = 0;
+       *http_code = 200;
+
+       for (i = 0; i < ARRAY_SIZE(page_table); i++) {
+               if (!strcmp(page, page_table[i].name)) {
+                       return page_table[i].fn(argv, final_len);
+               }
+       }
+       fprintf(stderr, "unhandled page: %s\n", page);
+       char *response = xstrdup("<response><error 
message=\"unimplemented\"/></response>");
+       if (final_len)
+               *final_len = strlen(response);
+       return response;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/test/include.sh 
new/lastpass-cli-1.2.0/test/include.sh
--- old/lastpass-cli-1.1.2/test/include.sh      1970-01-01 01:00:00.000000000 
+0100
+++ new/lastpass-cli-1.2.0/test/include.sh      2017-06-07 21:41:30.000000000 
+0200
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+function setup()
+{
+       cd `dirname $0`
+       . assert.sh
+       export LPASS_ASKPASS=./askpass.sh
+       export TEST_USER="[email protected]"
+       export TEST_PASS="123456"
+       export TEST_WRONG_PASS="000000"
+       export TEST_LPASS="../build/lpass-test"
+       export LPASS_HOME="./.lpass"
+}
+
+function setup_testcase()
+{
+       # start with fresh blob for every test
+       rm $LPASS_HOME/blob 2>/dev/null
+}
+
+function runtests()
+{
+       local tests=${1:-$(compgen -A function test_)}
+       local ret=0
+       for fn in $tests; do
+               setup_testcase
+               echo "*** $fn ***"
+               $fn
+               this_ret=$?
+               if [[ $this_ret -eq 0 ]]; then
+                       echo "pass"
+               else
+                       ret=1
+               fi
+       done
+       return $ret
+}
+
+function lpass()
+{
+       $TEST_LPASS "$@"
+}
+
+function login()
+{
+       # login and download the blob
+       lpass login $TEST_USER >/dev/null 2>&1 && lpass ls >/dev/null 2>&1
+}
+
+setup
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/test/tests 
new/lastpass-cli-1.2.0/test/tests
--- old/lastpass-cli-1.1.2/test/tests   1970-01-01 01:00:00.000000000 +0100
+++ new/lastpass-cli-1.2.0/test/tests   2017-06-07 21:41:30.000000000 +0200
@@ -0,0 +1,233 @@
+#!/bin/bash
+
+. $(dirname $0)/include.sh
+
+function test_login
+{
+       login || return 1
+       assertz $?
+}
+
+function test_login_wrong_pw_should_fail
+{
+       LPASS_ASKPASS=./askpass-wrong.sh login
+       local ret=$?
+       rm .askpass.lock 2>/dev/null
+       assert $ret
+}
+
+function test_add_account
+{
+       login || return 1
+       local name="test-add-account"
+       local url="https://example.com";
+       local username="user27"
+       local password="999999999"
+
+       cat<<__EOM__ | lpass add --sync=no --non-interactive $name
+Name: $name
+URL: $url
+Username: $username
+Password: $password
+__EOM__
+       assertz $? || return 1
+
+       assert_str_eq "$(lpass show --sync=no --password $name)" "$password"
+}
+
+function test_add_note
+{
+       login || return 1
+       local name="test-add-note"
+       cat<<__EOM__ | lpass add --sync=no --note-type=ssn --non-interactive 
$name
+Name: $name
+Number: 000-00-0000
+NoteType: Social Security
+__EOM__
+       assertz $? || return 1
+
+       assert_str_eq "$(lpass show --sync=no --field=Number $name)" 
"000-00-0000"
+}
+
+function test_add_ssn_name
+{
+       login || return 1
+       local name="test-add-note"
+       local person_name="John Doe"
+       cat<<__EOM__ | lpass add --sync=no --note-type=ssn --non-interactive 
$name
+Name: $name
+Name: $person_name
+Number: 000-00-0000
+NoteType: Social Security
+__EOM__
+       assertz $? || return 1
+
+       assert_str_eq "$(lpass show --sync=no --field=Name $name)" 
"$person_name"
+}
+
+function test_add_ssh_key
+{
+    login || return 1
+    local name="test-add-ssh-key"
+       read -r -d '' privkey <<__EOM__
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,5D91CAD2A62C0E3EDCCB853FCB21054D
+
+V/HhFCirRljWoJnjOwwhoFqdRnpWbXQsrppky/uT/Ttb9k5YmC9SLhEZyf8fAReJ
+KGiE8MvnnXKvDMj5eqeWge/YleHsNvyR+8qPqfPha9X/vYCUeR/ZoGg/CKzMVBN3
+bnghFVqB3npQykkkbiBEKLDwosTkR/0JO4I8PRzo34k=
+-----END EC PRIVATE KEY-----
+__EOM__
+    read -r -d '' pubkey <<__EOM__
+ecdsa-sha2-nistp256 
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHiavhPDPg2OH2YjJWDpF9JHpKfnVGB0xc7cSojMXMJftgH4UvgUr8fVJhfp1ix/I8a/a8C0RiCo/Q/A6z3o+7U=
 blah
+__EOM__
+    cat <<__EOM__ | lpass add --sync=no --note-type=ssh-key --non-interactive 
$name
+Name: $name
+Hostname: foobar
+__EOM__
+    echo "$privkey" | tr '\n' ' '| lpass edit --sync=no --field='Private Key' 
--non-interactive $name
+    echo "$pubkey" | lpass edit --sync=no --field='Public Key' 
--non-interactive $name
+    assert_str_eq "$(lpass show --sync=no --field='Private Key' $name)" 
"$privkey"
+    assert_str_eq "$(lpass show --sync=no --field='Public Key' $name)" 
"$pubkey"
+}
+
+function test_edit_username
+{
+       login || return 1
+       local username="new-test-user"
+       echo $username | lpass edit --username --non-interactive test-account
+       assertz $? || return 1
+       assert_str_eq "$(lpass show --sync=no --username test-account)" 
$username
+}
+
+function test_edit_field
+{
+       login || return 1
+       local hostname="quux.bar.com"
+       echo $hostname | lpass edit --field=Hostname --non-interactive test-note
+       assertz $? || return 1
+       assert_str_eq "$(lpass show --sync=no --field=Hostname test-note)" 
$hostname
+}
+
+function test_edit_reprompt
+{
+       login || return 1
+       echo "Reprompt: No" | lpass edit --non-interactive test-reprompt-account
+       assertz $? || return 1
+       assert_str_eq "$(lpass show --sync=no test-reprompt-account | grep 
Reprompt)" ""
+
+       echo "Reprompt: Yes" | lpass edit --non-interactive 
test-reprompt-account
+       assertz $? || return 1
+       assert_str_eq "$(lpass show --sync=no test-reprompt-account | grep 
Reprompt)" "Reprompt: Yes"
+}
+
+function test_edit_reprompt_note
+{
+       login || return 1
+       echo "Reprompt: No" | lpass edit --non-interactive test-reprompt-note
+       assertz $? || return 1
+       assert_str_eq "$(lpass show --sync=no test-reprompt-note | grep 
Reprompt)" ""
+
+       echo "Reprompt: Yes" | lpass edit --non-interactive test-reprompt-note
+       assertz $? || return 1
+       assert_str_eq "$(lpass show --sync=no test-reprompt-note | grep 
Reprompt)" "Reprompt: Yes"
+}
+
+function test_duplicate
+{
+       login || return 1
+       lpass duplicate --sync=no test-account || return 1
+       local numaccts=$(lpass ls --sync=no | grep test-account | wc -l)
+       assert_eq $numaccts 2
+}
+
+function test_generate
+{
+       login || return 1
+       lpass generate --sync=no test-generate 30 >/dev/null || return 1
+       local newpw=$(lpass show --sync=no --password test-generate)
+       assert_eq ${#newpw} 30
+}
+
+function test_show
+{
+       login || return 1
+       read -r -d '' expected <<__EOM__
+test-group/test-account [id: 0001]
+Username: [email protected]
+Password: test-account-password
+URL: https://test-url.example.com/
+__EOM__
+       local out=$(lpass show --sync=no test-account)
+       assert_str_eq "$expected" "$out"
+}
+
+function test_show_reprompt
+{
+       login || return 1
+       read -r -d '' expected <<__EOM__
+test-group/test-reprompt-account [id: 0003]
+Username: [email protected]
+Password: test-account-password
+URL: https://test-url.example.com/
+Reprompt: Yes
+__EOM__
+       local out=$(lpass show --sync=no test-reprompt-account)
+       assert_str_eq "$expected" "$out"
+}
+
+function test_ls
+{
+       login || return 1
+       read -r -d '' expected <<__EOM__
+test-group/test-account [id: 0001]
+test-group/test-note [id: 0002]
+test-group/test-reprompt-account [id: 0003]
+test-group/test-reprompt-note [id: 0004]
+__EOM__
+       local out=$(lpass ls --sync=no)
+       assert_str_eq "$expected" "$out"
+}
+
+function test_export
+{
+       login || return 1
+       read -r -d '' expected <<__EOM__
+url,username,password,extra,name,grouping,fav
+http://sn,,,"NoteType: Server
+Hostname: foo.example.com
+Username: test-note-user
+Password: test-note-password",test-reprompt-note,test-group,0
+https://test-url.example.com/,[email protected],test-account-password,,test-reprompt-account,test-group,0
+http://sn,,,"NoteType: Server
+Hostname: foo.example.com
+Username: test-note-user
+Password: test-note-password",test-note,test-group,0
+https://test-url.example.com/,[email protected],test-account-password,,test-account,test-group,0
+__EOM__
+       local out=$(lpass export --sync=no | tr -d '\r')
+       assert_str_eq "$expected" "$out"
+}
+
+function test_export_extended
+{
+       login || return 1
+       read -r -d '' expected <<__EOM__
+grouping,url,username,password,extra,name,fav,id,group,fullname,last_touch,last_modified_gmt,unknown,attachpresent
+test-group,http://sn,,,"NoteType: Server
+Hostname: foo.example.com
+Username: test-note-user
+Password: 
test-note-password",test-reprompt-note,0,0004,test-group,test-group/test-reprompt-note,skipped,skipped,,0
+test-group,https://test-url.example.com/,[email protected],test-account-password,,test-reprompt-account,0,0003,test-group,test-group/test-reprompt-account,skipped,skipped,,0
+test-group,http://sn,,,"NoteType: Server
+Hostname: foo.example.com
+Username: test-note-user
+Password: 
test-note-password",test-note,0,0002,test-group,test-group/test-note,skipped,skipped,,0
+test-group,https://test-url.example.com/,[email protected],test-account-password,,test-account,0,0001,test-group,test-group/test-account,skipped,skipped,,0
+__EOM__
+local out=$(lpass export --sync=no 
--fields=grouping,url,username,password,extra,name,fav,id,group,fullname,last_touch,last_modified_gmt,unknown,attachpresent
 | tr -d '\r')
+       assert_str_eq "$expected" "$out"
+}
+
+runtests "$@"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lastpass-cli-1.1.2/version.h 
new/lastpass-cli-1.2.0/version.h
--- old/lastpass-cli-1.1.2/version.h    2017-02-03 21:39:33.000000000 +0100
+++ new/lastpass-cli-1.2.0/version.h    2017-06-07 21:41:30.000000000 +0200
@@ -1,2 +1,2 @@
-#define LASTPASS_CLI_VERSION "1.1.2"
+#define LASTPASS_CLI_VERSION "1.2.0"
 #define LASTPASS_CLI_USERAGENT "LastPass-CLI/" LASTPASS_CLI_VERSION


Reply via email to