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", ¶ms); } -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(¶ms, "sharedfolderid", account->share->id, NULL); upload_queue_enqueue(sync, key, session, "loglogin.php", ¶ms); + + 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
