pax_global_header00006660000000000000000000000064146214321260014513gustar00rootroot0000000000000052 comment=d8e9e810d4517e8df0fd5446fc345cd9a5d2d52f lastpass-cli-1.5.0/000077500000000000000000000000001462143212600141155ustar00rootroot00000000000000lastpass-cli-1.5.0/.dockerignore000066400000000000000000000000251462143212600165660ustar00rootroot00000000000000/build/ /test/.lpass lastpass-cli-1.5.0/.editorconfig000066400000000000000000000002441462143212600165720ustar00rootroot00000000000000# EditorConfig is awesome: http://EditorConfig.org root = true [*] end_of_line = lf insert_final_newline = true indent_style = tab indent_size = 4 charset = utf-8 lastpass-cli-1.5.0/.gitignore000066400000000000000000000001501462143212600161010ustar00rootroot00000000000000*.o *.d *.swp lpass lpass.1 lpass.exe certificate.h tags build test/.lpass version.h # IDE /.idea /.vs lastpass-cli-1.5.0/.travis.yml000066400000000000000000000010141462143212600162220ustar00rootroot00000000000000language: cpp sudo: required dist: trusty osx_image: xcode7 compiler: - gcc - clang os: - linux - osx before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libxml2 || true ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install cmake || true ; fi - 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: make && make test lastpass-cli-1.5.0/CHANGELOG.md000066400000000000000000000271751462143212600157420ustar00rootroot00000000000000# Version 1.5.0 * Add support for URL logging * Fix segmentation faults on Mac OS X * Fix synchronization issues, which caused empty IDs * Fix feature flags file deletion on logout # Version 1.4.0 * Add support for writing encrypted URLs * Add support for feature flags * Fix shared folder username # Version 1.3.7 * Add support for reading encrypted URLs (Tibor Komlossy) * Fix GCC 10 compatibility issue #532 (Tibor Komlossy) # Version 1.3.6 * Fix version (Béla Ormos) # Version 1.3.5 * Updating certificate hashes (Béla Ormos) # Version 1.3.4 * Updating post parameter (Gergely Der) # Version 1.3.3 * Decrease the time for the cli app to do things (Wesley Schwengle) * 'blob_load' refactor (Wesley Schwengle) * Fixed bug where logout requires login (Wesley Schwengle) * Fix non-default PKG_CONFIG_PATH on macOS (Alyssa Ross) # Version 1.3.2 * Don't require using make (Eli Schwartz) * Disable IPv6 support (Wesley Schwengle) * Link against Brew Curl on MacOS (Tom Sullivan) * Autogenerate versions from git (Wesley Schwengle/Eli Schwartz) * Remove memory leak in `config_path_for_type` (Tom Sullivan) * Install bash-completions in PREFIX dir (Wesley Schwengle) * Include `libgen.h` for BSD builds (Tom Sullivan) * Create subdirectories when determining config path (Tom Sullivan) * Only show basename in usage (William Casarin) * Fix segmentation fault on BSD while running `make test` (Björn Ketelaars) * README.md updates: * Brew formula installation (Thomas Haggett) * Ubuntu Xenial dependencies (Nick Timkovich) * Debian stable/testing and Ubuntu dependencies (Wesley Schwengle) # Version 1.3.1 * Revert "pins: remove GlobalSign R1/R3 pins" from Robert Copeland * Readme update from Wesley Schwengle * Add Dockerfile to create a clean build environment from Wesley Schwengle * Missing dependencies in readme * Added CLion project files to ignore list # Version 1.3.0 * `lpass show` now supports `--json` format * `lpass show` now supports `--quiet` flag to suppress prompts, from Pau Sanchez * `lpass import` has `--keep-dupes` flag which will preserve duplicate accounts on import * `LPASS_PINENTRY` environment variable may now be used to set custom path to pinentry, from Martynas Mickevičius * Build fix for aarch64 and others from Natanael Copa * New fish completions from Israel Chauca Fuentes * Zsh completions from Richard Hillmann * Brew build instructions updates from Roger D. Winans * bugfix: site notes now show up in notes textarea instead of fields * spelling fixes from Josh Soref # Version 1.2.2 * `lpass ls --format` now supports "%al" to show URL, from Yikai Zhao * $VISUAL can be used in preference to $EDITOR, from Wesley Schwengle * `lpass edit` can now directly edit multiline ssh keys * fields are now preserved when edited with `lpass edit` * Bugfix: use-after-free in http.c fixed, from Björn Ketelaars * Bugfix: command-line completion now works for names with spaces * Bugfix: loading attachments from shared folders now works, from Spencer Whyte * Debian packing updates from Hannes Hörl * Documentation updates from Darragh Grealish and Steven Liekens # Version 1.2.1 * Bugfix: fix regression with ```lpass show``` not displaying all fields for secure notes * Use sysctl instead of procfs for pid-to-cmd on some versions of BSD, from Thomas Hurst * Build: fix build for test binaries on OpenBSD, from Björn Ketelaars # 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 * Bugfix: sort order in ```lpass ls``` is now consistent whether or not colors are used * Documentation has been enhanced to describe aliases and more options, with patches by Eric B. Hymowitz. * Build: debian package fixed for rebuild issues and missing build dependencies # Version 1.1.1 * Bugfix: fix crash in ```lpass show``` for secure notes without attachments * Build: fix build on OpenBSD * Build: fix build when using LibreSSL # Version 1.1.0 * New command ```lpass import``` can import an existing csv file (or output from ```lpass export``` into the vault * ```lpass show``` and ```lpass ls``` learned a ```--format``` argument to enable user-specified printf-style formats * Bash completions will now complete field names if ```--field``` is specified after the account name * Build: cmake now used for building, by Filippo Cucchetto and with fixes by Eli Schwartz * Build: lpass has been updated to work with OpenSSL 1.1; please note that libcurl-openssl must also be linked against the same version in order to avoid mysterious segfaults * Bugfix: crash in ```lpass ls -l``` with no last_modified_gmt fixed * Bugfix: secure notes editing with "Name" fields now works properly * Bugfix: editing secure note names now works (github #106) * Bugfix: lpass-created server secure notes are now compatible with the plugin * Bugfix: ```generate``` now uses all defined characters, by Ignat Korchagin * Bugfix: ```lpass show``` for ssh-key secure notes no longer corrupts password-protected ssh keys (github #232) # Version 1.0.0 * New command ```lpass status``` shows whether or not the user is logged in with agent, from Nick Knudson * ```lpass add``` can now be passed ```--note-type=X``` in order to add a secure note using a template. Specifying an unknown note template will list the available templates. * ```lpass ls``` now shows username with ```--long```, from Alli Witheford * Bash completions are now installed with make install, from Eli Schwartz * Fish shell completions supplied by Joar Wanboarg * Initial support for adding (```lpass add --app```) and editing applications * Updates to manpage for ```ls```, ```passwd```, ```add```, and basic usage examples * lpass now follows XDG base directory specifications for its files on platforms that use it. Set ```LP_HOME``` to ~/.lpass to keep the previous location * Bugfix: resolved syncing problems on some platforms (notably RHEL/CentOS) related to improper multiprocess usage of libcurl (github #166) * Bugfix: ```lpass show``` no longer crashes when a searched-for field is not found (github #167) * Bugfix: ```lpass``` no longer exits with an error if the blob is empty but otherwise without parsing errors. This fixes the case where a new user could not use the application without first adding a site elsewhere. * ```LPASS_LOG_LEVEL``` learned level=8 with which lpass will also dump libcurl verbose logs showing all traffic for debugging (not recommended for general use due to potentially sensitive headers being logged). # Version 0.9.0 * Add support for accounts in the EU datacenter (lastpass.eu) * ```lpass ls``` now sorts its output and properly displays group folder account entries * ```lpass export``` output has been reworked to match that of the website, from Justen Walker * ```lpass share limit``` subcommand was added which allows displaying and modifying user-specific restrictions for shared folders * The new ```LPASS_LOG_LEVEL``` environment variable can be set to cause the lpass uploader process to log its actions, useful for debugging syncing issues. Set it to 7 to get all debug logs; the logfile will be ~/.lpass/lpass.log. * Bugfix: syncing is fixed on systems that use XFS or other filesystems which do not support setting d_type in readdir() * Bugfix: ```lpass mv``` now works properly with linked accounts # Version 0.8.1, 0.7.2, 0.6.1, 0.5.1 * This update to all recent versions switches to the platform certificate store and adds pinning of LastPass public keys, in preparation for certificate changes at lastpass.com. Upgrade will be needed to avoid "Peer certificate cannot be authenticated with given CA certificates" errors when the cert changes are made. # Version 0.8.0 * New command ```lpass add``` works like ```lpass edit``` for new accounts * New command ```lpass mv``` can be used to move an account into a different (possibly shared) folder * New command ```lpass passwd``` can be used to change master password * Tab-completion for bash is now available; to use, source ```contrib/lpass_bash_completion``` from a bash startup file * ```lpass ls``` now interprets backslash properly for subfolder display * ```lpass edit``` gained the ability to edit all fields of an account at once by using a specially-formatted edit buffer * ```lpass show``` gained the ability to show multiple accounts at once, from Angus Galloway * ```lpass show``` now reformats SSH private key fields in secure notes into a usable form * ```lpass share useradd``` gained the ability to specify group names * ```lpass share``` got better documentation * Bugfix: logins with certain multifactors that support out-of-band authentication will now work correctly * Blob edits no longer reencrypt the entire database, just the changed accounts * Syncing operation is now much more robust in the face of server errors or invalid transactions. * OSX builds fixed for Xcode-less installations, with help from Wael Nasreddine * Corrections to FSF address from Tom Prince # Version 0.7.1 * This bugfix release fixes a build issue on OSX platforms without XCode. It is otherwise identical to 0.7.0. # Version 0.7.0 * ```lpass``` now supports aliases in order to set preferred switches or nicknames for commands. ```echo 'show -G' > ~/.lpass/alias.show```, for example, will turn regex matching on for ```lpass show```. * In addition to pinentry and in-process prompting, the ```LPASS_ASKPASS``` environment variable/config value is now checked for a binary to ask for passwords. It uses the same conventions as ssh-askpass. * ```lpass show``` will now match account id when using regex or substring matching * ```lpass ls``` learned the ```-l [-u]```switches to show mod and use times, from Lloyd Zusman * Secure notes are now created by default when empty sites are edited with --notes, from Lloyd Zusman * The new ```LPASS_CLIPBOARD_COMMAND``` environment variable/config value can be used to configure the helper application for the system clipboard, from Tom Prince. Among other things, you can use this to clear the clipboard after a certain number of pastes with ```xclip -l```. * Various code cleanups and documentation fixes from Tom Prince. * The license has been clarified to GPLv2 or later, plus the OpenSSL exception; please see individual files and the LICENSE.OpenSSL / COPYING files for details. This was the intended license all along but it was not spelled out consistently. # Version 0.6.0 * New share sub-command allows automating some common tasks with shared folders * PBKDF2 speedups from Thomas Hurst * Ungrouped entries now fall under "(none)" heading, from Gordon Celesta * Documentation updates from Eli Young * Cleanups from Björn Ketelaars # Version 0.5.1 * Update Thawte CA cert to support lastpass.com's new SHA-256 cert. # Version 0.5.0 * OpenBSD support * Updated build/install instructions for Cygwin, Debian, and RPM-based distributions * Regex and substring searching for cmd-show * Secure note parsing and field display * Fixes for pinentry errors and hangs lastpass-cli-1.5.0/CMakeLists.txt000066400000000000000000000177701462143212600166710ustar00rootroot00000000000000if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 3.1) set(CMAKE_LEGACY_CYGWIN_WIN32 0) # Remove when CMake > 2.8.4 is required cmake_minimum_required(VERSION 2.8) else() cmake_minimum_required(VERSION 3.1) endif() project(lpass) include(GNUInstallDirs) find_package(PkgConfig REQUIRED) if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 3.4) # pkg_get_variable is not available until CMake >= 3.4.0 # Debian oldstable still packages CMake 3.0.2 function(pkg_get_variable _output_name _pkg _name) execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=${_name} ${_pkg} OUTPUT_VARIABLE _pkg_result OUTPUT_STRIP_TRAILING_WHITESPACE) set("${_output_name}" "${_pkg_result}" CACHE STRING "pkg-config variable ${_name} of ${_pkg}") endfunction() pkg_get_variable(BASH_COMPLETION_PREFIX bash-completion prefix) if(BASH_COMPLETION_PREFIX) set(BASH_COMPLETION_FOUND TRUE) endif() else() include(FindPkgConfig) pkg_search_module(BASH_COMPLETION bash-completion) endif() if(APPLE) find_program(BREW_INSTALLED BREW) if(BREW_INSTALLED) function(get_brew_packageconfig_path _package_name _output_variable) execute_process(COMMAND brew --cellar ${_package_name} RESULT_VARIABLE _result_var_cellar OUTPUT_VARIABLE _output_variable_cellar OUTPUT_STRIP_TRAILING_WHITESPACE) if(_result_var_cellar AND NOT STATUS EQUAL 0) message(STATUS "Unable to obtain homebrew cellar location for package ${_package_name}. Error: ${_result_var_cellar}") else() execute_process(COMMAND find "${_output_variable_cellar}/" -type d -name pkgconfig COMMAND tr -d '\n' COMMAND tr -s '/' RESULT_VARIABLE _brew_pkgconfig_c OUTPUT_VARIABLE _brew_pkgconfig_cellar OUTPUT_STRIP_TRAILING_WHITESPACE) endif() execute_process(COMMAND brew --prefix ${_package_name} RESULT_VARIABLE _result_var_prefix OUTPUT_VARIABLE _output_variable_prefix OUTPUT_STRIP_TRAILING_WHITESPACE) if(_result_var_prefix AND NOT STATUS EQUAL 0) message(STATUS "Unable to obtain homebrew prefix location for package ${_package_name}. Error: ${_result_var_cellar}") else() execute_process(COMMAND find "${_output_variable_prefix}/" -type d -name pkgconfig COMMAND tr -d '\n' COMMAND tr -s '/' RESULT_VARIABLE _brew_pkgconfig_p OUTPUT_VARIABLE _brew_pkgconfig_prefix OUTPUT_STRIP_TRAILING_WHITESPACE) endif() set("${_output_variable}" "${_brew_pkgconfig_cellar}:${_brew_pkgconfig_prefix}" CACHE STRING "pkg-config homebrew locations for ${_package_name}") endfunction() # Get pkg-config paths for curl/openssl installed via homebrew get_brew_packageconfig_path("curl" BREW_CURL) get_brew_packageconfig_path("openssl" BREW_OPENSSL) set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}/usr/local/opt/curl/lib/pkgconfig:/usr/local/opt/openssl:${BREW_CURL}:${BREW_OPENSSL}") message(STATUS "PKG_CONFIG_PATH: $ENV{PKG_CONFIG_PATH}") else() set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/curl/lib/pkgconfig:/usr/local/opt/openssl") endif() endif() find_package(LibXml2 REQUIRED) include_directories(${LIBXML2_INCLUDE_DIR}) find_package(OpenSSL REQUIRED) include_directories(${OPENSSL_INCLUDE_DIR}) find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIR}) set(PROJECT_NAME lpass) file(GLOB PROJECT_HEADERS *.h version.h) file(GLOB PROJECT_SOURCES *.c) set(PROJECT_DEFINITIONS "_GNU_SOURCE") set(PROJECT_FLAGS "-std=gnu99 -pedantic -Wall -Wextra -Wno-language-extension-token") if(APPLE) set(PROJECT_FLAGS "${PROJECT_FLAGS} -Wno-deprecated-declarations") endif() execute_process(COMMAND ./LASTPASS-VERSION-GEN WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) # 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") endif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") add_custom_command(OUTPUT lpass.1 DEPENDS ${CMAKE_SOURCE_DIR}/lpass.1.txt COMMAND a2x -D ./ --no-xmllint -f manpage ${CMAKE_SOURCE_DIR}/lpass.1.txt) add_custom_command(OUTPUT lpass.1.html DEPENDS ${CMAKE_SOURCE_DIR}/lpass.1.txt COMMAND asciidoc -b html5 -a data-uri -a icons -a toc2 -o lpass.1.html ${CMAKE_SOURCE_DIR}/lpass.1.txt) install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) if(BASH_COMPLETION_FOUND) pkg_get_variable(BASH_COMPLETION_COMPLETIONSDIR bash-completion completionsdir) # Fix GH-478 if(NOT "${BASH_COMPLETION_PREFIX}" STREQUAL "${CMAKE_INSTALL_PREFIX}") string(REGEX REPLACE "^${BASH_COMPLETION_PREFIX}" "${CMAKE_INSTALL_PREFIX}" COMP_DIR ${BASH_COMPLETION_COMPLETIONSDIR}) set(BASH_COMPLETION_COMPLETIONSDIR ${COMP_DIR}) unset(COMP_DIR) endif() 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}) if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") target_link_libraries(lpass-test "-lkvm") endif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") 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_note_with_field ${CMAKE_SOURCE_DIR}/test/tests test_add_note_with_field) add_test(test_add_site_note ${CMAKE_SOURCE_DIR}/test/tests test_add_site_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_ssh_key ${CMAKE_SOURCE_DIR}/test/tests test_edit_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_json ${CMAKE_SOURCE_DIR}/test/tests test_show_json) add_test(test_show_note ${CMAKE_SOURCE_DIR}/test/tests test_show_note) 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 add_custom_target(install-doc COMMAND ${CMAKE_COMMAND} -DMANDIR=${CMAKE_INSTALL_FULL_MANDIR} -P ${CMAKE_SOURCE_DIR}/cmake_extras/install_doc.cmake DEPENDS doc-man) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -DPROJECT_NAME=${PROJECT_NAME} -DMANDIR=${CMAKE_INSTALL_FULL_MANDIR} -P ${CMAKE_SOURCE_DIR}/cmake_extras/uninstall.cmake) lastpass-cli-1.5.0/CONTRIBUTING000066400000000000000000000043171462143212600157540ustar00rootroot00000000000000 Contributions are welcome! Please open a pull request at github: https://github.com/lastpass/lastpass-cli The project is licensed under the GPL version 2 or later with an exception for linking with OpenSSL (see COPYING and LICENSE.OpenSSL), and requires acceptance of the Developer's Certificate of Origin for contributions (see below). To indicate your acceptance of Developer's Certificate of Origin 1.1 terms, please add the following line to the end of the commit message for each contribution you make to the project: Signed-off-by: Your Name using your real name. Pseudonyms or anonymous contributions cannot unfortunately be accepted. --------------------------------------------------------------------------- Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. --------------------------------------------------------------------------- lastpass-cli-1.5.0/COPYING000066400000000000000000000432541462143212600151600ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. lastpass-cli-1.5.0/LASTPASS-VERSION-GEN000077500000000000000000000020061462143212600170250ustar00rootroot00000000000000#!/bin/sh # This file has been adopted from the git project # You can find the original at https://github.com/git/git/blob/master/GIT-VERSION-GEN LPVF=version.h DEF_VER=v1.5.0.GIT LF=' ' # First see if there is a version file (included in release tarballs), # then try git-describe, then default. if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d ${GIT_DIR:-.git} -o -f .git && VN=$(git describe --match "v[0-9]*" HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) git update-index -q --refresh test -z "$(git diff-index --name-only HEAD --)" || VN="$VN-dirty" ;; esac then VN=$(echo "$VN" | sed -e 's/-/./g'); else VN="$DEF_VER" fi VN=$(expr "$VN" : v*'\(.*\)') if test -r $LPVF then VC=$(sed -ne 's/^#define LASTPASS_CLI_VERSION "\(.*\)"/\1/p' <$LPVF) else VC=unset fi test "$VN" = "$VC" || { echo >&2 "LASTPASS_CLI_VERSION =$VN" echo "#define LASTPASS_CLI_VERSION \"$VN\"" >$LPVF echo "#define LASTPASS_CLI_USERAGENT \"LastPass-CLI/\" LASTPASS_CLI_VERSION" >>$LPVF } lastpass-cli-1.5.0/LICENSE.OpenSSL000066400000000000000000000154721462143212600164150ustar00rootroot00000000000000Certain source files in this program permit linking with the OpenSSL library (http://www.openssl.org), which otherwise wouldn't be allowed under the GPL. For purposes of identifying OpenSSL, most source files giving this permission limit it to versions of OpenSSL having a license identical to that listed in this file (LICENSE.OpenSSL). It is not necessary for the copyright years to match between this file and the OpenSSL version in question. However, note that because this file is an extension of the license statements of these source files, this file may not be changed except with permission from all copyright holders of source files in this program which reference this file. LICENSE ISSUES ============== The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts. Actually both licenses are BSD-style Open Source licenses. In case of any license issues related to OpenSSL please contact openssl-core@openssl.org. OpenSSL License --------------- /* ==================================================================== * Copyright (c) 1998-2001 The OpenSSL Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" * * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * openssl-core@openssl.org. * * 5. Products derived from this software may not be called "OpenSSL" * nor may "OpenSSL" appear in their names without prior written * permission of the OpenSSL Project. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit (http://www.openssl.org/)" * * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim * Hudson (tjh@cryptsoft.com). * */ Original SSLeay License ----------------------- /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" * The word 'cryptographic' can be left out if the routines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ lastpass-cli-1.5.0/Makefile000066400000000000000000000014301462143212600155530ustar00rootroot00000000000000PREFIX ?= /usr MANDIR ?= $(PREFIX)/share/man BUILDDIR=build CMAKEMAKE=$(BUILDDIR)/Makefile CMAKEOPTS=-DCMAKE_INSTALL_PREFIX:PATH=$(PREFIX) -DCMAKE_INSTALL_MANDIR:PATH=$(MANDIR) all: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) all clean: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) clean $(RM) -r $(BUILDDIR) doc-man: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) doc-man doc-html: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) doc-html install-doc: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) install-doc install: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) install test: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) lpass-test && $(MAKE) -C $(BUILDDIR) test uninstall: $(CMAKEMAKE) $(MAKE) -C $(BUILDDIR) uninstall $(CMAKEMAKE): mkdir -p $(BUILDDIR) && cd $(BUILDDIR) && cmake $(CMAKEOPTS) .. .PHONY: all doc-man clean $(CMAKEMAKE) lastpass-cli-1.5.0/README.md000066400000000000000000000123221462143212600153740ustar00rootroot00000000000000# LastPass CLI #### (c) 2014-2019 LastPass. Command line interface to [LastPass.com](https://lastpass.com/). ## Operating System Support `lpass` is designed to run on GNU/Linux, Cygwin and Mac OS X. ## Dependencies * [LibreSSL](http://www.libressl.org/) or [OpenSSL](https://www.openssl.org/) * [libcurl](http://curl.haxx.se/) * [libxml2](http://xmlsoft.org/) * [pinentry](https://www.gnupg.org/related_software/pinentry/index.en.html) (optional) * [AsciiDoc](http://www.methods.co.nz/asciidoc/) (build-time documentation generation only) * [xclip](http://sourceforge.net/projects/xclip/), [xsel](http://www.vergenet.net/~conrad/software/xsel/), [pbcopy](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/pbcopy.1.html), or [putclip from cygutils-extra](https://cygwin.com/cgi-bin2/package-grep.cgi?grep=cygutils-extra) for clipboard support (optional) ### Installing on Linux #### Arch * A binary package is available from the community repository, use pacman to simple install lastpass-cli. * Can be build from source with the "lastpass-cli-git" *[Arch User Repository (AUR)](https://aur.archlinux.org/packages.php?O=0&L=0&C=0&K=lastpass-cli). Information about installing packages from the AUR [can be found on the Arch wiki](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages). ``` # from community repository sudo pacman -S lastpass-cli # from AUR repository packer -S lastpass-cli-git ``` #### Fedora * Packages are available in Fedora 22 and later. ``` sudo dnf install lastpass-cli ``` #### Red Hat/Centos * Packages are available in [EPEL](https://fedoraproject.org/wiki/EPEL) for RHEL/CentOS 7 and later. ``` sudo yum install lastpass-cli ``` * For older versions: Install the needed build dependencies, and then follow instructions in the 'Building' section. ``` sudo yum install openssl libcurl libxml2 pinentry xclip openssl-devel libxml2-devel libcurl-devel gcc gcc-c++ make cmake ``` #### Debian/Ubuntu * Install the needed build dependencies, and then follow instructions in the 'Building' section. * For Ubuntu 16.04 (xenial) ``` apt-get --no-install-recommends -yqq install \ bash-completion \ build-essential \ cmake \ libcurl3 \ libcurl3-openssl-dev \ libssl1.0.0 \ libssl-dev \ libxml2 \ libxml2-dev \ pkg-config \ ca-certificates \ xclip ``` * For Debian (stable/oldstable) and other Ubuntus < 18.04 ``` apt-get --no-install-recommends -yqq install \ bash-completion \ build-essential \ cmake \ libcurl3 \ libcurl3-openssl-dev \ libssl1.0 \ libssl1.0-dev \ libxml2 \ libxml2-dev \ pkg-config \ ca-certificates \ xclip ``` * For Debian (testing/experimental) and Ubuntu >= 18.04 ``` apt-get --no-install-recommends -yqq install \ bash-completion \ build-essential \ cmake \ libcurl4 \ libcurl4-openssl-dev \ libssl-dev \ libxml2 \ libxml2-dev \ libssl1.1 \ pkg-config \ ca-certificates \ xclip ``` #### Gentoo * Install the package: ``` sudo emerge lastpass-cli ``` #### Other Linux Distros Install the packages listed in the Dependencies section of this document, and then follow instructions in the 'Building' section. ### Installing on OS X #### With [Homebrew](http://brew.sh/) (easiest) * Install Homebrew, if necessary. * Update Homebrew's local formula cache: ``` brew update ``` * Install the lastpass-cli formula: ``` brew install lastpass-cli ``` #### With [MacPorts](https://www.macports.org/) * [Install MacPorts](https://www.macports.org/install.php), if necessary. * Update MacPorts' local ports tree: ``` sudo port selfupdate ``` * Install the lastpass-cli port: ``` sudo port install lastpass-cli ``` * Optionally install the documentation: ``` sudo port install lastpass-cli-doc ``` #### Manually Install the packages listed in the Dependencies section of this document, and then follow instructions in the 'Building' section. ### Installing on FreeBSD * Install the binary package: ``` sudo pkg install security/lastpass-cli ``` * Or build the port yourself: ``` sudo make -C /usr/ports/security/lastpass-cli all install clean ``` ### Installing on Cygwin * Install [apt-cyg](https://github.com/transcode-open/apt-cyg) * Using apt-cyg, install the needed build dependencies, and then follow instructions in the 'Building' section. ``` apt-cyg install wget make cmake gcc-core gcc-g++ openssl-devel libcurl-devel libxml2-devel libiconv-devel cygutils-extra ``` ## Building $ 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 $ sudo make install These environment variables can be passed to make to do the right thing: `PREFIX`, `DESTDIR`, `BINDIR`, `LIBDIR`, `MANDIR`. ## Running If you've installed it: $ lpass Otherwise, from the build directory: $ ./lpass ## 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. $ sudo make install-doc Once installed, $ man lpass You can view the full documentation in the manpage, `man lpass` or [view it online](https://lastpass.github.io/lastpass-cli/lpass.1.html). lastpass-cli-1.5.0/agent.c000066400000000000000000000212451462143212600153630ustar00rootroot00000000000000/* * agent for caching decryption key * * Copyright (C) 2014-2018 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 "agent.h" #include "config.h" #include "util.h" #include "password.h" #include "terminal.h" #include "process.h" #include #include #include #include #include #include #include #include #include #if (defined(__unix__) || defined(unix)) && !defined(USG) #include #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 { pid_t pid; uid_t uid; gid_t gid; }; #endif #define AGENT_VERIFICATION_STRING "`lpass` was written by LastPass.\n" static inline char *agent_socket_path(void) { return config_path("agent.sock"); } bool agent_load_key(unsigned char key[KDF_HASH_LEN]) { _cleanup_free_ char *iterationbuf = NULL; _cleanup_free_ char *verify = NULL; _cleanup_free_ char *username = NULL; _cleanup_free_ char *password = NULL; int iterations; iterationbuf = config_read_string("iterations"); username = config_read_string("username"); if (!iterationbuf || !username || !config_exists("verify")) return false; iterations = strtoul(iterationbuf, NULL, 10); if (iterations <= 0) return false; for (;;) { free(password); password = password_prompt("Master Password", password ? "Incorrect master password; please try again." : NULL, "Please enter the LastPass master password for <%s>.", username); if (!password) return false; kdf_decryption_key(username, password, iterations, key); /* no longer need password contents, zero it */ secure_clear_str(password); verify = config_read_encrypted_string("verify", key); if (verify && !strcmp(verify, AGENT_VERIFICATION_STRING)) break; } return true; } _noreturn_ static void agent_cleanup(int signal) { UNUSED(signal); char *path = agent_socket_path(); unlink(path); free(path); _exit(EXIT_SUCCESS); } #if defined(__linux__) || defined(__CYGWIN__) static int agent_socket_get_cred(int fd, struct ucred *cred) { socklen_t credlen = sizeof(struct ucred); return getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &credlen); } #elif defined(__APPLE__) && defined(__MACH__) || defined(BSD) static int agent_socket_get_cred(int fd, struct ucred *cred) { if (getpeereid(fd, &cred->uid, &cred->gid) < 0) return -1; if (read(fd, &cred->pid, sizeof(cred->pid)) != sizeof(cred->pid)) return -1; return 0; } #endif void _assert_socket_sun_path(struct sockaddr_un *sa, char *path) { if (strlen(path) >= sizeof(sa->sun_path)) { die("Path too large for agent control socket."); } } int _setup_agent_socket(struct sockaddr_un *sa, char *path) { int fd; _assert_socket_sun_path(sa, path); memset(sa, 0, sizeof(*sa)); sa->sun_family = AF_UNIX; strlcpy(sa->sun_path, path, sizeof(sa->sun_path)); fd = socket(AF_UNIX, SOCK_STREAM, 0); return fd; } static void agent_run(unsigned const char key[KDF_HASH_LEN]) { char *agent_timeout_str; unsigned int agent_timeout; struct sockaddr_un sa, listensa; struct ucred cred; int fd, listenfd; socklen_t len; signal(SIGHUP, agent_cleanup); signal(SIGINT, agent_cleanup); signal(SIGQUIT, agent_cleanup); signal(SIGTERM, agent_cleanup); signal(SIGALRM, agent_cleanup); agent_timeout_str = getenv("LPASS_AGENT_TIMEOUT"); agent_timeout = 60 * 60; /* One hour by default. */ if (agent_timeout_str && strlen(agent_timeout_str)) agent_timeout = strtoul(agent_timeout_str, NULL, 10); if (agent_timeout) alarm(agent_timeout); _cleanup_free_ char *path = agent_socket_path(); fd = _setup_agent_socket(&sa, path); unlink(path); if (bind(fd, (struct sockaddr *)&sa, SUN_LEN(&sa)) < 0 || listen(fd, 16) < 0) { listenfd = errno; close(fd); unlink(path); errno = listenfd; die_errno("bind|listen"); } for (len = sizeof(listensa); (listenfd = accept(fd, (struct sockaddr *)&listensa, &len)) > 0; len = sizeof(listensa)) { if (agent_socket_get_cred(listenfd, &cred) < 0) { close(listenfd); continue; } if (cred.uid != getuid() || cred.gid != getgid() || !process_is_same_executable(cred.pid)) { close(listenfd); continue; } #if SOCKET_SEND_PID == 1 pid_t pid = getpid(); IGNORE_RESULT(write(listenfd, &pid, sizeof(pid))); #endif IGNORE_RESULT(write(listenfd, key, KDF_HASH_LEN)); close(listenfd); } listenfd = errno; close(fd); unlink(path); errno = listenfd; die_errno("accept"); } void agent_kill(void) { struct sockaddr_un sa; struct ucred cred; int fd; _cleanup_free_ char *path = agent_socket_path(); fd = _setup_agent_socket(&sa, path); if (connect(fd, (struct sockaddr *)&sa, SUN_LEN(&sa)) < 0) goto out; #if SOCKET_SEND_PID == 1 pid_t pid = getpid(); if (write(fd, &pid, sizeof(pid)) != sizeof(pid)) goto out; #endif if (agent_socket_get_cred(fd, &cred) < 0) goto out; kill(cred.pid, SIGTERM); out: close(fd); } bool agent_ask(unsigned char key[KDF_HASH_LEN]) { struct sockaddr_un sa; int fd; bool ret = false; _cleanup_free_ char *path = agent_socket_path(); fd = _setup_agent_socket(&sa, path); ret = connect(fd, (struct sockaddr *)&sa, SUN_LEN(&sa)) >= 0; if (!ret) goto out; #if SOCKET_SEND_PID == 1 pid_t pid = getpid(); ret = write(fd, &pid, sizeof(pid)) == sizeof(pid); if (!ret) goto out; ret = read(fd, &pid, sizeof(pid)) == sizeof(pid); if (!ret) goto out; #endif ret = read(fd, key, KDF_HASH_LEN) == KDF_HASH_LEN; if (!ret) goto out; out: close(fd); return ret; } static void agent_start(unsigned const char key[KDF_HASH_LEN]) { pid_t child; agent_kill(); if (config_exists("plaintext_key")) return; char *disable_str = getenv("LPASS_AGENT_DISABLE"); if (disable_str && !strcmp(disable_str, "1")) { return; } child = fork(); if (child < 0) die_errno("fork(agent)"); if (child == 0) { int null = open("/dev/null", 0); if (null < 0) _exit(EXIT_FAILURE); dup2(null, 0); dup2(null, 1); dup2(null, 2); close(null); setsid(); if (chdir("/") < 0) _exit(EXIT_FAILURE); process_disable_ptrace(); process_set_name("lpass [agent]"); agent_run(key); _exit(EXIT_FAILURE); } } bool agent_get_decryption_key(unsigned char key[KDF_HASH_LEN]) { if (config_exists("plaintext_key")) { _cleanup_free_ unsigned char *key_buffer = NULL; if (config_read_buffer("plaintext_key", &key_buffer) == KDF_HASH_LEN) { _cleanup_free_ char *verify = config_read_encrypted_string("verify", (unsigned char *)key_buffer); if (!verify || strcmp(verify, AGENT_VERIFICATION_STRING)) goto badkey; memcpy(key, key_buffer, KDF_HASH_LEN); secure_clear(key_buffer, KDF_HASH_LEN); mlock(key, KDF_HASH_LEN); return true; } badkey: config_unlink("plaintext_key"); } if (!agent_ask(key)) { if (!agent_load_key(key)) return false; agent_start(key); } mlock(key, KDF_HASH_LEN); return true; } void agent_save(const char *username, int iterations, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *iterations_str = xultostr(iterations); config_write_string("iterations", iterations_str); config_write_string("username", username); config_write_encrypted_string("verify", AGENT_VERIFICATION_STRING, key); agent_start(key); } lastpass-cli-1.5.0/agent.h000066400000000000000000000005541462143212600153700ustar00rootroot00000000000000#ifndef AGENT_H #define AGENT_H #include "kdf.h" #include bool agent_get_decryption_key(unsigned char key[KDF_HASH_LEN]); void agent_save(const char *username, int iterations, unsigned const char key[KDF_HASH_LEN]); void agent_kill(void); bool agent_ask(unsigned char key[KDF_HASH_LEN]); bool agent_load_key(unsigned char key[KDF_HASH_LEN]); #endif lastpass-cli-1.5.0/blob.c000066400000000000000000001056271462143212600152120ustar00rootroot00000000000000/* * encrypted vault parsing * * Copyright (C) 2014-2024 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 "blob.h" #include "config.h" #include "endpoints.h" #include "cipher.h" #include "util.h" #include "upload-queue.h" #include "version.h" #include #include #include #include #include #include #include #if defined(__APPLE__) && defined(__MACH__) #include #define htobe32(x) OSSwapHostToBigInt32(x) #define be32toh(x) OSSwapBigToHostInt32(x) #else # if (defined(__unix__) || defined(unix)) && !defined(USG) # include # endif # if defined(BSD) # include # else # include # endif #endif struct app *account_to_app(const struct account *account) { return container_of(account, struct app, account); } void share_free(struct share *share) { if (!share) return; free(share->name); free(share->id); free(share->chunk); free(share); } void field_free(struct field *field) { if (!field) return; free(field->name); free(field->value); free(field->value_encrypted); free(field->type); free(field); } void account_free_contents(struct account *account) { struct field *field, *tmp; free(account->id); free(account->name); free(account->group); free(account->fullname); free(account->url); free(account->username); free(account->password); free(account->note); free(account->name_encrypted); free(account->group_encrypted); free(account->url_encrypted); free(account->username_encrypted); free(account->password_encrypted); free(account->note_encrypted); free(account->attachkey); free(account->attachkey_encrypted); list_for_each_entry_safe(field, tmp, &account->field_head, list) { field_free(field); } } void app_free(struct app *app) { account_free_contents(&app->account); free(app->appname); free(app->extra); free(app->extra_encrypted); free(app->wintitle); free(app->wininfo); free(app->exeversion); free(app->warnversion); free(app->exehash); } bool account_is_group(struct account *account) { return !strcmp(account->url, "http://group"); } static bool account_is_secure_note(const struct account *account) { return !strcmp(account->url, "http://sn"); } struct app *new_app() { struct app *app = new0(struct app, 1); struct account *account = &app->account; app->appname = xstrdup(""); app->extra = xstrdup(""); app->extra_encrypted = xstrdup(""); INIT_LIST_HEAD(&account->field_head); INIT_LIST_HEAD(&account->attach_head); account->is_app = true; return app; } struct account *new_account() { struct account *account = new0(struct account, 1); INIT_LIST_HEAD(&account->field_head); INIT_LIST_HEAD(&account->attach_head); return account; } void account_free(struct account *account) { if (!account) return; if (account->is_app) { app_free(account_to_app(account)); return; } account_free_contents(account); free(account); } void blob_free(struct blob *blob) { if (!blob) return; struct account *account, *tmp; struct share *share, *tmp_share; list_for_each_entry_safe(account, tmp, &blob->account_head, list) account_free(account); list_for_each_entry_safe(share, tmp_share, &blob->share_head, list) share_free(share); free(blob); } struct blob_pos { const unsigned char *data; size_t len; }; struct chunk { char name[4 + 1]; const unsigned char *data; size_t len; }; struct item { const unsigned char *data; size_t len; }; static bool read_chunk(struct blob_pos *blob, struct chunk *chunk) { if (blob->len < 4) return false; chunk->name[0] = blob->data[0]; chunk->name[1] = blob->data[1]; chunk->name[2] = blob->data[2]; chunk->name[3] = blob->data[3]; chunk->name[4] = '\0'; blob->len -= 4; blob->data += 4; if (blob->len < sizeof(uint32_t)) return false; chunk->len = be32toh(*((uint32_t *)blob->data)); blob->len -= sizeof(uint32_t); blob->data += sizeof(uint32_t); if (chunk->len > blob->len) return false; chunk->data = blob->data; blob->data += chunk->len; blob->len -= chunk->len; return true; } static bool read_item(struct chunk *chunk, struct item *item) { if (chunk->len < sizeof(uint32_t)) return false; item->len = be32toh(*((uint32_t *)chunk->data)); chunk->len -= sizeof(uint32_t); chunk->data += sizeof(uint32_t); if (item->len > chunk->len) return false; item->data = chunk->data; chunk->data += item->len; chunk->len -= item->len; return true; } static char *read_hex_string(struct chunk *chunk) { struct item item; int result; char *str = NULL; if (!read_item(chunk, &item)) return NULL; if (item.len == 0) return xstrdup(""); result = hex_to_bytes((char *) item.data, (unsigned char **) &str); if (result) { free(str); return NULL; } return str; } static char *read_plain_string(struct chunk *chunk) { struct item item; if (!read_item(chunk, &item)) return NULL; if (item.len == 0) return xstrdup(""); return xstrndup((char *) item.data, item.len); } static char *read_crypt_string(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN], char **stored_base64) { struct item item; char *ptext; if (!read_item(chunk, &item)) return NULL; if (stored_base64) *stored_base64 = cipher_base64(item.data, item.len); if (item.len == 0) return xstrdup(""); ptext = cipher_aes_decrypt(item.data, item.len, key); if (!ptext) /* don't fail whole blob if this item cannot be decrypted */ return xstrdup(""); return ptext; } static int read_boolean(struct chunk *chunk) { struct item item; if (!read_item(chunk, &item)) return -1; if (item.len != 1) return 0; return item.data[0] == '1'; } static bool check_next_entry_encrypted(struct chunk *chunk) { return (chunk->data + sizeof(uint32_t))[0] == '!'; } #define entry_plain_at(base, var) do { \ char *__entry_val__ = read_plain_string(chunk); \ if (!__entry_val__) \ goto error; \ base->var = __entry_val__; \ } while (0) #define entry_plain(var) entry_plain_at(parsed, var) #define entry_hex_at(base, var) do { \ char *__entry_val__ = read_hex_string(chunk); \ if (!__entry_val__) \ goto error; \ base->var = __entry_val__; \ } while (0) #define entry_hex(var) entry_hex_at(parsed, var) #define entry_boolean(var) do { \ int __entry_val__ = read_boolean(chunk); \ if (__entry_val__ < 0) \ goto error; \ parsed->var = __entry_val__; \ } while (0) #define entry_crypt_at(base, var) do { \ char *__entry_val__ = read_crypt_string(chunk, key, &base->var##_encrypted); \ if (!__entry_val__) \ goto error; \ base->var = __entry_val__; \ } while (0) #define entry_crypt(var) entry_crypt_at(parsed, var) #define skip(placeholder) do { \ struct item skip_item; \ if (!read_item(chunk, &skip_item)) \ goto error; \ } while (0) static struct account *account_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct account *parsed = new_account(); entry_plain(id); entry_crypt(name); entry_crypt(group); if (check_next_entry_encrypted(chunk)) entry_crypt(url); else entry_hex(url); entry_crypt(note); entry_boolean(fav); skip(sharedfromaid); entry_crypt(username); entry_crypt(password); entry_boolean(pwprotect); skip(genpw); skip(sn); entry_plain(last_touch); skip(autologin); skip(never_autofill); skip(realm_data); skip(fiid); skip(custom_js); skip(submit_id); skip(captcha_id); skip(urid); skip(basic_auth); skip(method); skip(action); skip(groupid); skip(deleted); entry_plain(attachkey_encrypted); entry_boolean(attachpresent); skip(individualshare); skip(notetype); skip(noalert); entry_plain(last_modified_gmt); skip(hasbeenshared); skip(last_pwchange_gmt); skip(created_gmt); skip(vulnerable); if (parsed->name[0] == 16) parsed->name[0] = '\0'; if (parsed->group[0] == 16) parsed->group[0] = '\0'; if (strlen(parsed->attachkey_encrypted)) { parsed->attachkey = cipher_aes_decrypt_base64( parsed->attachkey_encrypted, key); } if (!parsed->attachkey) parsed->attachkey = xstrdup(""); /* use name as 'fullname' only if there's no assigned group */ if (strlen(parsed->group) && (strlen(parsed->name) || account_is_group(parsed))) xasprintf(&parsed->fullname, "%s/%s", parsed->group, parsed->name); else parsed->fullname = xstrdup(parsed->name); return parsed; error: account_free(parsed); return NULL; } static struct field *field_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct field *parsed = new0(struct field, 1); entry_plain(name); entry_plain(type); if (!strcmp(parsed->type, "email") || !strcmp(parsed->type, "tel") || !strcmp(parsed->type, "text") || !strcmp(parsed->type, "password") || !strcmp(parsed->type, "textarea")) entry_crypt(value); else entry_plain(value); entry_boolean(checked); return parsed; error: field_free(parsed); return NULL; } static struct field *app_field_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct field *parsed = new0(struct field, 1); entry_plain(name); entry_crypt(value); entry_plain(type); return parsed; error: field_free(parsed); return NULL; } static struct share *share_parse(struct chunk *chunk, const struct private_key *private_key) { struct share *parsed = new0(struct share, 1); struct item item; _cleanup_free_ unsigned char *ciphertext = NULL; _cleanup_free_ char *hex_key = NULL; _cleanup_free_ unsigned char *key = NULL; _cleanup_free_ char *base64_name = NULL; size_t len; if (!private_key) goto error; if (chunk->len) { parsed->chunk_len = chunk->len; parsed->chunk = xmalloc(chunk->len); memcpy(parsed->chunk, chunk->data, chunk->len); } entry_plain(id); if (!read_item(chunk, &item) || item.len == 0 || item.len % 2 != 0) goto error; hex_to_bytes((char *) item.data, &ciphertext); hex_key = cipher_rsa_decrypt(ciphertext, item.len / 2, private_key); if (!hex_key) goto error; len = strlen(hex_key); if (len % 2 != 0) goto error; len /= 2; if (len != KDF_HASH_LEN) goto error; hex_to_bytes(hex_key, &key); mlock(parsed->key, KDF_HASH_LEN); memcpy(parsed->key, key, KDF_HASH_LEN); base64_name = read_plain_string(chunk); parsed->name = cipher_aes_decrypt_base64(base64_name, parsed->key); if (!parsed->name) goto error; entry_boolean(readonly); return parsed; error: share_free(parsed); return NULL; } static struct app *app_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct app *app = new_app(); struct account *parsed = &app->account; entry_plain(id); entry_hex_at(app, appname); entry_crypt_at(app, extra); entry_crypt(name); entry_crypt(group); entry_plain(last_touch); skip(fiid); entry_boolean(pwprotect); entry_boolean(fav); entry_plain_at(app, wintitle); entry_plain_at(app, wininfo); entry_plain_at(app, exeversion); skip(autologin); entry_plain_at(app, warnversion); entry_plain_at(app, exehash); parsed->username = xstrdup(""); parsed->password = xstrdup(""); parsed->note = xstrdup(""); parsed->url = xstrdup(""); if (strlen(parsed->group) && (strlen(parsed->name) || account_is_group(parsed))) xasprintf(&parsed->fullname, "%s/%s", parsed->group, parsed->name); else parsed->fullname = xstrdup(parsed->name); return app; error: app_free(app); return NULL; } static void attach_free(struct attach *attach) { if (!attach) return; free(attach->id); free(attach->parent); free(attach->mimetype); free(attach->storagekey); free(attach->size); free(attach->filename); free(attach); } static struct attach *attach_parse(struct chunk *chunk) { struct attach *parsed = new0(struct attach, 1); entry_plain(id); entry_plain(parent); entry_plain(mimetype); entry_plain(storagekey); entry_plain(size); entry_plain(filename); return parsed; error: attach_free(parsed); return NULL; } #undef entry_plain #undef entry_plain_at #undef entry_hex #undef entry_boolean #undef entry_crypt #undef entry_crypt_at #undef skip struct blob *blob_parse(const unsigned char *blob, size_t len, const unsigned char key[KDF_HASH_LEN], const struct private_key *private_key) { struct blob_pos blob_pos = { .data = blob, .len = len }; struct chunk chunk; struct account *account = NULL; struct field *field; struct share *share, *last_share = NULL; struct app *app = NULL; struct attach *attach; struct blob *parsed; _cleanup_free_ char *versionstr = NULL; parsed = new0(struct blob, 1); parsed->local_version = false; INIT_LIST_HEAD(&parsed->account_head); INIT_LIST_HEAD(&parsed->share_head); while (read_chunk(&blob_pos, &chunk)) { if (!strcmp(chunk.name, "LPAV")) { versionstr = xstrndup((char *) chunk.data, chunk.len); parsed->version = strtoull(versionstr, NULL, 10); } else if (!strcmp(chunk.name, "ACCT")) { account = account_parse(&chunk, last_share ? last_share->key : key); if (!account) goto error; if (last_share) { account->share = last_share; char *tmp = account->fullname; xasprintf(&account->fullname, "%s/%s", last_share->name, tmp); free(tmp); } list_add(&account->list, &parsed->account_head); } else if (!strcmp(chunk.name, "ACFL") || !strcmp(chunk.name, "ACOF")) { if (!account) goto error; field = field_parse(&chunk, last_share ? last_share->key : key); if (!field) goto error; list_add_tail(&field->list, &account->field_head); } else if (!strcmp(chunk.name, "LOCL")) { parsed->local_version = true; } else if (!strcmp(chunk.name, "SHAR")) { share = share_parse(&chunk, private_key); last_share = share; if (share) list_add_tail(&share->list, &parsed->share_head); } else if (!strcmp(chunk.name, "AACT")) { app = app_parse(&chunk, last_share ? last_share->key : key); if (app) list_add_tail(&app->account.list, &parsed->account_head); } else if (!strcmp(chunk.name, "AACF")) { if (!app) goto error; field = app_field_parse(&chunk, last_share ? last_share->key : key); if (!field) goto error; list_add_tail(&field->list, &app->account.field_head); } else if (!strcmp(chunk.name, "ATTA")) { struct account *tmp; bool found = false; attach = attach_parse(&chunk); if (!attach) goto error; /* add attachment to the proper account's list */ list_for_each_entry(tmp, &parsed->account_head, list) { if (!strcmp(tmp->id, attach->parent)) { found = true; list_add_tail(&attach->list, &tmp->attach_head); break; } } if (!found) attach_free(attach); } } if (!versionstr) goto error; return parsed; error: blob_free(parsed); 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) { buffer->max = buffer->len + len + 512; buffer->bytes = xrealloc(buffer->bytes, buffer->max); } memcpy(buffer->bytes + buffer->len, bytes, len); buffer->len += len; } void buffer_append_char(struct buffer *buf, char c) { if (buf->len + 1 >= buf->max) { buf->max += 80; buf->bytes = xrealloc(buf->bytes, buf->max); } buf->bytes[buf->len++] = c; buf->bytes[buf->len] = '\0'; } void buffer_append_str(struct buffer *buf, char *str) { /* * copy null terminator, but don't count in used len * so that append of multiple strings will work */ buffer_append(buf, str, strlen(str) + 1); buf->len--; } static void write_item(struct buffer *buffer, char *bytes, size_t len) { uint32_t be32len = htobe32(len); buffer_append(buffer, &be32len, sizeof(be32len)); buffer_append(buffer, bytes, len); } static void write_plain_string(struct buffer *buffer, char *bytes) { write_item(buffer, bytes, strlen(bytes)); } static void write_hex_string(struct buffer *buffer, char *bytes) { _cleanup_free_ char *hex = NULL; bytes_to_hex((unsigned char *) bytes, &hex, strlen(bytes)); write_plain_string(buffer, hex); } static void write_crypt_string(struct buffer *buffer, char *enc_str) { _cleanup_free_ unsigned char *encrypted = NULL; size_t len; /* * enc_str is base64-encoded, but we write out raw bytes in * the saved blob, so un-base64. */ len = cipher_unbase64(enc_str, &encrypted); write_item(buffer, (char *) encrypted, len); } static void write_boolean(struct buffer *buffer, bool yes) { write_plain_string(buffer, yes ? "1" : "0"); } static void write_chunk(struct buffer *dstbuffer, struct buffer *srcbuffer, char *tag) { if (strlen(tag) != 4) return; buffer_append(dstbuffer, tag, 4); write_item(dstbuffer, srcbuffer->bytes, srcbuffer->len); } static void write_app_chunk(struct buffer *buffer, struct account *account) { struct buffer accbuf, fieldbuf; struct field *field; struct app *app = account_to_app(account); memset(&accbuf, 0, sizeof(accbuf)); write_plain_string(&accbuf, account->id); write_hex_string(&accbuf, app->appname); write_crypt_string(&accbuf, app->extra_encrypted); write_crypt_string(&accbuf, account->name_encrypted); write_crypt_string(&accbuf, account->group_encrypted); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_boolean(&accbuf, account->pwprotect); write_boolean(&accbuf, account->fav); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_chunk(buffer, &accbuf, "AACT"); free(accbuf.bytes); list_for_each_entry(field, &account->field_head, list) { memset(&fieldbuf, 0, sizeof(fieldbuf)); write_plain_string(&fieldbuf, field->name); if (!strcmp(field->type, "email") || !strcmp(field->type, "tel") || !strcmp(field->type, "text") || !strcmp(field->type, "password") || !strcmp(field->type, "textarea")) write_crypt_string(&fieldbuf, field->value_encrypted); else write_plain_string(&fieldbuf, field->value); write_plain_string(&fieldbuf, field->type); write_chunk(buffer, &fieldbuf, "AACF"); free(fieldbuf.bytes); } } static void write_account_chunk(struct buffer *buffer, struct account *account, const struct feature_flag *feature_flag) { struct buffer accbuf, fieldbuf; struct field *field; if (account->is_app) { write_app_chunk(buffer, account); return; } memset(&accbuf, 0, sizeof(accbuf)); write_plain_string(&accbuf, account->id); write_crypt_string(&accbuf, account->name_encrypted); write_crypt_string(&accbuf, account->group_encrypted); if (feature_flag && feature_flag->url_encryption_enabled) { if (account->url_encrypted != NULL) write_crypt_string(&accbuf, account->url_encrypted); else write_hex_string(&accbuf, account->url); } else { write_hex_string(&accbuf, account->url); } write_crypt_string(&accbuf, account->note_encrypted); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_crypt_string(&accbuf, account->username_encrypted); write_crypt_string(&accbuf, account->password_encrypted); write_boolean(&accbuf, account->pwprotect); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_chunk(buffer, &accbuf, "ACCT"); free(accbuf.bytes); list_for_each_entry(field, &account->field_head, list) { memset(&fieldbuf, 0, sizeof(fieldbuf)); write_plain_string(&fieldbuf, field->name); write_plain_string(&fieldbuf, field->type); if (!strcmp(field->type, "email") || !strcmp(field->type, "tel") || !strcmp(field->type, "text") || !strcmp(field->type, "password") || !strcmp(field->type, "textarea")) write_crypt_string(&fieldbuf, field->value_encrypted); else write_plain_string(&fieldbuf, field->value); write_boolean(&fieldbuf, field->checked); write_chunk(buffer, &fieldbuf, "ACFL"); free(fieldbuf.bytes); } } static void write_share_chunk(struct buffer *buffer, struct share *share) { struct buffer sharebuf = { .bytes = share->chunk, .len = share->chunk_len, .max = share->chunk_len }; write_chunk(buffer, &sharebuf, "SHAR"); } size_t blob_write(const struct blob *blob, const unsigned char key[KDF_HASH_LEN], char **out, const struct feature_flag *feature_flag) { struct buffer buffer; struct share *last_share = NULL; struct account *account; UNUSED(key); memset(&buffer, 0, sizeof(buffer)); _cleanup_free_ char *version = xultostr(blob->version); buffer_append(&buffer, "LPAV", 4); write_plain_string(&buffer, version); buffer_append(&buffer, "LOCL", 4); write_plain_string(&buffer, LASTPASS_CLI_VERSION); list_for_each_entry(account, &blob->account_head, list) { if (!account->share) write_account_chunk(&buffer, account, feature_flag); } list_for_each_entry(account, &blob->account_head, list) { if (!account->share) continue; if (last_share != account->share) { write_share_chunk(&buffer, account->share); last_share = account->share; } write_account_chunk(&buffer, account, feature_flag); } *out = buffer.bytes; return buffer.len; } static struct blob *local_blob(const unsigned char key[KDF_HASH_LEN], const struct private_key *private_key) { _cleanup_free_ unsigned char *blob = NULL; size_t len = config_read_encrypted_buffer("blob", &blob, key); if (!blob) return NULL; return blob_parse(blob, len, key, private_key); } static struct blob *blob_get_latest(struct session *session, const unsigned char key[KDF_HASH_LEN]) { struct blob *local; unsigned long long remote_version; local = local_blob(key, &session->private_key); if (!local) return lastpass_get_blob(session, key); remote_version = lastpass_get_blob_version(session, key); if (remote_version == 0) { blob_free(local); return NULL; } if (remote_version > local->version) { blob_free(local); return lastpass_get_blob(session, key); } config_touch("blob"); return local; } static time_t auto_sync_time(void) { time_t time; char *env = getenv("LPASS_AUTO_SYNC_TIME"); if (!env) return 5; time = strtoul(env, NULL, 10); if (!time) return 5; return time; } struct blob *blob_load(enum blobsync sync, struct session *session, const unsigned char key[KDF_HASH_LEN]) { if (sync == BLOB_SYNC_YES) return blob_get_latest(session, key); if (sync == BLOB_SYNC_NO) return local_blob(key, &session->private_key); if (config_exists("blob") && time(NULL) - config_mtime("blob") < auto_sync_time()) { return local_blob(key, &session->private_key); } return blob_get_latest(session, key); } void blob_save(const struct blob *blob, const unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { _cleanup_free_ char *bluffer = NULL; size_t len; len = blob_write(blob, key, &bluffer, feature_flag); if (!len) die("Could not write blob."); config_write_encrypted_buffer("blob", bluffer, len, key); } #define set_field(obj, field) do { \ free(obj->field); \ obj->field = field; \ } while (0) #define set_encrypted_field(obj, field) do { \ if (!obj->field || !field || strcmp(obj->field, field)) { \ set_field(obj, field); \ free(obj->field##_encrypted); \ obj->field##_encrypted = encrypt_and_base64(field, account->share ? account->share->key : key); \ } \ } while (0) #define reencrypt_field(obj, field) do { \ free(obj->field##_encrypted); \ obj->field##_encrypted = encrypt_and_base64(obj->field, account->share ? account->share->key : key); \ } while (0) void account_set_username(struct account *account, char *username, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, username); } void account_set_password(struct account *account, char *password, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, password); } void account_set_group(struct account *account, char *group, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, group); } void account_set_name(struct account *account, char *name, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, name); } void account_set_note(struct account *account, char *note, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, note); } void account_set_url(struct account *account, char *url, unsigned const char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { if (url != NULL && strstr(url, "://") == NULL) { char *url_with_prefix; if ((url_with_prefix = malloc(strlen(url) + strlen("http://") + 1)) != NULL) { strcpy(url_with_prefix, "http://"); strcat(url_with_prefix, url); free(url); url = url_with_prefix; } } if (feature_flag && feature_flag->url_encryption_enabled) { set_encrypted_field(account, url); } else { UNUSED(key); set_field(account, url); } } void account_set_appname(struct account *account, char *appname, unsigned const char key[KDF_HASH_LEN]) { UNUSED(key); struct app *app; if (!account->is_app) return; app = account_to_app(account); set_field(app, appname); } void field_set_value(struct account *account, struct field *field, char *value, unsigned const char key[KDF_HASH_LEN]) { if (!strcmp(field->type, "email") || !strcmp(field->type, "tel") || !strcmp(field->type, "text") || !strcmp(field->type, "password") || !strcmp(field->type, "textarea")) set_encrypted_field(field, value); else set_field(field, value); } static bool is_shared_folder_name(const char *fullname) { return !strncmp(fullname, "Shared-", 7) && strchr(fullname, '/'); } void account_reencrypt(struct account *account, const unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { struct field *field; reencrypt_field(account, name); reencrypt_field(account, group); if (feature_flag && feature_flag->url_encryption_enabled) { reencrypt_field(account, url); } reencrypt_field(account, username); reencrypt_field(account, password); reencrypt_field(account, note); list_for_each_entry(field, &account->field_head, list) { reencrypt_field(field, value); } } /* * Set just group and name, assuming we've stripped off any leading * shared folder from fullname. */ static void account_set_group_name(struct account *account, const char *groupname, unsigned const char key[KDF_HASH_LEN]) { char *slash = strrchr(groupname, '/'); if (!slash) { account_set_name(account, xstrdup(groupname), key); account_set_group(account, xstrdup(""), key); } else { account_set_name(account, xstrdup(slash + 1), key); account_set_group(account, xstrndup(groupname, slash - groupname), key); } } void account_set_fullname(struct account *account, char *fullname, unsigned const char key[KDF_HASH_LEN]) { char *groupname = fullname; /* skip Shared-XXX/ for shared folders */ if (is_shared_folder_name(fullname)) { char *tmp = strchr(fullname, '/'); if (tmp) groupname = tmp + 1; } account_set_group_name(account, groupname, key); free(account->fullname); account->fullname = fullname; } struct share *find_unique_share(struct blob *blob, const char *name) { struct share *share; list_for_each_entry(share, &blob->share_head, list) { if (!strcasecmp(share->name, name)) { return share; } } return NULL; } /* * Assign an account to the proper shared folder, if any. * * If the share changed from whatever it was previously, the account * fields are reencrypted with either the share key or the blob key. * * This function may exit if the name represents a shared folder but * same folder is not available. */ void account_assign_share(struct blob *blob, struct account *account, unsigned const char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { struct share *share, *old_share; _cleanup_free_ char *shared_name = NULL; char *name = account->fullname; old_share = account->share; /* strip off shared groupname */ char *slash = strchr(name, '/'); if (!slash) { account->share = NULL; goto reencrypt; } shared_name = xstrndup(name, slash - name); /* find a share matching group name */ share = find_unique_share(blob, shared_name); if (!share && is_shared_folder_name(name)) { /* don't allow normal folders named like SFs */ die("Unable to find shared folder for %s in blob\n", name); } account->share = share; /* update group name to not include new share, if needed */ if (share) account_set_group_name(account, slash + 1, key); reencrypt: if (old_share != account->share) account_reencrypt(account, key, feature_flag); } struct account *notes_expand(struct account *acc) { struct account *expand; struct field *field = NULL; char *start, *lf, *colon, *name, *value; struct attach *attach, *tmp; char *line = NULL; size_t len; if (!account_is_secure_note(acc)) return NULL; expand = new_account(); expand->id = xstrdup(acc->id); expand->pwprotect = acc->pwprotect; expand->name = xstrdup(acc->name); expand->group = xstrdup(acc->group); expand->fullname = xstrdup(acc->fullname); expand->share = acc->share; 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; ; ) { name = value = NULL; lf = strchrnul(start, '\n'); if (lf == start && !field) goto skip; line = xstrndup(start, lf - start); colon = strchr(line, ':'); 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; } /* * 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 && note_field_is_multiline(note_type, field->name)) { xstrappendf(&field->value, "\n%s", line); goto skip; } if (!strcmp(name, "Username")) expand->username = xstrdup(value); else if (!strcmp(name, "Password")) expand->password = xstrdup(value); else if (!strcmp(name, "URL")) expand->url = xstrdup(value); else if (!strcmp(name, "Notes")) { expand->note = xstrdup(strchr(start, ':') + 1); len = strlen(expand->note); if (len && expand->note[len - 1] == '\n') expand->note[len - 1] = '\0'; lf = NULL; } else { field = new0(struct field, 1); field->type = xstrdup("text"); field->name = xstrdup(name); field->value = xstrdup(value); list_add(&field->list, &expand->field_head); } skip: free(value); free(name); free(line); line = NULL; if (!lf || !*lf) break; start = lf + 1; if (!*start) break; } if (!expand->note && !expand->username && !expand->url && !expand->password && list_empty(&expand->field_head)) expand->note = xstrdup(acc->note); else if (!expand->note) expand->note = xstrdup(""); if (!expand->url) expand->url = xstrdup(""); if (!expand->username) expand->username = xstrdup(""); if (!expand->password) expand->password = xstrdup(""); /* move attachments to expanded account */ expand->attachkey = xstrdup(acc->attachkey); expand->attachkey_encrypted = xstrdup(acc->attachkey_encrypted); expand->attachpresent = acc->attachpresent; list_for_each_entry_safe(attach, tmp, &acc->attach_head, list) { list_del(&attach->list); list_add_tail(&attach->list, &expand->attach_head); } return expand; } struct account *notes_collapse(struct account *acc) { struct account *collapse; struct field *field; struct attach *attach, *tmp; collapse = new_account(); collapse->id = xstrdup(acc->id); collapse->pwprotect = acc->pwprotect; collapse->name = xstrdup(acc->name); collapse->group = xstrdup(acc->group); collapse->fullname = xstrdup(acc->fullname); collapse->url = xstrdup("http://sn"); collapse->username = xstrdup(""); collapse->password = xstrdup(""); collapse->note = xstrdup(""); collapse->share = acc->share; /* move attachments back from expanded account */ collapse->attachkey = xstrdup(acc->attachkey); collapse->attachkey_encrypted = xstrdup(acc->attachkey_encrypted); collapse->attachpresent = acc->attachpresent; list_for_each_entry_safe(attach, tmp, &acc->attach_head, list) { list_del(&attach->list); list_add_tail(&attach->list, &collapse->attach_head); } list_for_each_entry(field, &acc->field_head, list) { trim(field->value); trim(field->name); if (!strcmp(field->name, "NoteType")) xstrprependf(&collapse->note, "%s:%s\n", field->name, field->value); else xstrappendf(&collapse->note, "%s:%s\n", field->name, field->value); } if (strlen(acc->username)) xstrappendf(&collapse->note, "%s:%s\n", "Username", trim(acc->username)); if (strlen(acc->password)) xstrappendf(&collapse->note, "%s:%s\n", "Password", trim(acc->password)); if (strlen(acc->url)) xstrappendf(&collapse->note, "%s:%s\n", "URL", trim(acc->url)); if (strlen(acc->note)) xstrappendf(&collapse->note, "%s:%s\n", "Notes", trim(acc->note)); return collapse; } lastpass-cli-1.5.0/blob.h000066400000000000000000000122271462143212600152100ustar00rootroot00000000000000#ifndef BLOB_H #define BLOB_H #include "kdf.h" #include "session.h" #include "list.h" #include "notes.h" #include "feature-flag.h" #include #include struct share_user { char *uid; char *username; char *realname; char *cgid; bool read_only; bool is_group; /* if set uid, username store gid, groupname */ bool hide_passwords; bool admin; bool outside_enterprise; bool accepted; struct public_key sharing_key; struct list_head list; }; struct share_limit_aid { char *aid; struct list_head list; }; struct share_limit { bool whitelist; struct list_head aid_list; }; struct share { char *id; char *name; unsigned char key[KDF_HASH_LEN]; bool readonly; char *chunk; size_t chunk_len; struct list_head list; }; struct field { char *type; char *name; char *value, *value_encrypted; bool checked; struct list_head list; }; struct account { char *id; char *name, *name_encrypted; char *group, *group_encrypted; char *fullname; char *url, *url_encrypted; char *username, *username_encrypted; char *password, *password_encrypted; char *note, *note_encrypted; char *last_touch, *last_modified_gmt; bool pwprotect; bool fav; bool is_app; char *attachkey, *attachkey_encrypted; bool attachpresent; size_t attach_len; char *attach_bytes; struct list_head field_head; struct share *share; struct list_head attach_head; struct list_head list; struct list_head match_list; }; struct app { struct account account; char *appname; char *extra, *extra_encrypted; char *wintitle; char *wininfo; char *exeversion; char *warnversion; char *exehash; }; struct attach { char *id; char *parent; char *mimetype; char *storagekey; char *size; char *filename; struct list_head list; }; /* resizable string buffer */ struct buffer { size_t len; size_t max; char *bytes; }; struct blob { unsigned long long version; bool local_version; /* TODO: extract other data eventually... */ struct list_head account_head; struct list_head share_head; }; /* state used during master password change */ struct pwchange_info { char *reencrypt_id; char *token; char *privkey_encrypted; char *new_privkey_encrypted; char *new_privkey_hash; char *new_key_hash; struct list_head fields; struct list_head su_keys; }; /* replacement items for password change blob updates */ struct pwchange_field { char *old_ctext; char *new_ctext; bool optional; struct list_head list; }; /* Super-user keys used for enterprise password recovery. */ struct pwchange_su_key { char *uid; /* uid for super user */ struct public_key sharing_key; /* pubkey for this user */ char *new_enc_key; /* user AES key, enc w/ SU's RSA key */ struct list_head list; }; enum blobsync { BLOB_SYNC_AUTO, BLOB_SYNC_YES, BLOB_SYNC_NO }; struct blob *blob_parse(const unsigned char *blob, size_t len, const unsigned char key[KDF_HASH_LEN], const struct private_key *private_key); void blob_free(struct blob *blob); size_t blob_write(const struct blob *blob, const unsigned char key[KDF_HASH_LEN], char **out, const struct feature_flag *feature_flag); struct blob *blob_load(enum blobsync sync, struct session *session, const unsigned char key[KDF_HASH_LEN]); void blob_save(const struct blob *blob, const unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag); void field_free(struct field *field); struct app *account_to_app(const struct account *account); struct app *new_app(); struct account *new_account(); void account_free(struct account *account); void account_set_username(struct account *account, char *username, unsigned const char key[KDF_HASH_LEN]); void account_set_password(struct account *account, char *password, unsigned const char key[KDF_HASH_LEN]); void account_set_group(struct account *account, char *group, unsigned const char key[KDF_HASH_LEN]); void account_set_name(struct account *account, char *name, unsigned const char key[KDF_HASH_LEN]); void account_set_fullname(struct account *account, char *fullname, unsigned const char key[KDF_HASH_LEN]); void account_set_url(struct account *account, char *url, unsigned const char key[KDF_HASH_LEN], const struct feature_flag *feature_flag); void account_set_note(struct account *account, char *note, unsigned const char key[KDF_HASH_LEN]); void account_set_appname(struct account *account, char *appname, unsigned const char key[KDF_HASH_LEN]); void account_assign_share(struct blob *blob, struct account *account, unsigned const char key[KDF_HASH_LEN], const struct feature_flag *feature_flag); void account_reencrypt(struct account *account, const unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag); bool account_is_group(struct account *account); void field_set_value(struct account *account, struct field *field, char *value, unsigned const char key[KDF_HASH_LEN]); struct account *notes_expand(struct account *acc); 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); #endif lastpass-cli-1.5.0/cipher.c000066400000000000000000000316051462143212600155400ustar00rootroot00000000000000/* * encryption and decryption routines * * Copyright (C) 2014-2018 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 "cipher.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #define LP_PKEY_PREFIX "LastPassPrivateKey<" #define LP_PKEY_SUFFIX ">LastPassPrivateKey" char *cipher_rsa_decrypt(const unsigned char *ciphertext, size_t len, const struct private_key *private_key) { PKCS8_PRIV_KEY_INFO *p8inf = NULL; EVP_PKEY *pkey = NULL; RSA *rsa = NULL; BIO *memory = NULL; char *ret = NULL; if (!len) return NULL; memory = BIO_new(BIO_s_mem()); if (BIO_write(memory, private_key->key, private_key->len) < 0) goto out; p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(memory, NULL); if (!p8inf) goto out; pkey = EVP_PKCS82PKEY(p8inf); if (!pkey) goto out; rsa = EVP_PKEY_get1_RSA(pkey); if (!rsa) goto out; ret = xcalloc(len + 1, 1); if (RSA_private_decrypt(len, (unsigned char *)ciphertext, (unsigned char *)ret, rsa, RSA_PKCS1_OAEP_PADDING) < 0) { free(ret); ret = NULL; goto out; } out: PKCS8_PRIV_KEY_INFO_free(p8inf); EVP_PKEY_free(pkey); RSA_free(rsa); BIO_free_all(memory); return ret; } int cipher_rsa_encrypt_bytes(const unsigned char *plaintext, size_t in_len, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len) { EVP_PKEY *pubkey = NULL; RSA *rsa = NULL; BIO *memory = NULL; int ret; if (*out_len < public_key->len) { ret = -EINVAL; goto out; } memory = BIO_new(BIO_s_mem()); ret = BIO_write(memory, public_key->key, public_key->len); if (ret < 0) goto out; ret = -EIO; pubkey = d2i_PUBKEY_bio(memory, NULL); if (!pubkey) goto out; rsa = EVP_PKEY_get1_RSA(pubkey); if (!rsa) goto out; ret = RSA_public_encrypt(in_len, plaintext, out_crypttext, rsa, RSA_PKCS1_OAEP_PADDING); if (ret < 0) goto out; *out_len = ret; ret = 0; out: EVP_PKEY_free(pubkey); RSA_free(rsa); BIO_free_all(memory); return ret; } int cipher_rsa_encrypt(const char *plaintext, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len) { return cipher_rsa_encrypt_bytes((unsigned char *) plaintext, strlen(plaintext), public_key, out_crypttext, out_len); } char *cipher_aes_decrypt(const unsigned char *ciphertext, size_t len, const unsigned char key[KDF_HASH_LEN]) { EVP_CIPHER_CTX *ctx; char *plaintext; int out_len; if (!len) return NULL; ctx = EVP_CIPHER_CTX_new(); if (!ctx) return NULL; plaintext = xcalloc(len + AES_BLOCK_SIZE + 1, 1); if (len >= 33 && len % 16 == 1 && ciphertext[0] == '!') { if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, (unsigned char *)(ciphertext + 1))) goto error; ciphertext += 17; len -= 17; } else { if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_ecb(), NULL, key, NULL)) goto error; } if (!EVP_DecryptUpdate(ctx, (unsigned char *)plaintext, &out_len, (unsigned char *)ciphertext, len)) goto error; len = out_len; if (!EVP_DecryptFinal_ex(ctx, (unsigned char *)(plaintext + out_len), &out_len)) goto error; len += out_len; plaintext[len] = '\0'; EVP_CIPHER_CTX_free(ctx); return plaintext; error: EVP_CIPHER_CTX_free(ctx); secure_clear(plaintext, len + AES_BLOCK_SIZE + 1); free(plaintext); return NULL; } static size_t cipher_aes_encrypt_bytes(const unsigned char *bytes, size_t len, const unsigned char key[KDF_HASH_LEN], const unsigned char *iv, unsigned char **out) { EVP_CIPHER_CTX *ctx; int out_len; size_t ret_len = 0; unsigned char *ctext; ctext = *out; if (!ctext) ctext = xcalloc(len + AES_BLOCK_SIZE * 2 + 1, 1); ctx = EVP_CIPHER_CTX_new(); if (!ctx) goto error; if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) goto error; if (!EVP_EncryptUpdate(ctx, ctext, &out_len, bytes, len)) goto error; ret_len += out_len; if (!EVP_EncryptFinal_ex(ctx, ctext + ret_len, &out_len)) goto error; ret_len += out_len; EVP_CIPHER_CTX_free(ctx); *out = ctext; return ret_len; error: EVP_CIPHER_CTX_free(ctx); if (!*out) free(ctext); die("Failed to encrypt data."); } size_t cipher_aes_encrypt(const char *plaintext, const unsigned char key[KDF_HASH_LEN], unsigned char **out) { unsigned char *ciphertext; unsigned char *tmp; unsigned char iv[AES_BLOCK_SIZE]; int in_len; size_t len; if (!RAND_bytes(iv, AES_BLOCK_SIZE)) die("Could not generate random bytes for CBC IV."); in_len = strlen(plaintext); ciphertext = xcalloc(in_len + AES_BLOCK_SIZE * 2 + 1, 1); ciphertext[0] = '!'; len = 1; memcpy(ciphertext + len, iv, AES_BLOCK_SIZE); len += AES_BLOCK_SIZE; tmp = ciphertext + len; len += cipher_aes_encrypt_bytes((unsigned char *)plaintext, in_len, key, iv, &tmp); *out = ciphertext; return len; } static char *base64(const unsigned char *bytes, size_t len) { BIO *memory, *b64; BUF_MEM *buffer; char *output; b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); memory = BIO_new(BIO_s_mem()); if (!b64 || !memory) goto error; b64 = BIO_push(b64, memory); if (!b64) goto error; if (BIO_write(b64, bytes, len) < 0 || BIO_flush(b64) < 0) goto error; BIO_get_mem_ptr(b64, &buffer); output = xmalloc(buffer->length + 1); memcpy(output, buffer->data, buffer->length); output[buffer->length] = '\0'; BIO_free_all(b64); return output; error: die("Could not base64 the given bytes."); } char *cipher_base64(const unsigned char *bytes, size_t len) { _cleanup_free_ char *iv = NULL; _cleanup_free_ char *data = NULL; char *output; if (len >= 33 && bytes[0] == '!' && len % 16 == 1) { iv = base64(bytes + 1, 16); data = base64(bytes + 17, len - 17); xasprintf(&output, "!%s|%s", iv, data); return output; } return base64(bytes, len); } size_t unbase64(const char *bytes, unsigned char **unbase64) { size_t len; BIO *memory, *b64; unsigned char *buffer; len = strlen(bytes); if (!len) goto error; b64 = BIO_new(BIO_f_base64()); memory = BIO_new_mem_buf((char *)bytes, len); if (!b64 || !memory) goto error; b64 = BIO_push(b64, memory); if (!b64) goto error; BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); buffer = xcalloc(len + 1, 1); len = BIO_read(b64, buffer, len); if ((int)len <= 0) goto error; buffer[len] = '\0'; BIO_free_all(b64); *unbase64 = buffer; return len; error: die("Could not unbase64 the given bytes."); } size_t cipher_unbase64(const char *ciphertext, unsigned char **b64data) { _cleanup_free_ char *copy = NULL; _cleanup_free_ unsigned char *iv = NULL; _cleanup_free_ unsigned char *data = NULL; unsigned char *unbase64_ciphertext = NULL; char *pipe; size_t iv_len, data_len, len; if (!strlen(ciphertext)) return 0; if (ciphertext[0] != '!') return unbase64(ciphertext, b64data); copy = xstrdup(&ciphertext[1]); pipe = strchr(copy, '|'); if (!pipe) return 0; *pipe = '\0'; iv_len = unbase64(copy, &iv); data_len = unbase64(pipe + 1, &data); len = iv_len + data_len + 1 /* '!' */; unbase64_ciphertext = xcalloc(len, 1); unbase64_ciphertext[0] = '!'; memcpy(&unbase64_ciphertext[1], iv, iv_len); memcpy(&unbase64_ciphertext[1 + iv_len], data, data_len); *b64data = unbase64_ciphertext; return len; } char *cipher_aes_decrypt_base64(const char *ciphertext, const unsigned char key[KDF_HASH_LEN]) { _cleanup_free_ unsigned char *unbase64_ciphertext = NULL; size_t len; len = cipher_unbase64(ciphertext, &unbase64_ciphertext); if (!len) return NULL; return cipher_aes_decrypt(unbase64_ciphertext, len, key); } char *encrypt_and_base64(const char *str, unsigned const char key[KDF_HASH_LEN]) { unsigned char *intermediate = NULL; char *base64 = NULL; size_t len; base64 = xstrdup(str); if (!*base64) return base64; len = cipher_aes_encrypt(base64, key, &intermediate); free(base64); base64 = cipher_base64(intermediate, len); free(intermediate); return base64; } /* * Decrypt the LastPass sharing RSA private key. The key has start_str * and end_str prepended / appended before encryption, and the result * is encrypted with the AES key. * * On success, the resulting key is stored in out_key and mlock()ed. * If there is a non-fatal error (or no key), the resulting structure * will have len = 0. */ void cipher_decrypt_private_key(const char *key_hex, unsigned const char key[KDF_HASH_LEN], struct private_key *out_key) { size_t len; _cleanup_free_ unsigned char *encrypted_key = NULL; _cleanup_free_ char *decrypted_key = NULL; unsigned char *encrypted_key_start; char *start, *end; unsigned char *dec_key = NULL; int ret; #define start_str LP_PKEY_PREFIX #define end_str LP_PKEY_SUFFIX memset(out_key, 0, sizeof(*out_key)); len = strlen(key_hex); if (!len) return; if (key_hex[0] == '!') { /* v2 format */ decrypted_key = cipher_aes_decrypt_base64( key_hex, key); } else { if (len % 2 != 0) die("Key hex in wrong format."); len /= 2; /* v1 format */ len += 16 /* IV */ + 1 /* bang symbol */; encrypted_key = xcalloc(len + 1, 1); encrypted_key[0] = '!'; memcpy(&encrypted_key[1], key, 16); encrypted_key_start = &encrypted_key[17]; hex_to_bytes(key_hex, &encrypted_key_start); decrypted_key = cipher_aes_decrypt(encrypted_key, len, key); } if (!decrypted_key) { warn("Could not decrypt private key."); return; } start = strstr(decrypted_key, start_str); end = strstr(decrypted_key, end_str); if (!start || !end || end <= start) { warn("Could not decode decrypted private key."); return; } start += strlen(start_str); *end = '\0'; ret = hex_to_bytes(start, &dec_key); if (ret) die("Invalid private key after decryption and decoding."); out_key->key = dec_key; out_key->len = strlen(start) / 2; mlock(out_key->key, out_key->len); #undef start_str #undef end_str } /* * Encrypt RSA sharing key. Encrypted key is returned as a hex-encoded string. */ char *cipher_encrypt_private_key(struct private_key *private_key, unsigned const char key[KDF_HASH_LEN]) { unsigned char *key_ptext; unsigned char *ctext = NULL; char *key_hex_dst; char *ctext_hex = NULL; size_t len, ctext_len, hex_len; if (!private_key->len) return xstrdup(""); hex_len = private_key->len * 2; len = strlen(LP_PKEY_PREFIX) + hex_len + strlen(LP_PKEY_SUFFIX); key_ptext = xcalloc(len + 1, 1); memcpy(key_ptext, LP_PKEY_PREFIX, strlen(LP_PKEY_PREFIX)); key_hex_dst = (char *) key_ptext + strlen(LP_PKEY_PREFIX); bytes_to_hex(private_key->key, &key_hex_dst, private_key->len); memcpy(key_ptext + strlen(LP_PKEY_PREFIX) + hex_len, LP_PKEY_SUFFIX, strlen(LP_PKEY_SUFFIX)); ctext_len = cipher_aes_encrypt_bytes(key_ptext, len, key, key, &ctext); bytes_to_hex(ctext, &ctext_hex, ctext_len); free(ctext); return ctext_hex; } /* * Get hex-encoded sha256() of a buffer. */ char *cipher_sha256_hex(unsigned char *bytes, size_t len) { char *tmp = NULL; SHA256_CTX sha256; unsigned char hash[SHA256_DIGEST_LENGTH]; if (!SHA256_Init(&sha256)) goto die; if (!SHA256_Update(&sha256, bytes, len)) goto die; if (!SHA256_Final(hash, &sha256)) goto die; bytes_to_hex(hash, &tmp, sizeof(hash)); return tmp; die: die("SHA-256 hash failed"); } char *cipher_sha256_b64(unsigned char *bytes, size_t len) { _cleanup_free_ unsigned char *hash_raw = NULL; _cleanup_free_ char *hash_hex = NULL; hash_hex = cipher_sha256_hex(bytes, len); hex_to_bytes(hash_hex, &hash_raw); return base64(hash_raw, strlen(hash_hex) / 2); } lastpass-cli-1.5.0/cipher.h000066400000000000000000000027041462143212600155430ustar00rootroot00000000000000#ifndef CIPHER_H #define CIPHER_H #include "kdf.h" #include "session.h" char *cipher_rsa_decrypt(const unsigned char *ciphertext, size_t len, const struct private_key *private_key); int cipher_rsa_encrypt_bytes(const unsigned char *plaintext, size_t in_len, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len); int cipher_rsa_encrypt(const char *plaintext, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len); char *cipher_aes_decrypt(const unsigned char *ciphertext, size_t len, const unsigned char key[KDF_HASH_LEN]); char *cipher_aes_decrypt_base64(const char *ciphertext, const unsigned char key[KDF_HASH_LEN]); size_t cipher_aes_encrypt(const char *plaintext, const unsigned char key[KDF_HASH_LEN], unsigned char **ciphertext); char *cipher_base64(const unsigned char *bytes, size_t len); size_t cipher_unbase64(const char *ciphertext, unsigned char **b64data); size_t unbase64(const char *ptext, unsigned char **b64data); char *encrypt_and_base64(const char *str, unsigned const char key[KDF_HASH_LEN]); void cipher_decrypt_private_key(const char *key_hex, unsigned const char key[KDF_HASH_LEN], struct private_key *out_key); char *cipher_encrypt_private_key(struct private_key *private_key, unsigned const char key[KDF_HASH_LEN]); char *cipher_sha256_hex(unsigned char *bytes, size_t len); char *cipher_sha256_b64(unsigned char *bytes, size_t len); #endif lastpass-cli-1.5.0/clipboard.c000066400000000000000000000065461462143212600162330ustar00rootroot00000000000000/* * system copy/paste routines * * Copyright (C) 2014-2018 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 "clipboard.h" #include "util.h" #include #include #include static pid_t clipboard_process = 0; static int saved_stdout = -1; static bool registered_closer = false; void clipboard_close(void) { if (!clipboard_process || saved_stdout < 0) return; fflush(stdout); dup2(saved_stdout, STDOUT_FILENO); close(saved_stdout); waitpid(clipboard_process, NULL, 0); clipboard_process = 0; saved_stdout = -1; } void exec_command(char *command) { char *shell = getenv("SHELL"); if (!shell) { shell = "/bin/sh"; } execlp(shell, shell, "-c", command, NULL); } void clipboard_open(void) { int pipefd[2]; if (clipboard_process > 0) return; if (pipe(pipefd) < 0) die_errno("pipe"); saved_stdout = dup(STDOUT_FILENO); if (saved_stdout < 0) die_errno("dup"); clipboard_process = fork(); if (clipboard_process == -1) die_errno("fork"); if (!clipboard_process) { close(pipefd[1]); dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); char *clipboard_command = getenv("LPASS_CLIPBOARD_COMMAND"); if (clipboard_command) { exec_command(clipboard_command); die("Unable to copy contents to clipboard. Please make sure you have `wl-clip`, `xclip`, `xsel`, `pbcopy`, or `putclip` installed."); } else { execlp("wl-copy", "wl-copy", NULL); execlp("xclip", "xclip", "-selection", "clipboard", "-in", NULL); execlp("xsel", "xsel", "--clipboard", "--input", NULL); execlp("pbcopy", "pbcopy", NULL); execlp("putclip", "putclip", "--dos", NULL); die("Unable to copy contents to clipboard. Please make sure you have `xclip`, `xsel`, `pbcopy`, or `putclip` installed."); } } close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); if (!registered_closer) { atexit(clipboard_close); registered_closer = true; } } lastpass-cli-1.5.0/clipboard.h000066400000000000000000000001501462143212600162210ustar00rootroot00000000000000#ifndef CLIPBOARD_H #define CLIPBOARD_H void clipboard_open(void); void clipboard_close(void); #endif lastpass-cli-1.5.0/cmake_extras/000077500000000000000000000000001462143212600165635ustar00rootroot00000000000000lastpass-cli-1.5.0/cmake_extras/install_doc.cmake000066400000000000000000000002571462143212600220640ustar00rootroot00000000000000execute_process(COMMAND install -v -d $ENV{DESTDIR}${MANDIR}/man1) execute_process(COMMAND install -m 0644 -v ${CMAKE_BINARY_DIR}/lpass.1 $ENV{DESTDIR}${MANDIR}/man1/lpass.1) lastpass-cli-1.5.0/cmake_extras/uninstall.cmake000066400000000000000000000022071462143212600215770ustar00rootroot00000000000000################ CMake Uninstall Template ####################### # Used for generating a "make uninstall" target ################################################################# set(MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt") if(EXISTS ${MANIFEST}) message(STATUS "============== Uninstalling ${PROJECT_NAME} ===================") file(STRINGS ${MANIFEST} files) set (files ${files} "${MANDIR}/man1/lpass.1") foreach(file ${files}) set(file "$ENV{DESTDIR}${file}") if(EXISTS ${file}) message(STATUS "Removing file: '${file}'") execute_process( COMMAND ${CMAKE_COMMAND} -E remove ${file} OUTPUT_VARIABLE rm_out RESULT_VARIABLE rm_retval ) if( rm_retval ) message(FATAL_ERROR "Failed to remove file: '${file}'.") endif() else() message(STATUS "File '${file}' does not exist.") endif() endforeach(file) else() message(STATUS "Cannot find install manifest: '${MANIFEST}'") message(STATUS "Have you *actually* run `make install` yet?") endif() lastpass-cli-1.5.0/cmd-add.c000066400000000000000000000077021462143212600155600ustar00rootroot00000000000000/* * command for adding vault entries * * Copyright (C) 2014-2018 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "endpoints.h" #include "agent.h" #include #include #include #include #include int cmd_add(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"username", no_argument, NULL, 'u'}, {"password", no_argument, NULL, 'p'}, {"url", no_argument, NULL, 'L'}, {"field", required_argument, NULL, 'F'}, {"notes", no_argument, NULL, 'O'}, {"app", no_argument, NULL, 'a'}, {"non-interactive", no_argument, NULL, 'X'}, {"note-type", required_argument, NULL, 'T'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; _cleanup_free_ char *field = NULL; char *name; bool non_interactive = false; enum blobsync sync = BLOB_SYNC_AUTO; enum edit_choice choice = EDIT_ANY; enum note_type note_type = NOTE_TYPE_NONE; bool is_app = false; #define ensure_choice() if (choice != EDIT_ANY) goto choice_die; while ((option = getopt_long(argc, argv, "up", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'u': ensure_choice(); choice = EDIT_USERNAME; break; case 'p': ensure_choice(); choice = EDIT_PASSWORD; break; case 'L': ensure_choice(); choice = EDIT_URL; break; case 'F': ensure_choice(); choice = EDIT_FIELD; field = xstrdup(optarg); break; case 'O': ensure_choice(); choice = EDIT_NOTES; break; case 'X': non_interactive = true; break; case 'a': is_app = true; break; case 'T': note_type = parse_note_type_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_add_usage); } } #undef ensure_choice if (argc - optind != 1) die_usage(cmd_add_usage); if (choice == EDIT_NONE) choice_die: die_usage("add ... {--username|--password|--url|--notes|--field=FIELD|--note-type=NOTE_TYPE}"); name = argv[optind]; init_all(sync, key, &session, &blob); return edit_new_account(session, blob, sync, name, choice, field, non_interactive, is_app, note_type, key); } lastpass-cli-1.5.0/cmd-duplicate.c000066400000000000000000000074271462143212600170060ustar00rootroot00000000000000/* * command for making copies of vault entries * * Copyright (C) 2014-2024 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "blob.h" #include "endpoints.h" #include #include #include int cmd_duplicate(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; char *name; enum blobsync sync = BLOB_SYNC_AUTO; struct account *found, *new; struct field *field, *copy_field; while ((option = getopt_long(argc, argv, "", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_duplicate_usage); } } if (argc - optind != 1) die_usage(cmd_duplicate_usage); name = argv[optind]; init_all(sync, key, &session, &blob); found = find_unique_account(blob, name); if (!found) die("Could not find specified account '%s'.", name); new = new_account(); new->share = found->share; new->id = xstrdup("0"); account_set_name(new, xstrdup(found->name), key); account_set_group(new, xstrdup(found->group), key); account_set_username(new, xstrdup(found->username), key); account_set_password(new, xstrdup(found->password), key); account_set_note(new, xstrdup(found->note), key); new->fullname = xstrdup(found->fullname); account_set_url(new, xstrdup(found->url), key, &session->feature_flag); new->pwprotect = found->pwprotect; list_for_each_entry(field, &found->field_head, list) { copy_field = new0(struct field, 1); copy_field->type = xstrdup(field->type); copy_field->name = xstrdup(field->name); field_set_value(found, copy_field, xstrdup(field->value), key); copy_field->checked = field->checked; list_add_tail(©_field->list, &new->field_head); } list_add(&new->list, &blob->account_head); lastpass_update_account(sync, key, session, new, blob); blob_save(blob, key, &session->feature_flag); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-edit.c000066400000000000000000000102611462143212600157470ustar00rootroot00000000000000/* * command for editing vault entries * * Copyright (C) 2014-2018 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "endpoints.h" #include "agent.h" #include #include #include #include #include int cmd_edit(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"username", no_argument, NULL, 'u'}, {"password", no_argument, NULL, 'p'}, {"url", no_argument, NULL, 'L'}, {"field", required_argument, NULL, 'F'}, {"name", no_argument, NULL, 'N'}, {"notes", no_argument, NULL, 'O'}, {"non-interactive", no_argument, NULL, 'X'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; _cleanup_free_ char *field = NULL; char *name; bool non_interactive = false; enum blobsync sync = BLOB_SYNC_AUTO; struct account *editable; enum edit_choice choice = EDIT_ANY; enum note_type note_type = NOTE_TYPE_NONE; #define ensure_choice() if (choice != EDIT_ANY) goto choice_die; while ((option = getopt_long(argc, argv, "up", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'u': ensure_choice(); choice = EDIT_USERNAME; break; case 'p': ensure_choice(); choice = EDIT_PASSWORD; break; case 'L': ensure_choice(); choice = EDIT_URL; break; case 'F': ensure_choice(); choice = EDIT_FIELD; field = xstrdup(optarg); break; case 'N': ensure_choice(); choice = EDIT_NAME; break; case 'O': ensure_choice(); choice = EDIT_NOTES; break; case 'X': non_interactive = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_edit_usage); } } #undef ensure_choice if (argc - optind != 1) die_usage(cmd_edit_usage); if (choice == EDIT_NONE) choice_die: die_usage("edit ... {--name|--username|--password|--url|--notes|--field=FIELD}"); name = argv[optind]; init_all(sync, key, &session, &blob); editable = find_unique_account(blob, name); if (!editable) return edit_new_account(session, blob, sync, name, choice, field, non_interactive, false, note_type, key); if (editable->share && editable->share->readonly) die("%s is a readonly shared entry from %s. It cannot be edited.", editable->fullname, editable->share->name); return edit_account(session, blob, sync, editable, choice, field, non_interactive, key); } lastpass-cli-1.5.0/cmd-export.c000066400000000000000000000146641462143212600163560ustar00rootroot00000000000000/* * command for exporting vault entries into CSV format * * Copyright (C) 2014-2018 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "blob.h" #include "endpoints.h" #include "agent.h" #include #include #include #include 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') { needs_quote = true; break; } } if (needs_quote) putchar('"'); for (ptr = cell; *ptr; ptr++) { putchar(*ptr); if (*ptr == '"') putchar('"'); } if (needs_quote) putchar('"'); if (is_last) printf("\r\n"); else 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, "", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'C': 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 */ list_for_each_entry(account, &blob->account_head, list) { if (account->pwprotect) { unsigned char pwprotect_key[KDF_HASH_LEN]; if (!agent_load_key(pwprotect_key)) die("Could not authenticate for protected entry."); if (memcmp(pwprotect_key, key, KDF_HASH_LEN)) die("Current key is not on-disk key."); break; } } struct field_selection *last_entry = list_last_entry_or_null(&field_list, struct field_selection, list); /* header */ list_for_each_entry(field_sel, &field_list, list) { print_csv_cell(field_sel->name, field_sel == last_entry); } /* entries */ list_for_each_entry(account, &blob->account_head, list) { /* skip groups */ if (!strcmp(account->url, "http://group")) continue; 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); } list_for_each_entry_safe(field_sel, tmp, &field_list, list) { free(field_sel); } session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-generate.c000066400000000000000000000116711462143212600166220ustar00rootroot00000000000000/* * command for generating passwords * * Copyright (C) 2014-2024 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "endpoints.h" #include "clipboard.h" #include #include #include static char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?"; #define ALL_CHARS_LEN (sizeof(chars) - 1) #define NICE_CHARS_LEN 62 int cmd_generate(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"username", required_argument, NULL, 'U'}, {"url", required_argument, NULL, 'L'}, {"no-symbols", no_argument, NULL, 'X'}, {"clip", no_argument, NULL, 'c'}, {0, 0, 0, 0} }; int option; int option_index; char *username = NULL; char *url = NULL; bool no_symbols = false; unsigned long length; char *name; enum blobsync sync = BLOB_SYNC_AUTO; _cleanup_free_ char *password = NULL; struct account *new = NULL, *found; struct account *notes_expansion, *notes_collapsed = NULL; bool clip = false; while ((option = getopt_long(argc, argv, "c", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'U': username = xstrdup(optarg); break; case 'L': url = xstrdup(optarg); break; case 'X': no_symbols = true; break; case 'c': clip = true; break; case '?': default: die_usage(cmd_generate_usage); } } if (argc - optind != 2) die_usage(cmd_generate_usage); name = argv[optind]; length = strtoul(argv[optind + 1], NULL, 10); if (!length) die_usage(cmd_generate_usage); init_all(sync, key, &session, &blob); password = xcalloc(length + 1, 1); for (size_t i = 0; i < length; ++i) password[i] = chars[range_rand(0, no_symbols ? NICE_CHARS_LEN : ALL_CHARS_LEN)]; found = find_unique_account(blob, name); if (found) { if (found->share && found->share->readonly) die("%s is a readonly shared entry from %s. It cannot be edited.", found->fullname, found->share->name); notes_expansion = notes_expand(found); if (notes_expansion) { notes_collapsed = found; found = notes_expansion; } account_set_password(found, xstrdup(password), key); if (username) account_set_username(found, username, key); if (url) { account_set_url(found, xstrdup(url), key, &session->feature_flag); } if (notes_expansion && notes_collapsed) { found = notes_collapsed; notes_collapsed = notes_collapse(notes_expansion); account_free(notes_expansion); account_set_note(found, xstrdup(notes_collapsed->note), key); account_free(notes_collapsed); } } else { new = new_account(); new->id = xstrdup("0"); account_set_password(new, xstrdup(password), key); account_set_fullname(new, xstrdup(name), key); account_set_username(new, username ? username : xstrdup(""), key); account_set_note(new, xstrdup(""), key); account_set_url(new, url ? url : xstrdup(""), key, &session->feature_flag); account_assign_share(blob, new, key, &session->feature_flag); list_add(&new->list, &blob->account_head); } lastpass_update_account(sync, key, session, found ? found : new, blob); blob_save(blob, key, &session->feature_flag); if (clip) clipboard_open(); printf("%s\n", password); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-import.c000066400000000000000000000233251462143212600163410ustar00rootroot00000000000000/* * command for importing vault entries from CSV file into the vault * * Copyright (C) 2014-2024 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "blob.h" #include "endpoints.h" #include "agent.h" #include "list.h" #include #include #include #include #include struct csv_record { struct list_head field_head; struct list_head list; }; struct csv_field { char *value; struct list_head list; }; enum csv_token { CSV_NONE, CSV_FIELD, CSV_NL, CSV_EOF }; static enum csv_token csv_next_token(FILE *fp, char **retp) { int ch, nextch; bool readch = false; bool in_quote = false; bool done = false; bool eol = false; char *rptr; struct buffer result = { .len = 0, .max = 80, .bytes = NULL, }; result.bytes = xcalloc(result.max, 1); *retp = NULL; while (!done) { ch = fgetc(fp); if (ch == EOF) break; readch = true; switch(ch) { case '\n': case ',': /* non-quoted newline / comma terminate field */ if (!in_quote) { done = true; eol = (ch == '\n'); break; } /* otherwise append */ buffer_append_char(&result, ch); break; case '"': /* * quote immediately after comma starts a * double-quoted field */ if (result.len == 0 && !in_quote) { in_quote = true; continue; } if (in_quote) { /* * inside a dqfield, two double quotes adds a * quote, print one */ nextch = fgetc(fp); if (nextch == '"') { buffer_append_char(&result, '"'); continue; } /* otherwise terminate the quote */ in_quote = false; ungetc(nextch, fp); continue; } /* quote not after a comma, treat as unescaped */ buffer_append_char(&result, ch); break; default: buffer_append_char(&result, ch); } } if (!readch) return CSV_EOF; rptr = result.bytes; /* trim cr/nl, but not spaces (they may be significant) */ while (rptr[strlen(rptr)-1] == '\r' || rptr[strlen(rptr)-1] == '\n') { rptr[strlen(rptr)-1] = '\0'; } *retp = rptr; if (eol) return CSV_NL; return CSV_FIELD; } static struct csv_record *csv_record_new() { struct csv_record *r; r = new0(struct csv_record, 1); INIT_LIST_HEAD(&r->field_head); return r; } /* * Return a list of csv_record items from parsing a CSV file. */ static void csv_parse(FILE *fp, struct list_head *list) { char *p; enum csv_token token; struct csv_record *record; record = csv_record_new(); while ((token = csv_next_token(fp, &p))) { if (p) { struct csv_field *field = new0(struct csv_field, 1); field->value = p; list_add_tail(&field->list, &record->field_head); } if (token == CSV_NL || token == CSV_EOF) { if (!list_empty(&record->field_head)) { list_add_tail(&record->list, list); record = csv_record_new(); } } if (token == CSV_EOF) break; } free(record); } static struct account *new_import_account(unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { struct account *account = new_account(); account_set_url(account, xstrdup(""), key, feature_flag); account_set_username(account, xstrdup(""), key); account_set_password(account, xstrdup(""), key); account_set_note(account, xstrdup(""), key); account_set_name(account, xstrdup(""), key); account_set_group(account, xstrdup(""), key); return account; } static int csv_parse_accounts(FILE *fp, struct list_head *account_list, unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { struct list_head items; struct csv_record *record, *tmp_record, *first; struct csv_field *field, *tmp_field; int i = 0; int num_accounts = 0; int url_index = -1, username_index = -1, password_index = -1, extra_index = -1, name_index = -1, grouping_index = -1, fav_index = -1; INIT_LIST_HEAD(&items); csv_parse(fp, &items); if (list_empty(&items)) return 0; #define set_field_index(x) \ do { \ if (!strcmp(field->value, #x)) { \ x ## _index = i; \ } \ } while (0) #define set_field(x, fieldname) \ do { \ if (i == x ## _index) { \ account_set_ ## fieldname (account, field->value, key); \ set = true; \ } \ } while (0) #define set_field_ff(x, fieldname, feature_flag) \ do { \ if (i == x ## _index) { \ account_set_ ## fieldname (account, field->value, key, feature_flag); \ set = true; \ } \ } while (0) /* * first line should tell us the field matrix; if * it doesn't reveal anything useful then we won't * import anything */ record = list_first_entry(&items, struct csv_record, list); list_for_each_entry(field, &record->field_head, list) { set_field_index(url); set_field_index(username); set_field_index(password); set_field_index(extra); set_field_index(name); set_field_index(grouping); set_field_index(fav); i++; } if (url_index == -1 && username_index == -1 && password_index == -1 && extra_index == -1 && name_index == -1 && grouping_index == -1 && fav_index == -1) { die("Could not read the CSV header at the first line of the input file"); return 0; } first = record; list_for_each_entry(record, &items, list) { struct account *account; if (record == first) continue; account = new_import_account(key, feature_flag); i = 0; list_for_each_entry(field, &record->field_head, list) { bool set = false; set_field_ff(url, url, feature_flag); set_field(username, username); set_field(password, password); set_field(name, name); set_field(grouping, group); set_field(extra, note); if (i == fav_index) { account->fav = field->value[0] == '1'; set = true; } /* free unknown field */ if (!set) free(field->value); i++; } num_accounts++; list_add_tail(&account->list, account_list); } list_for_each_entry_safe(record, tmp_record, &items, list) { list_for_each_entry_safe(field, tmp_field, &record->field_head, list) { free(field); } free(record); } return num_accounts; } /* dedupe based on password / url / name / username sets */ int csv_dedupe_compare(const void *k1, const void *k2) { const struct account *a1 = k1, *a2 = k2; int r; if ((r = strcmp(a1->password, a2->password))) return r; if ((r = strcmp(a1->username, a2->username))) return r; if ((r = strcmp(a1->url, a2->url))) return r; return strcmp(a1->name, a2->name); } void csv_dedupe_accounts(struct list_head *blob_accounts, struct list_head *new_accounts) { struct account *account, *tmp; void *search_tree = NULL; list_for_each_entry(account, blob_accounts, list) { tsearch(account, &search_tree, csv_dedupe_compare); } list_for_each_entry_safe(account, tmp, new_accounts, list) { if (tfind(account, &search_tree, csv_dedupe_compare)) { list_del(&account->list); account_free(account); } } } int cmd_import(int argc, char **argv) { static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"keep-dupes", no_argument, NULL, 'k'}, {0, 0, 0, 0} }; int option; int option_index; enum blobsync sync = BLOB_SYNC_AUTO; unsigned char key[KDF_HASH_LEN]; _cleanup_fclose_ FILE *fp; struct session *session = NULL; struct blob *blob = NULL; struct list_head accounts; struct account *account; int count, new_count; bool keep_dupes = false; int ret; while ((option = getopt_long(argc, argv, "", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'k': keep_dupes = true; break; case '?': default: die_usage(cmd_import_usage); } } if (argc - optind < 1) { fp = stdin; } else { char *filename = argv[optind]; fp = fopen(filename, "rb"); if (!fp) die("Unable to open %s", filename); } init_all(sync, key, &session, &blob); INIT_LIST_HEAD(&accounts); count = csv_parse_accounts(fp, &accounts, key, &session->feature_flag); printf("Parsed %d accounts\n", count); new_count = 0; if (!keep_dupes) csv_dedupe_accounts(&blob->account_head, &accounts); list_for_each_entry(account, &accounts, list) { new_count++; }; if (count - new_count) printf("Removed %d duplicate accounts\n", count - new_count); ret = lastpass_upload(session, &accounts); if (ret) die("Import failed (%d)\n", ret); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-login.c000066400000000000000000000102551462143212600161350ustar00rootroot00000000000000/* * command for logging into the service * * Copyright (C) 2014-2018 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 "cmd.h" #include "kdf.h" #include "password.h" #include "session.h" #include "util.h" #include "process.h" #include "endpoints.h" #include "config.h" #include "agent.h" #include "terminal.h" #include int cmd_login(int argc, char **argv) { static struct option long_options[] = { {"trust", no_argument, NULL, 't'}, {"plaintext-key", no_argument, NULL, 'P'}, {"force", no_argument, NULL, 'f'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; bool trust = false; bool plaintext_key = false; bool force = false; char *username; _cleanup_free_ char *error = NULL; _cleanup_free_ char *password = NULL; int iterations; struct session *session; unsigned char key[KDF_HASH_LEN]; char hex[KDF_HEX_LEN]; while ((option = getopt_long(argc, argv, "f", long_options, &option_index)) != -1) { switch (option) { case 't': trust = true; break; case 'P': plaintext_key = true; break; case 'f': force = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_login_usage); } } if (argc - optind != 1) die_usage(cmd_login_usage); if (!force && plaintext_key && !ask_yes_no(false, "You have used the --plaintext-key option. This option will greatly reduce the security of your passwords. You are advised, instead, to use the agent, whose timeout can be disabled by setting LPASS_AGENT_TIMEOUT=0. Are you sure you would like to do this?")) die("Login aborted. Try again without --plaintext-key."); username = argv[optind]; iterations = lastpass_iterations(username); if (!iterations) die("Unable to fetch iteration count. Check your internet connection and be sure your username is valid."); do { free(password); password = password_prompt("Master Password", error, "Please enter the LastPass master password for <%s>.", username); if (!password) die("Failed to enter correct password."); kdf_login_key(username, password, iterations, hex); kdf_decryption_key(username, password, iterations, key); free(error); error = NULL; session = lastpass_login(username, hex, key, iterations, &error, trust); } while (!session_is_valid(session)); config_unlink("plaintext_key"); if (plaintext_key) config_write_buffer("plaintext_key", (char *)key, KDF_HASH_LEN); agent_save(username, iterations, key); session_save(session, key); session_free(session); session = NULL; terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "Success" TERMINAL_RESET ": Logged in as " TERMINAL_UNDERLINE "%s" TERMINAL_RESET ".\n", username); return 0; } lastpass-cli-1.5.0/cmd-logout.c000066400000000000000000000055061462143212600163410ustar00rootroot00000000000000/* * command for logging out of LastPass * * Copyright (C) 2014-2018 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "agent.h" #include "upload-queue.h" #include "endpoints.h" #include #include #include int cmd_logout(int argc, char **argv) { static struct option long_options[] = { {"force", no_argument, NULL, 'f'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; bool force = false; struct session *session = NULL; unsigned char key[KDF_HASH_LEN]; while ((option = getopt_long(argc, argv, "f", long_options, &option_index)) != -1) { switch (option) { case 'f': force = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_logout_usage); } } if (optind < argc) die_usage(cmd_logout_usage); if (!force && !ask_yes_no(true, "Are you sure you would like to log out?")) { terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "Log out" TERMINAL_RESET ": aborted.\n"); return 1; } if (agent_ask(key)) { init_all(0, key, &session, NULL); lastpass_logout(session); } session_kill(); terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "Log out" TERMINAL_RESET ": complete.\n"); return 0; } lastpass-cli-1.5.0/cmd-ls.c000066400000000000000000000230441462143212600154430ustar00rootroot00000000000000/* * command for listing the vault * * Copyright (C) 2014-2024 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "format.h" #include "kdf.h" #include #include #include #include #include static bool long_listing = false; static bool show_mtime = true; struct node { char *name; struct account *account; bool shared; struct list_head children; struct list_head list; }; struct path_component { char *component; struct list_head list; }; /* * Tokenize path and add each component to the components list. * For group names, the path separator is a backslash. The path * string is modified in place and the component list stores * pointers to the modified string. */ static void parse_path(char *path, struct list_head *components) { char *token; struct path_component *pc; for (token = strtok(path, "\\"); token; token = strtok(NULL, "\\")) { pc = new0(struct path_component, 1); pc->component = token; list_add_tail(&pc->list, components); } } static void __insert_node(struct node *head, struct list_head *components, struct account *account) { struct path_component *pc; struct node *child, *tmp; /* iteratively build a tree from all the path components */ list_for_each_entry(pc, components, list) { child = NULL; list_for_each_entry(tmp, &head->children, list) { if (!strcmp(tmp->name, pc->component)) { child = tmp; break; } } if (!child) { child = new0(struct node, 1); child->shared= !!account->share; child->name = xstrdup(pc->component); INIT_LIST_HEAD(&child->children); list_add_tail(&child->list, &head->children); } head = child; } /* skip group display -- we already added the hierarchy for them */ if (account_is_group(account)) return; /* and add the site at the lowest level */ child = new0(struct node, 1); child->account = account; child->shared= !!account->share; child->name = xstrdup(account->name); INIT_LIST_HEAD(&child->children); list_add_tail(&child->list, &head->children); } static void insert_node(struct node *head, const char *path, struct account *account) { struct list_head components; struct path_component *pc, *tmp; _cleanup_free_ char *dirname = xstrdup(path); char *pos; /* remove name portion of fullname; we don't parse that */ if (strlen(dirname) >= strlen(account->name)) { char *tmp = dirname + strlen(dirname) - strlen(account->name); if (strcmp(tmp, account->name) == 0) { *tmp = 0; } } pos = dirname; /* trim trailing slash */ if (strlen(pos)) pos[strlen(pos)-1] = 0; /* * We are left with one of: * * (none)/ * groupname/ * Shared-folder/ * Shared-folder/groupname/ * * If there are embedded backslashes, these are treated as folder * names by parse_path(). */ INIT_LIST_HEAD(&components); if (account->share && strlen(pos) >= strlen(account->share->name)) { pos[strlen(account->share->name)] = 0; parse_path(pos, &components); pos += strlen(account->share->name) + 1; } /* either '(none)/' or group/ or empty string */ parse_path(pos, &components); __insert_node(head, &components, account); list_for_each_entry_safe(pc, tmp, &components, list) { list_del(&pc->list); free(pc); } } static void free_node(struct node *head) { struct node *node, *tmp; if (!head) return; list_for_each_entry_safe(node, tmp, &head->children, list) { free_node(node); } free(head->name); free(head); } static void print_node(struct node *head, char *fmt_str, int level) { struct node *node; list_for_each_entry(node, &head->children, list) { if (node->name) { for (int i = 0; i < level; ++i) printf(" "); if (node->account) { struct buffer buf; buffer_init(&buf); format_account(&buf, fmt_str, node->account); terminal_printf("%s\n", buf.bytes); free(buf.bytes); } else if (node->shared) terminal_printf(TERMINAL_FG_CYAN TERMINAL_BOLD "%s" TERMINAL_RESET "\n", node->name); else terminal_printf(TERMINAL_FG_BLUE TERMINAL_BOLD "%s" TERMINAL_RESET "\n", node->name); } print_node(node, fmt_str, level + 1); } } static int compare_account(const void *a, const void *b) { struct account * const *acct_a = a; struct account * const *acct_b = b; _cleanup_free_ char *str1 = get_display_fullname(*acct_a); _cleanup_free_ char *str2 = get_display_fullname(*acct_b); return strcmp(str1, str2); } int cmd_ls(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {"format", required_argument, NULL, 'f'}, {"long", no_argument, NULL, 'l'}, {0, 0, 0, 0} }; int option; int option_index; char *group = NULL; int group_len; char *sub; struct node *root; char *fullname; enum blobsync sync = BLOB_SYNC_AUTO; enum color_mode cmode = COLOR_MODE_AUTO; bool print_tree; struct account *account; _cleanup_free_ struct account **account_array = NULL; 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': sync = parse_sync_string(optarg); break; case 'C': cmode = parse_color_mode_string(optarg); break; case 'f': fmt_str = xstrdup(optarg); break; case 'l': long_listing = true; break; case 'm': show_mtime = true; break; case 'u': show_mtime = false; break; case '?': default: die_usage(cmd_ls_usage); } } switch (argc - optind) { case 0: break; case 1: group = argv[optind]; break; default: die_usage(cmd_ls_usage); } terminal_set_color_mode(cmode); print_tree = cmode == COLOR_MODE_ALWAYS || (cmode == COLOR_MODE_AUTO && isatty(fileno(stdout))); init_all(sync, key, &session, &blob); root = new0(struct node, 1); INIT_LIST_HEAD(&root->children); /* '(none)' group -> search for any without group */ if (group && !strcmp(group, "(none)")) group = ""; num_accounts = 0; 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, &session->feature_flag); account_array[i++] = account; } qsort(account_array, num_accounts, sizeof(struct account *), compare_account); if (!fmt_str) { xasprintf(&fmt_str, TERMINAL_FG_CYAN "%s" TERMINAL_FG_GREEN TERMINAL_BOLD "%%a%c" TERMINAL_NO_BOLD " [id: %%ai]" "%s" TERMINAL_RESET, (long_listing) ? ((show_mtime) ? "%am " : "%aU ") : "", (print_tree) ? 'n' : 'N', (long_listing) ? " [username: %au]" : ""); } for (i=0; i < num_accounts; i++) { struct account *account = account_array[i]; if (group) { sub = strstr(account->fullname, group); if (!sub || sub != account->fullname) continue; group_len = strlen(group); sub += group_len; if (group_len && group[group_len - 1] != '/' && sub[0] != '\0' && sub[0] != '/') continue; } fullname = get_display_fullname(account); if (print_tree) insert_node(root, fullname, account); else { struct buffer buf; buffer_init(&buf); format_account(&buf, fmt_str, account); terminal_printf("%s\n", buf.bytes); free(buf.bytes); } free(fullname); } if (print_tree) print_node(root, fmt_str, 0); free_node(root); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-mv.c000066400000000000000000000073211462143212600154470ustar00rootroot00000000000000/* * command for moving vault entries * * Copyright (C) 2014-2024 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "endpoints.h" #include "agent.h" #include #include #include #include #include int cmd_mv(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; enum blobsync sync = BLOB_SYNC_AUTO; int option; int option_index; char *name; char *folder; char *new_fullname = NULL; struct account *account; struct share *old_share; while ((option = getopt_long(argc, argv, "SC", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_mv_usage); } } if (argc - optind != 2) die_usage(cmd_mv_usage); name = argv[optind++]; folder = argv[optind++]; init_all(sync, key, &session, &blob); account = find_unique_account(blob, name); if (!account) { die("Unable to find account %s", name); } xasprintf(&new_fullname, "%s/%s", folder, account->name); old_share = account->share; account_set_fullname(account, new_fullname, key); account_assign_share(blob, account, key, &session->feature_flag); if (account->share && account->share->readonly) { die("You do not have access to move %s into %s", account->name, account->share->name); } if (old_share != account->share) { /* * when moving into / out of a shared folder, we need to * reencrypt and make a special api call for that. */ int ret = lastpass_share_move(session, account, old_share); if (ret) { die("Move to/from shared folder failed (%d)\n", ret); } list_del(&account->list); } else { /* standard case: account just changing group name */ lastpass_update_account(sync, key, session, account, blob); } blob_save(blob, key, &session->feature_flag); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-passwd.c000066400000000000000000000165511462143212600163330ustar00rootroot00000000000000/* * command for changing master password * * Copyright (C) 2014-2018 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 02111-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 #include #include "blob.h" #include "kdf.h" #include "cmd.h" #include "endpoints.h" #include "config.h" #include "password.h" #include "cipher.h" #include "session.h" static void show_status_bar(const char *operation, unsigned int cur, unsigned int max) { char progress[41] = {0}; size_t len; if (!max) max = 1; if (cur > max) cur = max; len = (cur * (sizeof(progress) - 1)) / max; if (len) memset(progress, '=', len); terminal_fprintf(stderr, TERMINAL_FG_CYAN "%s " TERMINAL_RESET TERMINAL_FG_BLUE "[%-*s] " TERMINAL_RESET TERMINAL_FG_CYAN "%d/%d \r" TERMINAL_RESET, operation, (int) sizeof(progress)-1, progress, cur, max); } static void reencrypt(struct session *session, struct pwchange_info *info, unsigned char key[KDF_HASH_LEN], unsigned char new_key[KDF_HASH_LEN]) { struct pwchange_field *field; struct pwchange_su_key *su_key; struct private_key tmp; unsigned int n_fields = 0; unsigned int i = 0; unsigned int n_required = 0; unsigned int errors = 0; /* count how many things we'll encrypt */ list_for_each_entry(field, &info->fields, list) { n_fields++; } list_for_each_entry(su_key, &info->su_keys, list) { n_fields++; } /* plus sharing key */ n_fields++; show_status_bar("Re-encrypting", i++, n_fields); /* decrypt and re-encrypt RSA sharing key */ cipher_decrypt_private_key(info->privkey_encrypted, key, &tmp); if (tmp.len != session->private_key.len || memcmp(session->private_key.key, tmp.key, session->private_key.len)) { die("Server and session private key don't match! Try lpass sync first."); } info->new_privkey_encrypted = cipher_encrypt_private_key(&tmp, new_key); secure_clear(tmp.key, tmp.len); free(tmp.key); /* reencrypt site info */ list_for_each_entry(field, &info->fields, list) { show_status_bar("Re-encrypting", i++, n_fields); if (!field->optional) n_required++; char *ptext = cipher_aes_decrypt_base64(field->old_ctext, key); if (!ptext) { if (!field->optional) errors++; ptext = " "; } field->new_ctext = encrypt_and_base64(ptext, new_key); } /* * Fail if > 10% decryption errors. This indicates the blob and key * are out of sync somehow, or that user has reverted a password * change but some entries are encrypted with the new key. */ if (errors > n_required / 10) die("Too many decryption failures."); /* encrypt recovery copy of our key */ list_for_each_entry(su_key, &info->su_keys, list) { show_status_bar("Re-encrypting", i++, n_fields); size_t enc_key_len = su_key->sharing_key.len; unsigned char *enc_key = xmalloc(enc_key_len); cipher_rsa_encrypt_bytes(new_key, KDF_HASH_LEN, &su_key->sharing_key, enc_key, &enc_key_len); bytes_to_hex(enc_key, &su_key->new_enc_key, enc_key_len); free(enc_key); } show_status_bar("Re-encrypting", n_fields, n_fields); info->new_privkey_hash = cipher_sha256_hex((unsigned char *) info->new_privkey_encrypted, strlen(info->new_privkey_encrypted)); info->new_key_hash = cipher_sha256_hex(new_key, KDF_HASH_LEN); printf("\n"); } int cmd_passwd(int argc, char **argv) { UNUSED(argc); UNUSED(argv); unsigned char key[KDF_HASH_LEN]; unsigned char new_key[KDF_HASH_LEN]; char hex[KDF_HEX_LEN]; char new_hex[KDF_HEX_LEN]; struct session *session = NULL; struct blob *blob; int ret; _cleanup_free_ char *password = NULL; _cleanup_free_ char *new_password = NULL; _cleanup_free_ char *pw2 = NULL; _cleanup_free_ char *username = NULL; int iterations; bool match; struct pwchange_info info; /* load existing session, if present */ init_all(BLOB_SYNC_YES, key, &session, &blob); username = config_read_string("username"); iterations = lastpass_iterations(username); if (!iterations) die("Unable to fetch iteration count. Check your internet connection and be sure your username is valid."); /* reprompt for old mpw */ password = password_prompt("Current Master Password", NULL, "Please enter the current LastPass master password for <%s>.", username); if (!password) die("Failed to enter password."); kdf_login_key(username, password, iterations, hex); secure_clear_str(password); /* prompt for new pw */ new_password = password_prompt("New Master Password", NULL, "Please enter the new LastPass master password for <%s>.", username); pw2 = password_prompt("Confirm New Master Password", NULL, "Please retype the new LastPass master password for <%s>.", username); if (!new_password || !pw2) die("Failed to enter new password."); match = strcmp(new_password, pw2) == 0; secure_clear_str(pw2); if (!match) die("Bad password: passwords don't match."); if (strlen(new_password) < 8) die("Bad password: too short."); kdf_decryption_key(username, new_password, iterations, new_key); kdf_login_key(username, new_password, iterations, new_hex); secure_clear_str(new_password); /* * Fetch the data to reencrypt. We may learn at this point that the * current password was incorrect, so handle that accordingly. */ terminal_printf(TERMINAL_FG_CYAN "Fetching data...\n" TERMINAL_RESET); ret = lastpass_pwchange_start(session, username, hex, &info); if (ret) { if (ret == -EPERM) die("Incorrect password. Password not changed."); else die("Error changing password (error=%d)", ret); } /* reencrypt */ reencrypt(session, &info, key, new_key); terminal_printf(TERMINAL_FG_CYAN "Uploading...\n" TERMINAL_RESET); _cleanup_free_ char *enc_username = encrypt_and_base64(username, new_key); ret = lastpass_pwchange_complete(session, username, enc_username, new_hex, iterations, &info); if (ret) die("Password change failed."); session_kill(); terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "Success" TERMINAL_RESET ": Password changed and logged out.\n"); return 0; } lastpass-cli-1.5.0/cmd-rm.c000066400000000000000000000060411462143212600154410ustar00rootroot00000000000000/* * command for removing vault entries * * Copyright (C) 2014-2024 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "blob.h" #include "endpoints.h" #include #include #include int cmd_rm(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; char *name; enum blobsync sync = BLOB_SYNC_AUTO; struct account *found; while ((option = getopt_long(argc, argv, "", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_rm_usage); } } if (argc - optind != 1) die_usage(cmd_rm_usage); name = argv[optind]; init_all(sync, key, &session, &blob); found = find_unique_account(blob, name); if (!found) die("Could not find specified account '%s'.", name); if (found->share && found->share->readonly) die("%s is a readonly shared entry from %s. It cannot be deleted.", found->fullname, found->share->name); list_del(&found->list); lastpass_remove_account(sync, key, session, found, blob); blob_save(blob, key, &session->feature_flag); account_free(found); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-share.c000066400000000000000000000325021462143212600161260ustar00rootroot00000000000000/* * commands to manipulate shared folders * * Copyright (C) 2015 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 grant you * additional permission to link or combine this program with the OpenSSL * library and distribute the resulting work. See the LICENSE.OpenSSL file * in this distribution for more details. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "agent.h" #include "kdf.h" #include "endpoints.h" #include "clipboard.h" #include "upload-queue.h" #include "process.h" #include #include #include #include #include struct share_args { struct session *session; struct blob *blob; enum blobsync sync; unsigned char key[KDF_HASH_LEN]; const char *sharename; struct share *share; bool read_only; bool set_read_only; bool admin; bool set_admin; bool hide_passwords; bool set_hide_passwords; bool specified_limit_type; bool whitelist; bool add; bool remove; bool clear; }; struct share_command { const char *name; const char *usage; int (*cmd)(struct share_command *cmd, int, char **, struct share_args *share); }; #define share_userls_usage "userls SHARE" #define share_useradd_usage "useradd [--read-only=[true|false] --hidden=[true|false] --admin=[true|false] SHARE USERNAME" #define share_usermod_usage "usermod [--read-only=[true|false] --hidden=[true|false] --admin=[true|false] SHARE USERNAME" #define share_userdel_usage "userdel SHARE USERNAME" #define share_create_usage "create SHARE" #define share_limit_usage "limit [--deny|--allow] [--add|--rm|--clear] SHARE USERNAME [sites]" #define share_rm_usage "rm SHARE" static char *checkmark(int x) { return (x) ? "x" : "_"; } static void die_share_usage(struct share_command *cmd) { die_usage(cmd->usage); } static int share_userls(struct share_command *cmd, int argc, char **argv, struct share_args *args) { UNUSED(argv); struct share_user *user; char name[40]; LIST_HEAD(users); bool has_groups = false; if (argc) die_share_usage(cmd); if (lastpass_share_getinfo(args->session, args->share->id, &users)) die("Unable to access user list for share %s\n", args->sharename); terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "%-40s %6s %6s %6s %6s %6s" TERMINAL_RESET "\n", "User", "RO", "Admin", "Hide", "OutEnt", "Accept"); list_for_each_entry(user, &users, list) { if (user->is_group) { has_groups = true; continue; } if (user->realname) { snprintf(name, sizeof(name), "%s <%s>", user->realname, user->username); } else { snprintf(name, sizeof(name), "%s", user->username); } terminal_printf("%-40s %6s %6s %6s %6s %6s" "\n", name, checkmark(user->read_only), checkmark(user->admin), checkmark(user->hide_passwords), checkmark(user->outside_enterprise), checkmark(user->accepted)); } if (!has_groups) return 0; terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "%-40s %6s %6s %6s %6s %6s" TERMINAL_RESET "\n", "Group", "RO", "Admin", "Hide", "OutEnt", "Accept"); list_for_each_entry(user, &users, list) { if (!user->is_group) continue; terminal_printf("%-40s %6s %6s %6s %6s %6s" "\n", user->username, checkmark(user->read_only), checkmark(user->admin), checkmark(user->hide_passwords), checkmark(user->outside_enterprise), checkmark(user->accepted)); } return 0; } static int share_useradd(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user new_user = { .read_only = args->read_only, .hide_passwords = args->hide_passwords, .admin = args->admin }; if (argc != 1) die_share_usage(cmd); new_user.username = argv[0]; lastpass_share_user_add(args->session, args->share, &new_user); return 0; } static struct share_user *get_user_from_share(struct session *session, struct share *share, const char *username) { struct share_user *tmp, *found = NULL; LIST_HEAD(users); if (lastpass_share_getinfo(session, share->id, &users)) die("Unable to access user list for share %s\n", share->name); list_for_each_entry(tmp, &users, list) { if (strcmp(tmp->username, username) == 0) { found = tmp; break; } } if (!found) die("Unable to find user %s in the user list\n", username); return found; } static int share_usermod(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user *user; if (argc != 1) die_share_usage(cmd); user = get_user_from_share(args->session, args->share, argv[0]); if (args->set_read_only) user->read_only = args->read_only; if (args->set_hide_passwords) user->hide_passwords = args->hide_passwords; if (args->set_admin) user->admin = args->admin; lastpass_share_user_mod(args->session, args->share, user); return 0; } static int share_userdel(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user *found; if (argc != 1) die_share_usage(cmd); found = get_user_from_share(args->session, args->share, argv[0]); lastpass_share_user_del(args->session, args->share->id, found); return 0; } static void print_share_limits(struct blob *blob, struct share *share, struct share_limit *limit) { struct account *account; struct share_limit_aid *aid; char sitename[80]; /* display current settings for this user */ terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "%-60s %7s %5s" TERMINAL_RESET "\n", "Site", "Unavail", "Avail"); list_for_each_entry(account, &blob->account_head, list) { if (account->share != share) continue; bool in_list = false; list_for_each_entry(aid, &limit->aid_list, list) { if (!strcmp(aid->aid, account->id)) { in_list = true; } } bool avail = (in_list && limit->whitelist) || (!in_list && !limit->whitelist); snprintf(sitename, sizeof(sitename), TERMINAL_BOLD "%-.30s" TERMINAL_NO_BOLD " [id: %s]", account->name, account->id); terminal_printf(TERMINAL_FG_GREEN "%-66s" TERMINAL_RESET " %8s %5s\n", sitename, checkmark(!avail), checkmark(avail)); } } static int share_limit(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user *found; struct share_limit limit; struct account *account; struct share_limit_aid *aid, *tmp; struct blob *blob = args->blob; bool changed_list_type; int optind; struct list_head potential_set; struct list_head matches; if (argc < 1) die_share_usage(cmd); found = get_user_from_share(args->session, args->share, argv[0]); lastpass_share_get_limits(args->session, args->share, found, &limit); if (!args->specified_limit_type) args->whitelist = limit.whitelist; /* * prompt if we switch list type and there are entries already, in * order to avoid accidentally changing a blacklist to a whitelist */ changed_list_type = args->whitelist != limit.whitelist && !list_empty(&limit.aid_list); if (argc == 1 && !changed_list_type) { /* nothing to do, just print current limits */ print_share_limits(blob, args->share, &limit); return 0; } if (changed_list_type) { bool isok = ask_yes_no(false, "Supplied limit type (%s) doesn't match existing list (%s).\nContinue and switch?", args->whitelist ? "default deny" : "default allow", limit.whitelist ? "default deny" : "default allow"); if (!isok) die("Aborted."); } /* add to, or subtract from current list */ INIT_LIST_HEAD(&potential_set); INIT_LIST_HEAD(&matches); /* search only accts in this share */ list_for_each_entry(account, &blob->account_head, list) { if (account->share == args->share) list_add(&account->match_list, &potential_set); } for (optind = 1; optind < argc; optind++) { char *name = argv[optind]; find_matching_accounts(&potential_set, name, &matches); } if (args->clear) { list_for_each_entry_safe(aid, tmp, &limit.aid_list, list) { list_del(&aid->list); free(aid->aid); } } list_for_each_entry(account, &matches, match_list) { /* add account to share_limit */ bool in_list = false; list_for_each_entry(aid, &limit.aid_list, list) { if (!strcmp(aid->aid, account->id)) { in_list = true; break; } } if ((!in_list && args->add) || args->clear) { struct share_limit_aid *newaid = new0(struct share_limit_aid, 1); newaid->aid = account->id; list_add_tail(&newaid->list, &limit.aid_list); } else if (in_list && args->remove) { list_del(&aid->list); } } limit.whitelist = args->whitelist; lastpass_share_set_limits(args->session, args->share, found, &limit); print_share_limits(blob, args->share, &limit); return 0; } 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); 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; } static int share_rm(struct share_command *cmd, int argc, char **argv, struct share_args *args) { if (argc != 0) die_share_usage(cmd); UNUSED(argv); lastpass_share_delete(args->session, args->share); return 0; } #define SHARE_CMD(name) { #name, "share " share_##name##_usage, share_##name } static struct share_command share_commands[] = { SHARE_CMD(userls), SHARE_CMD(useradd), SHARE_CMD(usermod), SHARE_CMD(userdel), SHARE_CMD(create), SHARE_CMD(rm), SHARE_CMD(limit), }; #undef SHARE_CMD /* Display more verbose usage if no subcmd is given or matched. */ static void share_help(void) { terminal_fprintf(stderr, "Usage: %s %s\n", ARGV[0], cmd_share_usage); for (size_t i = 0; i < ARRAY_SIZE(share_commands); ++i) printf(" %s %s\n", ARGV[0], share_commands[i].usage); exit(1); } int cmd_share(int argc, char **argv) { char *subcmd; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {"read-only", required_argument, NULL, 'r'}, {"hidden", required_argument, NULL, 'H'}, {"admin", required_argument, NULL, 'a'}, {"deny", no_argument, NULL, 'd'}, {"allow", no_argument, NULL, 'w'}, {"add", no_argument, NULL, 'A'}, {"rm", no_argument, NULL, 'R'}, {"clear", no_argument, NULL, 'c'}, {0, 0, 0, 0} }; struct share_args args = { .sync = BLOB_SYNC_AUTO, .read_only = true, .hide_passwords = true, .add = true, }; bool invalid_params = false; struct share_command *command; /* * Parse out all option commands for all subcommands, and store * them in the share_args struct. * * All commands have at least subcmd and sharename non-option args. * Additional non-option commands are passed as argc/argv to the * sub-command. */ int option; int option_index; while ((option = getopt_long(argc, argv, "S:C:r:H:a:dwARc", long_options, &option_index)) != -1) { switch (option) { case 'S': args.sync = parse_sync_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case 'r': args.read_only = parse_bool_arg_string(optarg); args.set_read_only = true; break; case 'H': args.hide_passwords = parse_bool_arg_string(optarg); args.set_hide_passwords = true; break; case 'a': args.admin = parse_bool_arg_string(optarg); args.set_admin = true; break; case 'w': args.whitelist = true; args.specified_limit_type = true; break; case 'd': args.whitelist = false; args.specified_limit_type = true; break; case 'A': args.add = true; args.remove = args.clear = false; break; case 'R': args.remove = true; args.add = args.clear = false; break; case 'c': args.clear = true; args.add = args.remove = false; break; case '?': default: invalid_params = true; } } if (argc - optind < 1) share_help(); subcmd = argv[optind++]; command = NULL; for (unsigned int i=0; i < ARRAY_SIZE(share_commands); i++) { if (strcmp(subcmd, share_commands[i].name) == 0) { command = &share_commands[i]; break; } } if (!command) share_help(); if (argc - optind < 1 || invalid_params) die_share_usage(command); args.sharename = argv[optind++]; init_all(args.sync, args.key, &args.session, &args.blob); if (strcmp(subcmd, "create") != 0) { args.share = find_unique_share(args.blob, args.sharename); if (!args.share) die("Share %s not found.", args.sharename); } command->cmd(command, argc - optind, &argv[optind], &args); session_free(args.session); blob_free(args.blob); return 0; } lastpass-cli-1.5.0/cmd-show.c000066400000000000000000000335561462143212600160160ustar00rootroot00000000000000/* * command to show the contents of a vault entry * * Copyright (C) 2014-2018 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 "cmd.h" #include "cipher.h" #include "util.h" #include "config.h" #include "terminal.h" #include "agent.h" #include "kdf.h" #include "endpoints.h" #include "clipboard.h" #include "format.h" #include "json-format.h" #include #include #include #include #include /* * If a secure note field contains ascii armor, its newlines will * have been replaced by spaces when saving. Undo this for * display purposes. The replacement (if applicable) is done * in-place. */ static char *fix_ascii_armor(char *armor_str) { char *end_header, *start_trailer, *ptr; /* need at least 4 "-----" strings */ if (strlen(armor_str) < 20) return armor_str; /* look for -----BEGIN [xxx]----- and -----END [xxx]----- strings */ if (strncmp(armor_str, "-----BEGIN", 10)) return armor_str; end_header = strstr(armor_str + 10, "----- "); if (!end_header) return armor_str; start_trailer = strstr(end_header, "-----END"); if (!start_trailer) return armor_str; if (strncmp(armor_str + strlen(armor_str) - 5, "-----", 5)) return armor_str; /* ok, probably ascii armor, go ahead and munge it as such */ ptr = end_header; while ((ptr = strchr(ptr, ' ')) != NULL) { if (ptr >= start_trailer) break; /* don't modify spaces after headers, e.g. encrypted keys */ if (ptr[-1] == ':') { ptr++; continue; } *ptr = '\n'; } return armor_str; } static char *attachment_filename(struct account *account, struct attach *attach) { _cleanup_free_ unsigned char *key_bin = NULL; if (!attach->filename || !account->attachkey || strlen(account->attachkey) != KDF_HASH_LEN * 2 || hex_to_bytes(account->attachkey, &key_bin)) { return xstrdup("unknown"); } return cipher_aes_decrypt_base64(attach->filename, key_bin); } static bool attachment_is_binary(unsigned char *data, size_t len) { size_t i; for (i = 0; i < min(len, 100); i++) { if (!isprint(data[i])) return true; } return false; } static void show_attachment(const struct session *session, struct account *account, struct attach *attach, bool quiet) { _cleanup_free_ unsigned char *key_bin = NULL; _cleanup_free_ char *result = NULL; _cleanup_free_ char *filename = NULL; int ret; char opt; char *ptext; size_t len; char *shareid = NULL; unsigned char *bytes = NULL; FILE *fp = stdout; if (!account->attachkey || strlen(account->attachkey) != KDF_HASH_LEN * 2) die("Missing attach key for account %s\n", account->name); if (hex_to_bytes(account->attachkey, &key_bin)) die("Invalid attach key for account %s\n", account->name); if (account->share != NULL) shareid = account->share->id; filename = attachment_filename(account, attach); ret = lastpass_load_attachment(session, shareid, attach, &result); if (ret) die("Could not load attachment %s\n", attach->id); ptext = cipher_aes_decrypt_base64(result, key_bin); if (!ptext) die("Unable to decrypt attachment %s\n", attach->id); len = unbase64(ptext, &bytes); if (attachment_is_binary(bytes, len) && !quiet) { opt = ask_options("yns", 's', "\"%s\" is a binary file, print it anyway (or save)? ", filename); switch (opt) { case 'n': return; case 's': fp = fopen(filename, "wb"); if (!fp) die("Unable to open %s\n", filename); break; default: break; } } len = fwrite(bytes, 1, len, fp); if (fp != stdout) { fprintf(stderr, TERMINAL_FG_GREEN "Wrote %zu bytes to \"%s\"\n" TERMINAL_RESET, len, filename); fclose(fp); } } static char *pretty_field_value(struct field *field) { char *value; if (!strcmp(field->type, "checkbox")) value = xstrdup(field->checked ? "Checked" : "Unchecked"); else if (!strcmp(field->type, "radio")) xasprintf(&value, "%s, %s", field->value, field->checked ? "Checked" : "Unchecked"); else value = fix_ascii_armor(xstrdup(field->value)); return value; } static void print_header(char *title_format, struct account *found) { struct buffer buf; buffer_init(&buf); format_account(&buf, title_format, found); terminal_printf("%s\n", buf.bytes); free(buf.bytes); } static void print_field(char *field_format, struct account *account, char *name, char *value) { struct buffer buf; buffer_init(&buf); format_field(&buf, field_format, account, name, value); terminal_printf("%s\n", buf.bytes); free(buf.bytes); } static void print_attachment(char *field_format, struct account *account, struct attach *attach) { _cleanup_free_ char *attach_id = NULL; _cleanup_free_ char *filename = NULL; xasprintf(&attach_id, "att-%s", attach->id); filename = attachment_filename(account, attach); print_field(field_format, account, attach_id, filename); } static struct attach *find_attachment(struct account *account, const char *attach_id) { struct attach *attach = NULL; /* trim 'att-' off id if someone passed it */ if (!strncmp(attach_id, "att-", 4)) attach_id += 4; list_for_each_entry(attach, &account->attach_head, list) { if (!strcmp(attach->id, attach_id)) return attach; } return NULL; } int cmd_show(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; _cleanup_free_ char *value = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"all", no_argument, NULL, 'A'}, {"username", no_argument, NULL, 'u'}, {"password", no_argument, NULL, 'p'}, {"url", no_argument, NULL, 'L'}, {"field", required_argument, NULL, 'f'}, {"id", no_argument, NULL, 'I'}, {"name", no_argument, NULL, 'N'}, {"notes", no_argument, NULL, 'O'}, {"attach", required_argument, NULL, 'a'}, {"clip", no_argument, NULL, 'c'}, {"color", required_argument, NULL, 'C'}, {"basic-regexp", no_argument, NULL, 'G'}, {"fixed-strings", no_argument, NULL, 'F'}, {"expand-multi", no_argument, NULL, 'x'}, {"title-format", required_argument, NULL, 't'}, {"format", required_argument, NULL, 'o'}, {"json", no_argument, NULL, 'j'}, {"quiet", no_argument, NULL, 'q'}, {0, 0, 0, 0} }; int option; int option_index; enum { ALL, USERNAME, PASSWORD, URL, FIELD, ID, NAME, NOTES, ATTACH } choice = ALL; _cleanup_free_ char *field = NULL; struct account *notes_expansion = NULL; struct field *found_field; char *name, *pretty_field; struct account *found, *last_found, *account; struct app *app; enum blobsync sync = BLOB_SYNC_AUTO; bool clip = false; bool json = false; bool expand_multi = false; bool quiet = false; struct list_head matches, potential_set; enum search_type search = SEARCH_EXACT_MATCH; int fields = ACCOUNT_NAME | ACCOUNT_ID | ACCOUNT_FULLNAME; struct attach *attach; _cleanup_free_ char *title_format = NULL; _cleanup_free_ char *field_format = NULL; _cleanup_free_ char *attach_id = NULL; while ((option = getopt_long(argc, argv, "cupFGxtoqj", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'A': choice = ALL; break; case 'u': choice = USERNAME; break; case 'p': choice = PASSWORD; break; case 'L': choice = URL; break; case 'f': choice = FIELD; field = xstrdup(optarg); break; case 'G': search = SEARCH_BASIC_REGEX; break; case 'F': search = SEARCH_FIXED_SUBSTRING; break; case 'I': choice = ID; break; case 'N': choice = NAME; break; case 'j': json = true; break; case 'a': choice = ATTACH; attach_id = xstrdup(optarg); break; case 'O': choice = NOTES; break; case 'c': clip = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case 'x': expand_multi = true; break; case 'o': field_format = xstrdup(optarg); break; case 't': title_format = xstrdup(optarg); break; case 'q': quiet = true; break; case '?': default: die_usage(cmd_show_usage); } } if (argc - optind < 1) die_usage(cmd_show_usage); if (argc - optind > 1) { /* * if multiple search criteria supplied, go ahead * and expand all matches */ expand_multi = true; } init_all(sync, key, &session, &blob); INIT_LIST_HEAD(&matches); INIT_LIST_HEAD(&potential_set); if (!title_format) { title_format = xstrdup( TERMINAL_FG_CYAN "%/as" TERMINAL_RESET TERMINAL_FG_BLUE "%/ag" TERMINAL_BOLD "%an" TERMINAL_RESET TERMINAL_FG_GREEN " [id: %ai]" TERMINAL_RESET); } if (!field_format) { field_format = xstrdup( TERMINAL_FG_YELLOW "%fn" TERMINAL_RESET ": %fv"); } list_for_each_entry(account, &blob->account_head, list) list_add(&account->match_list, &potential_set); for (; optind < argc; optind++) { name = argv[optind]; switch (search) { case SEARCH_EXACT_MATCH: find_matching_accounts(&potential_set, name, &matches); break; case SEARCH_BASIC_REGEX: find_matching_regex(&potential_set, name, fields, &matches); break; case SEARCH_FIXED_SUBSTRING: find_matching_substr(&potential_set, name, fields, &matches); break; } } if (list_empty(&matches)) die("Could not find specified account(s)."); found = list_first_entry(&matches, struct account, match_list); last_found = list_last_entry(&matches, struct account, match_list); if (found != last_found && !expand_multi) { /* Multiple matches; dump the ids and exit */ terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "Multiple matches found.\n"); list_for_each_entry(found, &matches, match_list) print_header(title_format, found); exit(EXIT_SUCCESS); } /* reprompt if necessary for any matched item */ list_for_each_entry(found, &matches, match_list) { if (found->pwprotect) { unsigned char pwprotect_key[KDF_HASH_LEN]; if (!agent_load_key(pwprotect_key)) die("Could not authenticate for protected entry."); if (memcmp(pwprotect_key, key, KDF_HASH_LEN)) die("Current key is not on-disk key."); break; } } if (clip) clipboard_open(); if (json) { json_format_account_list(&matches); goto done; } list_for_each_entry(account, &matches, match_list) { found = account; lastpass_log_access(sync, session, key, found); notes_expansion = notes_expand(found); if (notes_expansion) found = notes_expansion; if (choice == FIELD) { bool has_field = false; list_for_each_entry(found_field, &found->field_head, list) { if (!strcmp(found_field->name, field)) { has_field = true; break; } } if (!has_field) die("Could not find specified field '%s'.", field); value = pretty_field_value(found_field); } else if (choice == USERNAME) value = xstrdup(found->username); else if (choice == PASSWORD) value = xstrdup(found->password); else if (choice == URL) value = xstrdup(found->url); else if (choice == ID) value = xstrdup(found->id); else if (choice == NAME) value = xstrdup(found->name); else if (choice == NOTES) value = xstrdup(found->note); else if (choice == ATTACH) { struct attach *attach = find_attachment(found, attach_id); if (!attach) die("Could not find specified attachment '%s'.", attach_id); show_attachment(session, found, attach, quiet); } if (choice == ALL) { print_header(title_format, found); if (strlen(found->username)) print_field(field_format, found, "Username", found->username); if (strlen(found->password)) print_field(field_format, found, "Password", found->password); if (strlen(found->url) && strcmp(found->url, "http://")) print_field(field_format, found, "URL", found->url); if (found->is_app) { app = account_to_app(found); if (strlen(app->appname)) print_field(field_format, found, "Application", app->appname); } list_for_each_entry(found_field, &found->field_head, list) { pretty_field = pretty_field_value(found_field); print_field(field_format, found, found_field->name, pretty_field); free(pretty_field); } 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) { if (!value) die("Programming error."); printf("%s", value); if (!clip) putchar('\n'); } account_free(notes_expansion); } done: session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd-status.c000066400000000000000000000054301462143212600163470ustar00rootroot00000000000000/* * command to get the status of the LastPass agent * * Copyright (C) 2014-2018 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 "agent.h" #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "upload-queue.h" #include #include #include #include int cmd_status(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; static struct option long_options[] = { {"quiet", no_argument, NULL, 'q'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; bool quiet = false; _cleanup_free_ char *username = NULL; while ((option = getopt_long(argc, argv, "q", long_options, &option_index)) != -1) { switch (option) { case 'q': quiet = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_status_usage); } } if (!agent_ask(key)) { if(!quiet) { terminal_printf(TERMINAL_FG_RED TERMINAL_BOLD "Not logged in" TERMINAL_RESET ".\n"); } return 1; } else { if(!quiet) { username = config_read_string("username"); terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "Logged in" TERMINAL_RESET " as " TERMINAL_UNDERLINE "%s" TERMINAL_RESET ".\n", username); } return 0; } } lastpass-cli-1.5.0/cmd-sync.c000066400000000000000000000052051462143212600160000ustar00rootroot00000000000000/* * command to synchronize with LastPass servers * * Copyright (C) 2014-2018 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "upload-queue.h" #include #include #include #include int cmd_sync(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"background", no_argument, NULL, 'b'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; bool background = false; while ((option = getopt_long(argc, argv, "b", long_options, &option_index)) != -1) { switch (option) { case 'b': background = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_sync_usage); } } init_all(0, key, &session, NULL); upload_queue_ensure_running(key, session); if (!background) { while (upload_queue_is_running()) usleep(1000000 / 3); } session_free(session); blob_free(blob); return 0; } lastpass-cli-1.5.0/cmd.c000066400000000000000000000164461462143212600150370ustar00rootroot00000000000000/* * general utility functions used by multiple commands * * Copyright (C) 2014-2018 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 "cmd.h" #include "agent.h" #include "blob.h" #include "session.h" #include "util.h" #include "process.h" #include #include #include enum blobsync parse_sync_string(const char *syncstr) { if (!syncstr || !strcasecmp(syncstr, "auto")) return BLOB_SYNC_AUTO; else if (!strcasecmp(syncstr, "now")) return BLOB_SYNC_YES; else if (!strcasecmp(syncstr, "no")) return BLOB_SYNC_NO; else die_usage("... --sync=auto|now|no"); } enum color_mode parse_color_mode_string(const char *colormode) { if (!colormode || strcmp(colormode, "auto") == 0) return COLOR_MODE_AUTO; else if (strcmp(colormode, "never") == 0) return COLOR_MODE_NEVER; else if (strcmp(colormode, "always") == 0) return COLOR_MODE_ALWAYS; else die_usage("... --color=auto|never|always"); } bool parse_bool_arg_string(const char *extra) { return !extra || strcmp(extra, "true") == 0; } enum note_type parse_note_type_string(const char *extra) { enum note_type result; result = notes_get_type_by_shortname(extra); if (result == NOTE_TYPE_NONE) { _cleanup_free_ char *params = NULL; _cleanup_free_ char *usage = NULL; params = note_type_usage(); xasprintf(&usage, "... %s", params); die_usage(usage); } return result; } void init_all(enum blobsync sync, unsigned char key[KDF_HASH_LEN], struct session **session, struct blob **blob) { if (!agent_get_decryption_key(key)) die("Could not find decryption key. Perhaps you need to login with `%s login`.", ARGV[0]); *session = session_load(key); if (!*session) die("Could not find session. Perhaps you need to login with `%s login`.", ARGV[0]); if (blob) { *blob = blob_load(sync, *session, key); if (!*blob) die("Unable to fetch blob. Either your session is invalid and you need to login with `%s login`, you need to synchronize, your blob is empty, or there is something wrong with your internet connection.", ARGV[0]); } } /* * cmp_regex - do regex comparison with a basic regex */ static int cmp_regex(const char *haystack, const char *needle) { return regexec((void *) needle, haystack, 0, NULL, 0); } /* * cmp_substr - do substring comparison with a fixed pattern */ static int cmp_substr(const char *haystack, const char *needle) { return strstr(haystack, needle) == NULL; } /* * Search accounts with a given comparator. * * Any matched account is removed from the accounts list, and added to * ret_list. * * Note, the account list is iterated through match_list, so the caller * must first create a list of possible matches (from blob->account_head). * This is done instead of searching blob->account_head directly to enable * multiple searches of the potential match set. */ static void search_accounts(struct list_head *accounts, const void *needle, int (*cmp)(const char *haystack, const char *needle), int fields, struct list_head *ret_list) { struct account *account, *tmp; list_for_each_entry_safe(account, tmp, accounts, match_list) { if (((fields & ACCOUNT_ID) && cmp(account->id, needle) == 0) || ((fields & ACCOUNT_NAME) && cmp(account->name, needle) == 0) || ((fields & ACCOUNT_FULLNAME) && cmp(account->fullname, needle) == 0) || ((fields & ACCOUNT_URL) && cmp(account->url, needle) == 0) || ((fields & ACCOUNT_USERNAME) && cmp(account->username, needle) == 0)) { list_del(&account->match_list); list_add_tail(&account->match_list, ret_list); } } } /* * Search accounts on given fields, returning results into ret_list. * * @pattern - a basic regular expression * @fields - which fields to search on */ void find_matching_regex(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list) { regex_t regex; if (regcomp(®ex, pattern, REG_ICASE)) die("Invalid regex '%s'", pattern); search_accounts(accounts, ®ex, cmp_regex, fields, ret_list); regfree(®ex); } /* * Search accounts on name, username, and url fields, adding all matches * into ret_list. * * @pattern - a basic regular expression * @fields - which fields to search on */ void find_matching_substr(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list) { search_accounts(accounts, pattern, cmp_substr, fields, ret_list); } /* * Search list of accounts for any and all accounts matching a given name. * Matching accounts are appended to ret_list which should be initialized * by the caller. * * In the case of an id match, we return only the matching id entry. */ void find_matching_accounts(struct list_head *accounts, const char *name, struct list_head *ret_list) { /* look for exact id match */ struct account *account; list_for_each_entry(account, accounts, match_list) { if (strcmp(name, "0") && !strcasecmp(account->id, name)) { list_del(&account->match_list); list_add_tail(&account->match_list, ret_list); /* if id match, stop processing */ return; } } /* search for fullname or name match */ search_accounts(accounts, name, strcmp, ACCOUNT_NAME | ACCOUNT_FULLNAME, ret_list); } struct account *find_unique_account(struct blob *blob, const char *name) { struct list_head matches; struct list_head potential_set; struct account *account, *last_account; INIT_LIST_HEAD(&matches); INIT_LIST_HEAD(&potential_set); list_for_each_entry(account, &blob->account_head, list) list_add(&account->match_list, &potential_set); find_matching_accounts(&potential_set, name, &matches); if (list_empty(&matches)) return NULL; account = list_first_entry(&matches, struct account, match_list); last_account = list_last_entry(&matches, struct account, match_list); if (account != last_account) die("Multiple matches found for '%s'. You must specify an ID instead of a name.", name); return account; } lastpass-cli-1.5.0/cmd.h000066400000000000000000000100061462143212600150260ustar00rootroot00000000000000#ifndef CMD_H #define CMD_H #include "blob.h" #include "session.h" #include "terminal.h" #include "kdf.h" enum search_type { SEARCH_EXACT_MATCH, SEARCH_BASIC_REGEX, SEARCH_FIXED_SUBSTRING, }; #define BIT(x) (1ull << (x)) enum account_field { ACCOUNT_ID = BIT(0), ACCOUNT_NAME = BIT(1), ACCOUNT_FULLNAME = BIT(2), ACCOUNT_URL = BIT(3), ACCOUNT_USERNAME = BIT(4), }; enum edit_choice { EDIT_NONE, EDIT_USERNAME, EDIT_PASSWORD, EDIT_URL, EDIT_FIELD, EDIT_NAME, EDIT_NOTES, EDIT_ANY }; void init_all(enum blobsync sync, unsigned char key[KDF_HASH_LEN], struct session **session, struct blob **blob); enum blobsync parse_sync_string(const char *str); struct account *find_unique_account(struct blob *blob, const char *name); void find_matching_accounts(struct list_head *accounts, const char *name, struct list_head *ret_list); void find_matching_regex(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list); void find_matching_substr(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list); enum color_mode parse_color_mode_string(const char *colormode); bool parse_bool_arg_string(const char *extra); enum note_type parse_note_type_string(const char *extra); int edit_account(struct session *session, struct blob *blob, enum blobsync sync, struct account *editable, enum edit_choice choice, const char *field, bool non_interactive, unsigned char key[KDF_HASH_LEN]); int edit_new_account(struct session *session, struct blob *blob, enum blobsync sync, const char *name, enum edit_choice choice, const char *field, bool non_interactive, bool is_app, enum note_type note_type, unsigned char key[KDF_HASH_LEN]); #define color_usage "[--color=auto|never|always]" int cmd_login(int argc, char **argv); #define cmd_login_usage "login [--trust] [--plaintext-key [--force, -f]] " color_usage " USERNAME" int cmd_logout(int argc, char **argv); #define cmd_logout_usage "logout [--force, -f] " color_usage int cmd_passwd(int argc, char **argv); #define cmd_passwd_usage "passwd" int cmd_show(int argc, char **argv); #define cmd_show_usage "show [--sync=auto|now|no] [--clip, -c] [--quiet, -q] [--expand-multi, -x] [--json, -j] [--all|--username|--password|--url|--notes|--field=FIELD|--id|--name|--attach=ATTACHID] [--basic-regexp, -G|--fixed-strings, -F] " color_usage " {UNIQUENAME|UNIQUEID}" int cmd_ls(int argc, char **argv); #define cmd_ls_usage "ls [--sync=auto|now|no] [--long, -l] [-m] [-u] " color_usage " [GROUP]" int cmd_add(int argc, char **argv); #define cmd_add_usage "add [--sync=auto|now|no] [--non-interactive] " color_usage " {--username|--password|--url|--notes|--field=FIELD|--note-type=NOTETYPE} NAME" int cmd_edit(int argc, char **argv); #define cmd_edit_usage "edit [--sync=auto|now|no] [--non-interactive] " color_usage " {--name|--username|--password|--url|--notes|--field=FIELD} {NAME|UNIQUEID}" int cmd_generate(int argc, char **argv); #define cmd_generate_usage "generate [--sync=auto|now|no] [--clip, -c] [--username=USERNAME] [--url=URL] [--no-symbols] {NAME|UNIQUEID} LENGTH" int cmd_duplicate(int argc, char **argv); #define cmd_duplicate_usage "duplicate [--sync=auto|now|no] " color_usage " {UNIQUENAME|UNIQUEID}" int cmd_rm(int argc, char **argv); #define cmd_rm_usage "rm [--sync=auto|now|no] " color_usage " {UNIQUENAME|UNIQUEID}" int cmd_status(int argc, char **argv); #define cmd_status_usage "status [--quiet, -q] " color_usage int cmd_sync(int argc, char **argv); #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 " [--fields=FIELDLIST]" int cmd_share(int argc, char **argv); #define cmd_share_usage "share subcommand sharename ..." #endif int cmd_mv(int argc, char **argv); #define cmd_mv_usage "mv " color_usage " {UNIQUENAME|UNIQUEID} GROUP" int cmd_import(int argc, char **argv); #define cmd_import_usage "import [--keep-dupes] [CSV_FILENAME]" lastpass-cli-1.5.0/config.c000066400000000000000000000273371462143212600155420ustar00rootroot00000000000000/* * configuration file handling * * Copyright (C) 2014-2018 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 "config.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include /* * Map well-known pathnames to their configuration type. */ struct pathname_type_tuple { char *name; enum config_type type; }; struct pathname_type_tuple pathname_type_lookup[] = { { "env", CONFIG_CONFIG }, { "blob", CONFIG_DATA }, { "iterations", CONFIG_DATA }, { "username", CONFIG_DATA }, { "verify", CONFIG_DATA }, { "plaintext_key", CONFIG_DATA }, { "trusted_id", CONFIG_DATA }, { "session_uid", CONFIG_DATA }, { "session_sessionid", CONFIG_DATA }, { "session_token", CONFIG_DATA }, { "session_privatekey", CONFIG_DATA }, { "session_server", CONFIG_DATA }, { "lpass.log", CONFIG_DATA }, { "agent.sock", CONFIG_RUNTIME }, { "uploader.pid", CONFIG_RUNTIME }, }; char *config_type_to_xdg[] = { [CONFIG_DATA] = "XDG_DATA_HOME", [CONFIG_CONFIG] = "XDG_CONFIG_HOME", [CONFIG_RUNTIME] = "XDG_RUNTIME_DIR", }; static char *get_xdg_dir(const char *xdg_var) { char *home; char *retstr = NULL; if (getenv(xdg_var)) return xstrdup(getenv(xdg_var)); /* * $XDG var not set in environment; decide whether * to use backups locations based on existence of * $XDG_RUNTIME_DIR. */ if (!getenv("XDG_RUNTIME_DIR")) return NULL; home = getenv("HOME"); if (!home) return NULL; if (!strcmp(xdg_var, "XDG_DATA_HOME")) xasprintf(&retstr, "%s/.local/share", home); else if (!strcmp(xdg_var, "XDG_CONFIG_HOME")) xasprintf(&retstr, "%s/.config", home); return retstr; } /* * Get the path to a config file given its name and the type of file. * * lpass looks for files in the following directories: * * First, if $LPASS_HOME is set, everything goes there. * * After that, if it is a persistent, user-specific data file, * it goes in $XDG_DATA_HOME/lpass. * * If a configuration item, it goes in $XDG_CONFIG_HOME. * * If a purely runtime item (socket, pidfile, etc) it goes in * $XDG_RUNTIME_HOME. * * If none of the $XDG environment variables are set, fall-back * to ~/.lpass. */ static char *config_path_for_type(enum config_type type, const char *name) { char *home, *path, *xdg_env; _cleanup_free_ char *config = NULL; _cleanup_free_ char *xdg_dir = NULL; struct stat sbuf; int ret; xdg_env = config_type_to_xdg[type]; home = getenv("LPASS_HOME"); if (home) config = xstrdup(home); else if ((xdg_dir = get_xdg_dir(xdg_env))) { xasprintf(&config, "%s/lpass", xdg_dir); } else { home = getenv("HOME"); if (!home) die("HOME is not set"); xasprintf(&config, "%s/.lpass", home); } ret = stat(config, &sbuf); if ((ret == -1 && errno == ENOENT) || !S_ISDIR(sbuf.st_mode)) { unlink(config); if (mkdir(config, 0700) < 0) die_errno("mkdir(%s)", config); } else if (ret == -1) die_errno("stat(%s)", config); _cleanup_free_ char *buffer = xstrdup(name); _cleanup_free_ char *dir_path = xstrdup(config); char *saveptr = NULL; for (char *token = strtok_r(buffer, "/", &saveptr); token && saveptr && strlen(saveptr) > 0; token = strtok_r(NULL, "/", &saveptr)) { xstrappendf(&dir_path, "/%s", token); ret = stat(dir_path, &sbuf); if ((ret == -1 && errno == ENOENT) || !S_ISDIR(sbuf.st_mode)) { unlink(dir_path); if (mkdir(dir_path, 0700) < 0) die_errno("mkdir(%s)", dir_path); } else if (ret == -1) die_errno("stat(%s)", dir_path); } xasprintf(&path, "%s/%s", config, name); return path; } enum config_type config_path_type(const char *name) { unsigned int i; /* aliases are config files */ if (!strncmp(name, "alias", 5)) { return CONFIG_CONFIG; } /* lock files are runtime */ if (strlen(name) >= 5 && !strcmp(name + strlen(name) - 5, ".lock")) { return CONFIG_RUNTIME; } /* categorized this configuration file by name? */ for (i=0; i < ARRAY_SIZE(pathname_type_lookup); i++) { if (!strcmp(name, pathname_type_lookup[i].name)) { return pathname_type_lookup[i].type; } } /* everything else is config_data */ return CONFIG_DATA; } char *config_path(const char *name) { return config_path_for_type(config_path_type(name), name); } FILE *config_fopen(const char *name, const char *mode) { _cleanup_free_ char *path = config_path(name); return fopen(path, mode); } void config_touch(const char *name) { _cleanup_free_ char *path = NULL; path = config_path(name); if (utime(path, NULL) < 0) die_errno("utime"); } bool config_exists(const char *name) { _cleanup_free_ char *path = NULL; struct stat sbuf; path = config_path(name); return stat(path, &sbuf) != -1; } time_t config_mtime(const char *name) { _cleanup_free_ char *path = NULL; struct stat sbuf; path = config_path(name); if (stat(path, &sbuf) < 0) return 0; return sbuf.st_mtime; } bool config_unlink(const char *name) { _cleanup_free_ char *path = config_path(name); return unlink(path) == 0; } void config_write_string(const char *name, const char *string) { config_write_buffer(name, string, strlen(string)); } void config_write_buffer(const char *name, const char *buffer, size_t len) { _cleanup_free_ char *tempname = NULL; _cleanup_free_ char *finalpath = config_path(name); int tempfd; FILE *tempfile = NULL; xasprintf(&tempname, "%s.XXXXXX", finalpath); tempfd = mkstemp(tempname); if (tempfd < 0) die_errno("mkstemp(%s)", tempname); tempfile = fdopen(tempfd, "w"); if (!tempfile) goto error; if (fwrite(buffer, 1, len, tempfile) != len) goto error; fclose(tempfile); tempfile = NULL; if (rename(tempname, finalpath) < 0) goto error; return; error: tempfd = errno; if (tempfile) fclose(tempfile); unlink(tempname); errno = tempfd; die_errno("config-%s", name); } char *config_read_string(const char *name) { _cleanup_free_ char *buffer = NULL; size_t len = config_read_buffer(name, (unsigned char **) &buffer); if (!buffer) return NULL; return xstrndup(buffer, len); } size_t config_read_buffer(const char *name, unsigned char **out) { _cleanup_fclose_ FILE *file = NULL; unsigned char *buffer; size_t len, read; file = config_fopen(name, "r"); if (!file) { *out = NULL; return 0; } for (len = 0, buffer = xmalloc(8192); ; buffer = xrealloc(buffer, len + 8192)) { read = fread(buffer + len, 1, 8192, file); len += read; if (read != 8192) { if (ferror(file)) die_errno("fread(config-%s)", name); break; } } *out = buffer; return len; } /* * ciphertext = IV | aes-256-cbc(plaintext, key) * authenticated-ciphertext = HMAC-SHA256(ciphertext, key) | ciphertext * * These two functions work with `authenticated-ciphertext`. */ static size_t encrypt_buffer(const char *buffer, size_t in_len, unsigned const char key[KDF_HASH_LEN], char **out) { EVP_CIPHER_CTX *ctx; char *ciphertext; unsigned char iv[AES_BLOCK_SIZE]; int out_len; unsigned int hmac_len; size_t len; if (!RAND_bytes(iv, AES_BLOCK_SIZE)) die("Could not generate random bytes for CBC IV."); ciphertext = xcalloc(in_len + AES_BLOCK_SIZE * 2 + SHA256_DIGEST_LENGTH, 1); ctx = EVP_CIPHER_CTX_new(); if (!ctx) goto error; len = SHA256_DIGEST_LENGTH; memcpy(ciphertext + len, iv, AES_BLOCK_SIZE); len += AES_BLOCK_SIZE; if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) goto error; if (!EVP_EncryptUpdate(ctx, (unsigned char *)(ciphertext + len), &out_len, (unsigned char *)buffer, in_len)) goto error; len += out_len; if (!EVP_EncryptFinal_ex(ctx, (unsigned char *)(ciphertext + len), &out_len)) goto error; len += out_len; if (!HMAC(EVP_sha256(), key, KDF_HASH_LEN, (unsigned char *)(ciphertext + SHA256_DIGEST_LENGTH), len - SHA256_DIGEST_LENGTH, (unsigned char *)ciphertext, &hmac_len)) goto error; EVP_CIPHER_CTX_free(ctx); *out = ciphertext; return len; error: EVP_CIPHER_CTX_free(ctx); free(ciphertext); die("Failed to encrypt data."); } static size_t decrypt_buffer(const unsigned char *buffer, size_t in_len, unsigned const char key[KDF_HASH_LEN], unsigned char **out) { EVP_CIPHER_CTX *ctx; unsigned char *plaintext = NULL; int out_len; unsigned int hmac_len; size_t len; unsigned char hmac[SHA256_DIGEST_LENGTH]; ctx = EVP_CIPHER_CTX_new(); if (!ctx) goto error; if (in_len < (SHA256_DIGEST_LENGTH + AES_BLOCK_SIZE * 2)) goto error; if (!HMAC(EVP_sha256(), key, KDF_HASH_LEN, (unsigned char *)(buffer + SHA256_DIGEST_LENGTH), in_len - SHA256_DIGEST_LENGTH, hmac, &hmac_len)) goto error; if (CRYPTO_memcmp(hmac, buffer, SHA256_DIGEST_LENGTH)) goto error; plaintext = xcalloc(in_len + AES_BLOCK_SIZE, 1); if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, (unsigned char *)(buffer + SHA256_DIGEST_LENGTH))) goto error; if (!EVP_DecryptUpdate(ctx, (unsigned char *)plaintext, &out_len, (unsigned char *)(buffer + SHA256_DIGEST_LENGTH + AES_BLOCK_SIZE), in_len - SHA256_DIGEST_LENGTH - AES_BLOCK_SIZE)) goto error; len = out_len; if (!EVP_DecryptFinal_ex(ctx, (unsigned char *)(plaintext + out_len), &out_len)) goto error; len += out_len; EVP_CIPHER_CTX_free(ctx); *out = plaintext; return len; error: EVP_CIPHER_CTX_free(ctx); free(plaintext); *out = NULL; return 0; } void config_write_encrypted_string(const char *name, const char *string, unsigned const char key[KDF_HASH_LEN]) { config_write_encrypted_buffer(name, string, strlen(string), key); } void config_write_encrypted_buffer(const char *name, const char *buffer, size_t len, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *encrypted_buffer = NULL; len = encrypt_buffer(buffer, len, key, &encrypted_buffer); config_write_buffer(name, encrypted_buffer, len); } char *config_read_encrypted_string(const char *name, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *buffer = NULL; size_t len = config_read_encrypted_buffer(name, (unsigned char **) &buffer, key); if (!buffer) return NULL; return xstrndup(buffer, len); } size_t config_read_encrypted_buffer(const char *name, unsigned char **buffer, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ unsigned char *encrypted_buffer = NULL; size_t len; len = config_read_buffer(name, &encrypted_buffer); if (!encrypted_buffer) { *buffer = NULL; return 0; } return decrypt_buffer(encrypted_buffer, len, key, buffer); } lastpass-cli-1.5.0/config.h000066400000000000000000000021701462143212600155330ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H #include "kdf.h" #include #include #include enum config_type { CONFIG_DATA, CONFIG_CONFIG, CONFIG_RUNTIME, }; char *config_path(const char *name); FILE *config_fopen(const char *name, const char *mode); bool config_exists(const char *name); bool config_unlink(const char *name); time_t config_mtime(const char *name); void config_touch(const char *name); void config_write_string(const char *name, const char *string); void config_write_buffer(const char *name, const char *buffer, size_t len); char *config_read_string(const char *name); size_t config_read_buffer(const char *name, unsigned char **buffer); void config_write_encrypted_string(const char *name, const char *string, unsigned const char key[KDF_HASH_LEN]); void config_write_encrypted_buffer(const char *name, const char *buffer, size_t len, unsigned const char key[KDF_HASH_LEN]); char *config_read_encrypted_string(const char *name, unsigned const char key[KDF_HASH_LEN]); size_t config_read_encrypted_buffer(const char *name, unsigned char **buffer, unsigned const char key[KDF_HASH_LEN]); #endif lastpass-cli-1.5.0/contrib/000077500000000000000000000000001462143212600155555ustar00rootroot00000000000000lastpass-cli-1.5.0/contrib/Dockerfile000066400000000000000000000013701462143212600175500ustar00rootroot00000000000000FROM debian:testing-slim as build ENV DEBIAN_FRONTEND=noninteractive RUN mkdir -p /tmp/build WORKDIR /tmp/build COPY . /tmp/build/ RUN apt-get update \ && apt-get --no-install-recommends -yqq install \ # Build dependencies build-essential \ cmake \ libcurl4-openssl-dev \ libssl-dev \ libxml2-dev \ pkg-config \ # Run time dependencies libcurl4 \ libssl1.1 \ libxml2 \ # Optionals handy for testing within the container bash-completion \ ca-certificates \ xclip \ && make \ && make test \ && make install \ && apt-get autoremove --purge -yqq \ bash-completion \ libcurl4-openssl-dev \ libssl-dev \ libxml2-dev \ pkg-config \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/build lastpass-cli-1.5.0/contrib/Dockerfile.Debian-stable000066400000000000000000000013751462143212600222060ustar00rootroot00000000000000FROM debian:stable-slim as build ENV DEBIAN_FRONTEND=noninteractive RUN mkdir -p /tmp/build WORKDIR /tmp/build COPY . /tmp/build/ RUN apt-get update \ && apt-get --no-install-recommends -yqq install \ # Build dependencies build-essential \ cmake \ libcurl3-openssl-dev \ libssl1.0-dev \ libxml2-dev \ pkg-config \ # Run time dependencies libcurl3 \ libssl1.0 \ libxml2 \ # Optionals handy for testing within the container bash-completion \ ca-certificates \ xclip \ && make \ && make test \ && make install \ && apt-get autoremove --purge -yqq \ bash-completion \ libcurl3-openssl-dev \ libssl1.0-dev \ libxml2-dev \ pkg-config \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/build lastpass-cli-1.5.0/contrib/Dockerfile.dev000066400000000000000000000010271462143212600203240ustar00rootroot00000000000000FROM debian:testing-slim ENV DEBIAN_FRONTEND=noninteractive RUN mkdir -p /tmp/build WORKDIR /tmp/build RUN apt-get update \ && apt-get --no-install-recommends -y install \ # Build dependencies build-essential \ cmake \ libcurl4-openssl-dev \ libssl-dev \ libxml2-dev \ pkg-config \ # Run time dependencies libssl1.1 \ libcurl4 \ libxml2 \ # Optionals handy for testing within the container bash-completion \ ca-certificates \ xclip COPY . /tmp/build/ RUN make \ && make test \ && make install lastpass-cli-1.5.0/contrib/completions-lpass.fish000066400000000000000000000136031462143212600221070ustar00rootroot00000000000000# fish-shell completion for lastpass-cli # # for single-user installation, copy this file to # ~/.config/fish/completions/lpass.fish function __lpass_entries lpass ls --sync auto --color never \ | string replace -r '^(\(none\)/)?(.*)' '$2' \ | string replace -r '^ \[id: (\d+)\]$' '$1' \ | string replace -r '^(.*) \[id: \d+\]$' '$1' end function __lpass_needs_command set cmd (commandline -opc) test (count $cmd) -eq 1 end function __lpass_using_command set cmd (commandline -opc) test (count $cmd) -gt 1 and contains -- $cmd[2] $argv end complete -f -c lpass -l help -n '__lpass_needs_command' -d 'Print usage' complete -f -c lpass -l version -n '__lpass_needs_command' -d 'Print version' # Commands complete -f -c lpass -n '__lpass_needs_command' -a add \ -d 'Add entry' complete -f -c lpass -n '__lpass_needs_command' -a duplicate \ -d 'Duplicate password' complete -f -c lpass -n '__lpass_needs_command' -a edit \ -d 'Edit entry' complete -f -c lpass -n '__lpass_needs_command' -a export \ -d 'Export passwords as CSV' complete -f -c lpass -n '__lpass_needs_command' -a generate \ -d 'Create a new entry with a generated password' complete -f -c lpass -n '__lpass_needs_command' -a import \ -d 'Import CSV as passwords' complete -f -c lpass -n '__lpass_needs_command' -a login \ -d 'Login to LastPass' complete -f -c lpass -n '__lpass_needs_command' -a logout \ -d 'Logout from LastPass' complete -f -c lpass -n '__lpass_needs_command' -a ls \ -d 'List entries' complete -f -c lpass -n '__lpass_needs_command' -a mv \ -d 'Move entry to group' complete -f -c lpass -n '__lpass_needs_command' -a passwd \ -d 'Change your LastPass master password' complete -f -c lpass -n '__lpass_needs_command' -a rm \ -d 'Remove entry' complete -f -c lpass -n '__lpass_needs_command' -a share \ -d 'Perform operations on a share' complete -f -c lpass -n '__lpass_needs_command' -a show \ -d 'Show entry details' complete -f -c lpass -n '__lpass_needs_command' -a status \ -d 'Show status' complete -f -c lpass -n '__lpass_needs_command' -a sync \ -d 'Synchronize local cache with server' # {UNIQUENAME|UNIQUEID} complete -f -c lpass \ -n '__lpass_using_command show mv edit generate duplicate rm' \ -a '(__lpass_entries)' # --all complete -f -c lpass -n '__lpass_using_command show' \ -l all \ -d 'All fields' # --attach=ATTACHID complete -f -c lpass -n '__lpass_using_command show' \ -l attach \ -d 'Attach' # --background -b complete -f -c lpass -n '__lpass_using_command sync' \ -s b -l background \ -d 'Synchronize in background' # --basic-regexp -G complete -f -c lpass -n '__lpass_using_command show' \ -s G -l basic-regexp \ -d 'Search with regular expression' # --clip -c complete -f -c lpass -n '__lpass_using_command show generate' \ -s c -l clip \ -d 'Copy output to clipboard' # --color=COLOR complete -f -c lpass \ -n '__lpass_using_command login logout show ls mv add edit duplicate rm sync export status' \ -r -l color \ -a 'auto never always' \ -d 'When to use colors' # --expand-multi complete -f -c lpass -n '__lpass_using_command show' \ -s x -l expand-multi \ -d 'Expand multi' # --field=FIELD complete -f -c lpass -n '__lpass_using_command show add edit' \ -r -l field \ -d 'Custom field' # --fields=FIELDLIST complete -f -c lpass -n '__lpass_using_command export' \ -r -l fields \ -d 'Field list' # --fixed-strings -F complete -f -c lpass -n '__lpass_using_command show' \ -s F -l fixed-strings \ -d 'Search substrings' # --force -f complete -f -c lpass -n '__lpass_using_command login logout' \ -s f -l force \ -d 'Do not ask for confirmation' # --format=FMTSTR complete -f -c lpass -n '__lpass_using_command show ls' \ -l format \ -d 'Format string' # --id complete -f -c lpass -n '__lpass_using_command show' \ -l id \ -d 'ID' # --long -l complete -f -c lpass -n '__lpass_using_command ls' \ -s l -l long \ -d 'More info' # -m complete -f -c lpass -n '__lpass_using_command ls' \ -s m \ -d 'Modified time' # --name complete -f -c lpass -n '__lpass_using_command edit show' \ -l name \ -d 'Name' # --non-interactive complete -f -c lpass -n '__lpass_using_command add edit' \ -l non-interactive \ -d 'Use standard input instead of $EDITOR' # --no-symbols complete -f -c lpass -n '__lpass_using_command generate' \ -l no-symbols \ -d 'No symbols' # --note-type=NOTETYPE complete -f -c lpass -n '__lpass_using_command add' \ -r -l note-type \ -d 'Note type' # --notes complete -f -c lpass -n '__lpass_using_command show add edit' \ -l notes \ -d 'Notes' # --password complete -f -c lpass -n '__lpass_using_command show add edit' \ -l password \ -d 'Password' # --plaintext-key complete -f -c lpass -n '__lpass_using_command login' \ -l plaintext-key \ -d 'Store key in plain text' # --quiet -q complete -f -c lpass -n '__lpass_using_command status' \ -s q -l quiet \ -d 'No output' # --sync=SYNC complete -f -c lpass \ -n '__lpass_using_command show ls add edit generate duplicate rm export import' \ -r -l sync \ -a 'auto now no' \ -d 'Synchronize local cache with server' # --trust complete -f -c lpass -n '__lpass_using_command login' \ -l trust \ -d 'Do not require multifactor authentication for next logins' # -u complete -f -c lpass -n '__lpass_using_command ls' \ -s u \ -d 'Last used time' # --url=URL complete -f -c lpass -n '__lpass_using_command generate' \ -r -l url \ -d 'URL' # --url complete -f -c lpass -n '__lpass_using_command show add edit' \ -l url \ -d 'URL' # --username complete -f -c lpass -n '__lpass_using_command show add edit' \ -l username \ -d 'Username' # --username=USERNAME complete -f -c lpass -n '__lpass_using_command generate' \ -r -l username \ -d 'Username' lastpass-cli-1.5.0/contrib/examples/000077500000000000000000000000001462143212600173735ustar00rootroot00000000000000lastpass-cli-1.5.0/contrib/examples/change-mysql-password.sh000077500000000000000000000021341462143212600241620ustar00rootroot00000000000000#!/bin/bash # # Changes MySQL passwords. # # Copyright (c) 2014 LastPass. # if [[ $# != 1 ]]; then echo "Usage: $0 hostname" exit 1 fi hostname="$1" username="$(lpass show --username "$hostname")" password="$(lpass show --password "$hostname")" if [[ -z $username || -z $password || -z $hostname ]]; then echo "Could not fetch credentials." exit 1 fi temporary_password_name="temporary-passwords/${hostname}_$RANDOM$RANDOM$RANDOM" number_of_characters="$(shuf -i 15-30 -n 1)" new_password="$(lpass generate "$temporary_password_name" "$number_of_characters")" if [[ -z $new_password ]]; then echo "Could not generate new password." exit 1 fi lpass sync if ! mysqladmin -h "$hostname" -u "$username" "-p$password" password "$new_password"; then lpass rm "$temporary_password_name" echo "Failed to change password for ${hostname}." exit 1 fi if ! lpass edit --non-interactive --password "$hostname" <<<"$new_password"; then echo "Warning: could not change password of $hostname entry. Current password lives in ${temporary_password_name}." exit 1 fi lpass sync lpass rm "$temporary_password_name" lastpass-cli-1.5.0/contrib/examples/change-ssh-password.sh000077500000000000000000000043431462143212600236160ustar00rootroot00000000000000#!/bin/bash # # Changes unix passwords by sshing in and calling passwd. # # Copyright (c) 2014 LastPass. # if [[ $# != 1 ]]; then echo "Usage: $0 hostname" exit 1 fi change_password() { HOST="$2@$1" OLD_PASSWORD="$3" NEW_PASSWORD="$4" expect <<-_EOF set timeout 15 set old_password \$env(OLD_PASSWORD) set new_password \$env(NEW_PASSWORD) set host \$env(HOST) spawn ssh "\$host" expect { "(yes/no)?" { send_user "Host key is not recognized. Exiting early.\n" send "no\\n" exit 1 } "assword:" { send -- "\$old_password\n" } } expect { "assword:" { send_user "Invalid password.\n" exit 1 } -re "\\\$|#" { send "passwd\n" expect "assword:" send -- "\$old_password\n" expect { "failure" { send_user "Old password did not work.\n" exit 1 } "assword:" { send -- "\$new_password\n" } } expect { "BAD PASSWORD" { send_user "Bad password.\n" exit 1 } "unchanged" { send_user "New password is not new.\n" exit 1 } "assword:" { send "\$new_password\n" } } expect { "successfully" { send_user "Password successfully updated.\n" exit 0 } default { send_user "Could not update password.\n" exit 1 } } } } exit 1 _EOF return $? } hostname="$1" username="$(lpass show --username "$hostname")" password="$(lpass show --password "$hostname")" if [[ -z $username || -z $password || -z $hostname ]]; then echo "Could not fetch credentials." exit 1 fi temporary_password_name="temporary-passwords/${hostname}_$RANDOM$RANDOM$RANDOM" number_of_characters="$(shuf -i 15-30 -n 1)" new_password="$(lpass generate "$temporary_password_name" "$number_of_characters")" if [[ -z $new_password ]]; then echo "Could not generate new password." exit 1 fi lpass sync if ! change_password "$hostname" "$username" "$password" "$new_password"; then lpass rm "$temporary_password_name" echo "Failed to change password for ${hostname}." exit 1 fi if ! lpass edit --non-interactive --password "$hostname" <<<"$new_password"; then echo "Warning: could not change password of $hostname entry. Current password lives in ${temporary_password_name}." exit 1 fi lpass sync lpass rm "$temporary_password_name" lastpass-cli-1.5.0/contrib/examples/git-credential-lastpass000077500000000000000000000022631462143212600240470ustar00rootroot00000000000000#!/bin/bash # A credential helper for git to retrieve usernames and passwords from lastpass. # For general usage, see https://git-scm.com/docs/gitcredentials. # Here's a quick version: # 1. Put this somewhere in your path. # 2. git config --global credential.helper lastpass declare -A params if [ "x$1" == "x-l" ]; then shift lpassuser=$1 shift fi if [ "x$1" == "xget" ]; then read line while [ -n "$line" ]; do key=${line%%=*} value=${line#*=} params[$key]=$value read line done if [ "x${params['protocol']}" != "xhttps" ]; then exit fi if [ -z "${params["host"]}" ]; then exit fi lpass ls > /dev/null 2>&1 if [ $? -ne 0 ]; then if [ -z "$lpassuser" ]; then read -p "Lastpass username: " lpassuser < /dev/tty > /dev/tty fi if [ -z "$lpassuser" ]; then exit fi lpass login $lpassuser > /dev/null if [ $? -ne 0 ]; then echo "Failed to login to lastpass" > /dev/stderr exit fi fi user=`lpass show --username ${params["host"]}` pass=`lpass show --password ${params["host"]}` if [ "x$user" == "x" ] || [ "x$pass" == "x" ]; then echo "Couldn't find host in lastpass DB." > /dev/stderr exit fi echo username=$user echo password=$pass fi lastpass-cli-1.5.0/contrib/examples/lpass-sudo-askpass.sh000077500000000000000000000004211462143212600234640ustar00rootroot00000000000000#!/bin/sh # # Tell sudo the user's password (based on hostname match). # See lpass-sudo for the caller that sets up the environ. # # Copyright (c) 2014 LastPass. # PREFIX=/usr/bin if [ -z "$HOSTNAME" ]; then HOSTNAME=`hostname` fi $PREFIX/lpass show --password $HOSTNAME lastpass-cli-1.5.0/contrib/examples/lpass-sudo.sh000077500000000000000000000002741462143212600220270ustar00rootroot00000000000000#!/bin/sh # # Run sudo using lpass-sudo-askpass # # Copyright (c) 2014 LastPass. # PREFIX=/usr/bin SUDO_ASKPASS=$PREFIX/lpass-sudo-askpass.sh export SUDO_ASKPASS exec $PREFIX/sudo -A "$@" lastpass-cli-1.5.0/contrib/examples/msmtprc000066400000000000000000000004251462143212600210040ustar00rootroot00000000000000defaults account default host companymailserver.example.com port 587 auth on user someuser passwordeval lpass Email/companysmtp/someuser tls on tls_fingerprint SOME_TLS_FINGERPRINT tls_certcheck on from someuser@example.com domain acomputer.example.com logfile ~/.msmtp.log lastpass-cli-1.5.0/contrib/lpass-att-export.sh000077500000000000000000000033321462143212600213440ustar00rootroot00000000000000#!/bin/bash ## ## Usage: lpass-att-export.sh ## ## usage() { echo "Usage: $0 [-l ] [-o ] [-i ]" 1>&2; exit 1; } while getopts ":i:o:hl:" o; do case "${o}" in i) id=${OPTARG} ;; o) outdir=${OPTARG} ;; l) email=${OPTARG} ;; h) usage ;; *) usage ;; esac done shift $((OPTIND-1)) if [ -z "${outdir}" ]; then usage fi command -v lpass >/dev/null 2>&1 || { echo >&2 "I require lpass but it's not installed. Aborting."; exit 1; } if [ ! -d ${outdir} ]; then echo "${outdir} does not exist. Exiting." exit 1 fi if ! lpass status; then if [ -z ${email} ]; then echo "No login data found, Please login with -l or use lpass login before." exit 1; fi lpass login ${email} fi if [ -z ${id} ]; then ids=$(lpass ls | sed -n "s/^.*id:\s*\([0-9]*\).*$/\1/p") else ids=${id} fi for id in ${ids}; do show=$(lpass show ${id}) attcount=$(echo "${show}" | grep -c "att-") path=$(lpass show --format="%/as%/ag%an" ${id} | uniq | tail -1) until [ ${attcount} -lt 1 ]; do att=`lpass show ${id} | grep att- | sed "${attcount}q;d" | tr -d :` attid=$(echo ${att} | awk '{print $1}') attname=$(echo ${att} | awk '{print $2}') if [[ -z ${attname} ]]; then attname=${path#*/} fi path=${path//\\//} mkdir -p "${outdir}/${path}" out=${outdir}/${path}/${attname} if [[ -f ${out} ]]; then out=${outdir}/${path}/${attcount}_${attname} fi echo ${id} - ${path} ": " ${attid} "-" ${attname} " > " ${out} lpass show --attach=${attid} ${id} --quiet > "${out}" let attcount-=1 done done lastpass-cli-1.5.0/contrib/lpass_bash_completion000066400000000000000000000101171462143212600220500ustar00rootroot00000000000000__lpass_complete_fieldname() { local acct=$1 local cur=$2 local matches matches=$(lpass show $acct --format='%fn' --title-format=' ' | \ egrep -v '^ $') local IFS=$'\n' COMPREPLY=($(compgen -W "$matches" "$cur")) if [[ ! -z $COMPREPLY ]]; then COMPREPLY=($(printf "%q\n" "${COMPREPLY[@]}")) fi } __lpass_complete_name() { local cur=$1 local matches # matches on full path matches=$(lpass ls | egrep "^$cur" | sed -e "s/ \[id.*//g") # matches on leaves matches+=$(lpass ls | egrep "/$cur" | sed -e "s/ \[id.*//g" | \ awk -F '/' '{print $NF}') local IFS=$'\n' COMPREPLY=($(compgen -W "$matches" "$cur")) if [[ ! -z $COMPREPLY ]]; then COMPREPLY=($(printf "%q\n" "${COMPREPLY[@]}")) fi } __lpass_complete_group() { local cur=$1 local matches matches=$(lpass ls | egrep "^$cur.*/" | awk -F '/' '{print $1}') local IFS=$'\n' COMPREPLY=($(compgen -W "$matches" "$cur")) if [[ ! -z $COMPREPLY ]]; then COMPREPLY=($(printf "%q\n" "${COMPREPLY[@]}")) fi } __lpass_complete_opt() { local cmd=$1 local cur=$2 local name=$3 opts="" case "$cmd" in login) opts="--trust --plaintext-key --force --color" ;; logout) opts="--force --color" ;; show) opts="--sync --clip --expand-multi --all --username --password --url --notes --field --id --name --basic-regexp --fixed-strings --color" ;; ls) opts="--sync --long --color" ;; mv|duplicate|rm|export|import) opts="--sync --color" ;; edit) opts="--sync --non-interactive --name --username --password --url --notes --field --color" ;; generate) opts="--sync --clip --username --url --no-symbols --color" ;; share) opts="--read_only --hidden --admin" esac COMPREPLY=($(compgen -W "$opts" -- $cur)) } _lpass() { local cur="${COMP_WORDS[COMP_CWORD]}" local cmd="${COMP_WORDS[1]}" local subcmd="${COMP_WORDS[2]}" local prev="${COMP_WORDS[COMP_CWORD-1]}" local optind=1 for i in `seq 2 $COMP_CWORD`; do if [[ ${COMP_WORDS[COMP_CWORD]} != "-*" ]]; then optind=i break fi done local name="${COMP_WORDS[$optind]}" local all_cmds=" login logout passwd show ls mv add edit generate duplicate rm sync export import share " local share_cmds=" userls useradd usermod userdel create rm " # include aliases (although we can't really do much with them) for a in ~/.lpass/alias.*; do all_cmds="$all_cmds ${a#*alias.}" done # subcommands if [[ $COMP_CWORD -eq 1 ]]; then COMPREPLY=($(compgen -W "$all_cmds" $cur)) return # share subcommands elif [[ $COMP_CWORD -eq 2 && $cmd == "share" ]]; then COMPREPLY=($(compgen -W "$share_cmds" $cur)) return fi COMPREPLY=() case "$prev" in --field) __lpass_complete_fieldname $name $cur return ;; esac case "$cur" in -*) __lpass_complete_opt $cmd $cur return ;; esac case "$cmd" in show|rm|edit|duplicate|generate) __lpass_complete_name $cur ;; mv) if [[ $COMP_CWORD -eq $optind ]]; then __lpass_complete_name $cur else __lpass_complete_group $cur fi ;; ls|add) __lpass_complete_group $cur ;; share) case "$subcmd" in userls|useradd|usermod|userdel|rm) if [[ $cur != "Shared-*" ]]; then cur="Shared-$cur" fi __lpass_complete_group $cur ;; create) ;; esac ;; *) ;; esac } complete -o default -F _lpass lpass lastpass-cli-1.5.0/contrib/lpass_zsh_completion000066400000000000000000000141511462143212600217410ustar00rootroot00000000000000#compdef lpass _lpass() { local cmd has_color has_sync has_interactive if (( CURRENT > 2)); then cmd=${words[2]} # Set the context for the subcommand. curcontext="${curcontext%:*:*}:lpass-$cmd" # Narrow the range of words we are looking at to exclude `lpass' (( CURRENT-- )) shift words # Run the completion for the subcommand case "${cmd}" in login) _arguments : \ '--trust[Cause subsequent logins to not require multifactor authentication.]' \ '--plaintext-key[Save plaintext decryption key to the hard disk]' \ '--force[Do not ask on saving plaintext key]' has_color=1 ;; logout) _arguments : '--force[Force confirmation]' has_color=1 ;; show) _arguments : \ '(-c --clip)'{-c,--clip}'[Copy output to clipboard]' \ '(-x --expand-multi)'{-x,---expand-multi}'[Show the requested information from all of the matching sites]' \ '(--all --username --password --url --notes --field= --id --name --attach=)'{--all,--username,--password,--url,--notes,--field=,--id,--name,--attach=}'[Output the specific field]' \ '(--basic-regexp,--fixed-string)'{-G,--basic-regexp}'[Find a site by substring or regular expression]' \ '--format=[Format output with printf-style placeholders]' _lpass_complete_uniqenames has_color=1 has_sync=1 ;; ls) _arguments : \ '(-l --long)'{-l,--long}'[Also list the last modification time and username]' \ '-u[List username]' \ '-m[List modification time]' \ '--format=[Format output with printf-style placeholders]' _lpass_complete_groups has_color=1 has_sync=1 ;; mv) _lpass_complete_uniqenames _lpass_complete_groups has_color=1 ;; duplicate|rm) _lpass_complete_uniqenames has_color=1 has_sync=1 ;; add) _arguments : '(--username --password --url --notes --field=)'{--username,--password,--url,--notes,--field=}'[Add field]' _lpass_complete_uniqenames has_color=1 has_sync=1 has_interactive=1 ;; edit) _arguments : '(--name --username --password --url --notes --field=)'{--name,--username,--password,--url,--notes,--field=}'[Update field]' _lpass_complete_uniqenames has_color=1 has_sync=1 has_interactive=1 ;; generate) _arguments : \ '(-c --clip)'{-c,--clip}'[Copy output to clipboard]' \ '--username=[USERNAME]' \ '--url=[URL]' \ '--no-symbols[Do not use symbols]' has_sync=1 ;; status) _arguments : '(-q --quiet)'{-q,--quiet}'[Supress output to stdout]' has_color=1 ;; sync) _arguments : '(-b --background)'{-b,--background}'[Run sync in background]' has_color=1 ;; export) _arguments : '--fields=[Field list]' has_color=1 has_sync=1 ;; import) if ((CURRENT < 3)); then _files fi ;; esac if [ -n "$has_sync" ] || [ -n "$has_color" ] || [ -n "$has_interactive" ]; then local -a generic_options if [ "$has_sync" -eq 1 ]; then generic_options+=('--sync=[Synchronize local cache with server: auto | now | no]') fi if [ "$has_color" -eq 1 ]; then generic_options+=('--color=[Color: auto | never | always]') fi if [ "$has_interactive" -eq 1 ]; then generic_options+=("--non-interactive[Use stardard input instead of $EDITOR]") fi _arguments $generic_options fi else local -a subcommands subcommands=( "login:Authenticate with the LastPass server and initialize a local cache" "logout:Remove the local cache and stored encryption keys" "passwd:Change your LastPass password" "show:Display a password or selected field" "ls:List names in groups in a tree structure" "mv:Move the specified entry to a new group" "add:Add a new entry" "edit:Edit the selected field" "generate:Create a randomly generated password" "duplicate:Create a duplicate entry of the one specified" "rm:Remove the specified entry" "status:Show current login status" "sync:Synchronize local cache with server" "export:Dump all account information including passwords as unencrypted csv to stdout" "import:Upload accounts from an unencrypted CSV file to the server" "share:Manipulate shared folders (only enterprise or premium user)" ) _describe -t commands 'lpass' subcommands _arguments : \ '(-h --help)'{-h,--help}'[show help]' \ '(-v --version)'{-v,--version}'[show version]' fi } _lpass_complete_uniqenames(){ local -a entries while read i; do if [ -n "$i" ]; then entries+=("$i") fi done < <(lpass ls --sync auto --format "%an" --color=never) compadd -a entries } _lpass_complete_groups() { local -a entries while read i; do if [ -n "$i" ]; then entries+=("$i") fi done < <(lpass ls --sync auto --format "%aN" --color=never | grep -E "\/$") compadd -a entries } _lpass # Local Variables: # mode: Shell-Script # sh-indentation: 2 # indent-tabs-mode: nil # sh-basic-offset: 2 # End: # vim: ft=zsh sw=2 ts=2 et lastpass-cli-1.5.0/contrib/specfile/000077500000000000000000000000001462143212600173475ustar00rootroot00000000000000lastpass-cli-1.5.0/contrib/specfile/lastpass-cli.spec000066400000000000000000000015131462143212600226220ustar00rootroot00000000000000Name: lastpass-cli Version: 0.4.0 Release: 2%{?dist} Summary: C99 command line interface to LastPass.com License: GPLv2 URL: https://github.com/LastPass/lastpass-cli Source0: lastpass-cli-0.4.0.tgz BuildRequires: openssl-devel,libxml2-devel,libcurl-devel,asciidoc Requires: openssl,libcurl,libxml2,pinentry,xclip %description A command line interface to LastPass.com. Made open source and available on github. %prep %setup -q %build make %{?_smp_mflags} %install rm -rf $RPM_BUILD_ROOT %make_install make install-doc DESTDIR=%{?buildroot} %files /usr/bin/lpass /usr/share/man/man1/lpass.1.gz %doc %changelog * Tue Dec 30 2014 Rohan Ferris - 0.4.0-2 - Include asciidoc * Tue Dec 30 2014 Rohan Ferris - 0.4.0-1 - Version number bump * Fri Nov 7 2014 Rohan Ferris - lastpass-cli-1.5.0/debian/000077500000000000000000000000001462143212600153375ustar00rootroot00000000000000lastpass-cli-1.5.0/debian/changelog000066400000000000000000000045541462143212600172210ustar00rootroot00000000000000lastpass-cli (1.5.0) unstable; urgency=medium * New upstream 1.5.0 -- Julio Cavalin Thu, 16 May 2024 14:53:07 +0000 lastpass-cli (1.4.0) unstable; urgency=medium * New upstream 1.4.0 -- Rui Rafael Mon, 15 Apr 2024 14:06:52 +0100 lastpass-cli (1.3.7) unstable; urgency=medium * New upstream 1.3.7 -- Peter Majoros Fri, 10 Nov 2023 09:41:50 +0000 lastpass-cli (1.3.6) unstable; urgency=medium * New upstream 1.3.6 -- Béla Ormos Mon, 04 Sep 2023 14:24:35 +0100 lastpass-cli (1.3.5) unstable; urgency=medium * New upstream 1.3.5 -- Béla Ormos Wed, 30 Aug 2023 18:24:35 +0100 lastpass-cli (1.3.4) unstable; urgency=medium * New upstream 1.3.4 -- Gergely Der Fri, 09 Dec 2022 12:01:35 +0100 lastpass-cli (1.3.3) unstable; urgency=medium * New upstream 1.3.3 -- Gergo Paulovics Mon, 15 Apr 2019 14:07:04 +0200 lastpass-cli (1.3.2) unstable; urgency=medium * New upstream 1.3.2 -- Gergo Paulovics Fri, 22 Mar 2019 12:59:04 +0200 lastpass-cli (1.3.1) unstable; urgency=medium Thu, 17 May 2018 11:39:15 +0200 * New upstream 1.3.1 -- Andras Rutkai Thu, 15 Mar 2018 10:16:04 -0400 lastpass-cli (1.3.0) unstable; urgency=medium * New upstream 1.3.0 -- Bob Copeland Thu, 15 Mar 2018 10:16:04 -0400 lastpass-cli (1.2.2) unstable; urgency=medium * New upstream 1.2.2 -- Bob Copeland Wed, 25 Oct 2017 16:07:56 -0400 lastpass-cli (1.2.1) unstable; urgency=medium * New upstream 1.2.1 -- Bob Copeland Wed, 28 Jun 2017 08:50:06 -0400 lastpass-cli (1.2.0) unstable; urgency=medium * New upstream 1.2.0 -- Bob Copeland Wed, 07 Jun 2017 15:15:56 -0400 lastpass-cli (1.1.2) unstable; urgency=medium * New upstream 1.1.2 -- Bob Copeland Fri, 03 Feb 2017 15:36:22 -0500 lastpass-cli (1.1.0) unstable; urgency=medium * New upstream 1.1.0 -- Bob Copeland Tue, 03 Jan 2017 15:47:40 -0500 lastpass-cli (0.3.0) unstable; urgency=low * Initial Debian Package release. -- Igor Partola Thu, 23 Oct 2014 11:57:23 -0400 lastpass-cli-1.5.0/debian/compat000066400000000000000000000000021462143212600165350ustar00rootroot000000000000008 lastpass-cli-1.5.0/debian/control000066400000000000000000000032351462143212600167450ustar00rootroot00000000000000Source: lastpass-cli Section: utils Priority: optional Maintainer: Troy Heber Build-Depends: debhelper (>= 9), quilt (>= 0.47), libssl-dev | libssl1.0-dev, libxml2-dev, libcurl4-openssl-dev, asciidoc, xsltproc, docbook-xsl, cmake, pkg-config Standards-Version: 3.9.8.0 Package: lastpass-cli Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, binutils Description: command line interface to LastPass.com This application is a command line interface to the LastPass.com services. It brings both better security and convenience by allowing you to access, add, modify, and delete entries in your online LastPass vault, all from the terminal. You can also generate passwords for every server you use and securely store those passwords directly in LastPass. LastPass Enterprise features are supported as well, including Shared Folders. . Users who prefer the command line can access their data directly with “lpass ls” then using “lpass show -c --password Sitename” to put the Sitename password on the copy buffer. You can utilize “lpass show” to store passwords used in scripts, rather than putting passwords in the scripts themselves. LastPass can also be used as you work within the command line to help you login to servers. We’ve included some example scripts below. . The new tool is beneficial for LastPass users who want to use the command line to login to other machines as they work. There are examples such as contrib/examples/change-ssh-password.sh which shows automated password changing on a server. You could run it automatically on a nightly basis, regularly changing the password on the server as a security measure. lastpass-cli-1.5.0/debian/copyright000066400000000000000000000052111462143212600172710ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: lastpass-cli Upstream-Author: Jason A. Donenfeld Original-Source-Location: https://github.com/LastPass/lastpass-cli Packaged-By: Troy Heber Packaged-Date: Thu, 23 Oct 2014 09:12:57 -0600 Files: * Copyright: 2014 LastPass. All Rights Reserved License: GPL-2+ with OpenSSL exception 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. . In addition, as a special exception, the author of this program gives permission to link the code of its release with the OpenSSL project's "OpenSSL" library (or with modified versions of it that use the same license as the "OpenSSL" library), and distribute the linked executables. You must obey the GNU General Public License in all respects for all of the code used other than "OpenSSL". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your 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 package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file `/usr/share/common-licenses/GPL-2'. Files: util.c Copyright: 2008 Otto Moerbeek . 1998 Todd C. Miller . License: ISC Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. lastpass-cli-1.5.0/debian/docs000066400000000000000000000000261462143212600162100ustar00rootroot00000000000000lpass.1.txt README.md lastpass-cli-1.5.0/debian/manpages000066400000000000000000000000161462143212600170520ustar00rootroot00000000000000build/lpass.1 lastpass-cli-1.5.0/debian/rules000077500000000000000000000007611462143212600164230ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 override_dh_auto_build: dh_auto_build -- all doc-man %: dh $@ lastpass-cli-1.5.0/debian/source/000077500000000000000000000000001462143212600166375ustar00rootroot00000000000000lastpass-cli-1.5.0/debian/source/format000066400000000000000000000000151462143212600200460ustar00rootroot000000000000003.0 (native) lastpass-cli-1.5.0/edit.c000066400000000000000000000421741462143212600152160ustar00rootroot00000000000000/* * common routines for editing / adding accounts * * Copyright (C) 2014-2024 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 "cmd.h" #include "endpoints.h" #include "blob.h" #include "agent.h" #include #include #include #include #include "util.h" #include "feature-flag.h" #define MAX_NOTE_LEN (unsigned long) 45000 #if defined(__linux__) || defined(__CYGWIN__) static char *shared_memory_dir(void) { return xstrdup("/dev/shm"); } #elif defined(__APPLE__) && defined(__MACH__) static const char *shared_memory_dir_mount = "dir=\"$(mktemp -d \"${TMPDIR:-/tmp}/lpass.XXXXXXXXXXXXX\")\"\n" "dev=\"$(hdid -drivekey system-image=yes -nomount 'ram://32768' | cut -d ' ' -f 1)\"\n" "[[ -z $dev ]] && exit 1\n" "newfs_hfs -M 700 \"$dev\" >/dev/null 2>&1 || exit 1\n" "mount -t hfs -o noatime -o nobrowse \"$dev\" \"$dir\" || exit 1\n" "echo \"$dev\"\necho \"$dir\"\n"; static const char *shared_memory_dir_unmount = "umount \"$SECURE_TMPDIR\"\n" "diskutil quiet eject \"$RAMDISK_DEV\"\n" "rm -rf \"$SECURE_TMPDIR\"\n"; static void shared_memory_dir_eject(void) { system(shared_memory_dir_unmount); } static char *shared_memory_dir(void) { char *stored = getenv("SECURE_TMPDIR"); if (stored) return xstrdup(stored); _cleanup_free_ char *dev = NULL; char *dir = NULL; size_t len; FILE *script = popen(shared_memory_dir_mount, "r"); if (!script) return NULL; len = 0; if (getline(&dev, &len, script) <= 0) { pclose(script); return NULL; } trim(dev); len = 0; if (getline(&dir, &len, script) <= 0) { pclose(script); return NULL; } trim(dir); setenv("SECURE_TMPDIR", dir, true); setenv("RAMDISK_DEV", dev, true); atexit(shared_memory_dir_eject); return dir; } #else static char *shared_memory_dir(void) { char *tmpdir = getenv("SECURE_TMPDIR"); if (!tmpdir) { if (!(tmpdir = getenv("TMPDIR"))) tmpdir = "/tmp"; fprintf(stderr, "Warning: Using %s as secure temporary directory.\n" "Recommend using tmpfs and encrypted swap.\n" "Set SECURE_TMPDIR environment variable to override.\n", tmpdir); sleep(5); } return xstrdup(tmpdir); } #endif _noreturn_ static inline void die_unlink_errno(const char *str, const char *file, const char *dir) { int saved = errno; if (file) unlink(file); if (dir) rmdir(dir); errno = saved; die_errno("%s", str); } static void assign_account_value(struct account *account, const char *label, char *value, int lineno, unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { struct field *editable_field = NULL; #define assign_if(title, field) do { \ if (!strcmp(label, title)) { \ account_set_##field(account, xstrdup(trim(value)), key); \ return; \ } \ } while (0) #define assign_if_ff(title, field, feature_flag) do { \ if (!strcmp(label, title)) { \ account_set_##field(account, xstrdup(trim(value)), key, feature_flag); \ return; \ } \ } while (0) /* * "Name" may be used in note templates; only assign fullname * in the first line. */ if (lineno == 1) { assign_if("Name", fullname); } assign_if_ff("URL", url, feature_flag); assign_if("Username", username); assign_if("Password", password); assign_if("Application", appname); assign_if("Notes", note); 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)) { field_set_value(account, editable_field, xstrdup(trim(value)), key); return; } } /* Some other name: value pair -- treat like a new field */ editable_field = new0(struct field, 1); editable_field->name = xstrdup(label); editable_field->type = xstrdup("password"); field_set_value(account, editable_field, xstrdup(trim(value)), key); list_add_tail(&editable_field->list, &account->field_head); #undef assign_if } static int read_file_buf(FILE *fp, char **value_out, size_t *len_out) { size_t len; size_t read; char *value; *len_out = 0; *value_out = NULL; for (len = 0, value = xmalloc(8192 + 1); ; value = xrealloc(value, len + 8192 + 1)) { read = fread(value + len, 1, 8192, fp); len += read; if (read != 8192) { if (ferror(fp)) return -EIO; break; } } value[len] = '\0'; *value_out = value; *len_out = len; return 0; } enum note_type get_note_type(struct account *account) { struct field *editable_field; list_for_each_entry(editable_field, &account->field_head, list) { if (!strcmp(editable_field->name, "NoteType")) { return notes_get_type_by_name(editable_field->value); } } return NOTE_TYPE_NONE; } struct parsed_name_value { char *name; char *value; int lineno; struct list_head list; }; /* * Read a file representing all of the data in an account. * We generate this file when editing an account, and parse it back * after a user has edited it. * * Multiline values are accepted (though they may not be supported by * lastpass in all cases). * * Once the "Notes:" label is encountered, everything else is concatenated * into the note. * * Name: text0 * URL: text1 * [...] * Notes: * notes text here */ static void parse_account_file(FILE *input, enum note_type note_type, struct list_head *list_head) { _cleanup_free_ char *line = NULL; ssize_t read; size_t len = 0; char *name, *delim, *value = NULL; bool parsing_notes = false; int ret; int lineno = 0; struct parsed_name_value *current = NULL; /* parse label: [value] */ while ((read = getline(&line, &len, input)) != -1) { lineno++; line = trim(line); delim = strchr(line, ':'); if (!delim) { /* non keyed strings go to existing field (if any) */ if (current) xstrappendf(¤t->value, "\n%s", line); continue; } name = xstrndup(line, delim - line); value = xstrdup(delim + 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) && current && note_field_is_multiline(note_type, current->name)) { xstrappendf(¤t->value, "\n%s", line); free(name); free(value); continue; } if (!strcmp(name, "Notes")) { parsing_notes = true; free(name); free(value); break; } current = new0(struct parsed_name_value, 1); current->name = name; current->value = value; current->lineno = lineno; list_add_tail(¤t->list, list_head); } if (!parsing_notes) return; /* everything else goes into notes section */ value = NULL; len = 0; ret = read_file_buf(input, &value, &len); if (ret) return; if (len > MAX_NOTE_LEN) { die("Maximum note length is %lu bytes (was %lu)", MAX_NOTE_LEN, len); } current = new0(struct parsed_name_value, 1); current->name = xstrdup("Notes"); current->value = value; current->lineno = lineno; list_add_tail(¤t->list, list_head); } static void read_account_file(FILE *input, struct account *account, unsigned char key[KDF_HASH_LEN], const struct feature_flag *feature_flag) { LIST_HEAD(fields); struct parsed_name_value *entry, *tmp; parse_account_file(input, get_note_type(account), &fields); list_for_each_entry_safe(entry, tmp, &fields, list) { assign_account_value(account, entry->name, entry->value, entry->lineno, key, feature_flag); free(entry->name); free(entry->value); list_del(&entry->list); free(entry); } } static struct field *add_default_field(struct account *account, const char *field_name, unsigned char key[KDF_HASH_LEN]) { struct field *editable_field = NULL; bool found = false; list_for_each_entry(editable_field, &account->field_head, list) { if (!strcmp(editable_field->name, field_name)) { found = true; break; } } if (found) return editable_field; editable_field = new0(struct field, 1); editable_field->type = xstrdup("text"); editable_field->name = xstrdup(field_name); field_set_value(account, editable_field, xstrdup(""), key); list_add_tail(&editable_field->list, &account->field_head); return editable_field; } static void add_default_fields(struct account *account, enum note_type note_type, unsigned char key[KDF_HASH_LEN]) { int i; struct note_template *tmpl; if (note_type <= NOTE_TYPE_NONE || note_type >= NUM_NOTE_TYPES) return; /* * Add a new, empty field for any label in the template which * does not already exist in the account. */ tmpl = ¬e_templates[note_type]; for (i=0; tmpl->fields[i]; i++) { /* * ... but skip these: they are already handled by the * collapse code. */ if (!strcmp(tmpl->fields[i], "Username")) continue; if (!strcmp(tmpl->fields[i], "Password")) continue; add_default_field(account, tmpl->fields[i], key); } } static int write_account_file(FILE *fp, struct account *account, unsigned char key[KDF_HASH_LEN]) { struct field *editable_field = NULL; enum note_type note_type; #define write_field(title, field) do { \ if (fprintf(fp, "%s: %s\n", title, field) < 0) \ return -errno; \ } while (0) write_field("Name", account->fullname); note_type = get_note_type(account); if (account->is_app) { struct app *app = account_to_app(account); write_field("Application", app->appname); } else if (note_type != NOTE_TYPE_NONE) { add_default_fields(account, note_type, key); } else { write_field("URL", account->url); write_field("Username", account->username); write_field("Password", account->password); } list_for_each_entry(editable_field, &account->field_head, list) { 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; return 0; #undef write_field } int edit_account(struct session *session, struct blob *blob, enum blobsync sync, struct account *editable, enum edit_choice choice, const char *field, bool non_interactive, unsigned char key[KDF_HASH_LEN]) { size_t len; struct account *notes_expansion, *notes_collapsed = NULL; struct field *editable_field = NULL; _cleanup_free_ char *tmppath = NULL; _cleanup_free_ char *tmpdir = NULL; _cleanup_free_ char *editcmd = NULL; int tmpfd; FILE *tmpfile; char *value; int ret; struct share *old_share = editable->share; notes_expansion = notes_expand(editable); if (notes_expansion) { notes_collapsed = editable; editable = notes_expansion; } else if (choice == EDIT_FIELD) die("Editing fields of entries that are not secure notes is currently not supported."); switch(choice) { case EDIT_USERNAME: value = editable->username; break; case EDIT_PASSWORD: value = editable->password; break; case EDIT_URL: value = editable->url; break; case EDIT_NAME: value = editable->fullname; break; case EDIT_FIELD: editable_field = add_default_field(editable, field, key); value = editable_field->value; break; case EDIT_NOTES: value = editable->note; break; default: value = NULL; } if (!non_interactive) { if (editable->pwprotect) { unsigned char pwprotect_key[KDF_HASH_LEN]; if (!agent_load_key(pwprotect_key)) die("Could not authenticate for protected entry."); if (memcmp(pwprotect_key, key, KDF_HASH_LEN)) die("Current key is not on-disk key."); } if (strcmp(editable->id, "0")) lastpass_log_access(sync, session, key, editable); tmpdir = shared_memory_dir(); xstrappend(&tmpdir, "/lpass.XXXXXX"); if (!mkdtemp(tmpdir)) die_errno("mkdtemp"); xasprintf(&tmppath, "%s/lpass.XXXXXX", tmpdir); tmpfd = mkstemp(tmppath); if (tmpfd < 0) die_unlink_errno("mkstemp", tmppath, tmpdir); tmpfile = fdopen(tmpfd, "w"); if (!tmpfile) die_unlink_errno("fdopen", tmppath, tmpdir); if (choice == EDIT_ANY) { if (write_account_file(tmpfile, editable, key)) die_unlink_errno("fprintf", tmppath, tmpdir); } else { if (fprintf(tmpfile, "%s\n", value) < 0) die_unlink_errno("fprintf", tmppath, tmpdir); } fclose(tmpfile); xasprintf(&editcmd, "${VISUAL:-${EDITOR:-vi}} '%s'", tmppath); if (system(editcmd) < 0) die_unlink_errno("system($VISUAL)", tmppath, tmpdir); tmpfile = fopen(tmppath, "r"); } else tmpfile = stdin; if (!tmpfile) die_unlink_errno("fopen", tmppath, tmpdir); if (choice == EDIT_NOTES) { ret = read_file_buf(tmpfile, &value, &len); if (ret) die_unlink_errno("fread(tmpfile)", tmppath, tmpdir); } else if (choice == EDIT_ANY) { read_account_file(tmpfile, editable, key, &session->feature_flag); value = NULL; } else { ret = read_file_buf(tmpfile, &value, &len); if (ret) die_unlink_errno("fread(tmpfile)", tmppath, tmpdir); } fclose(tmpfile); if (value) { len = strlen(value); if (len && value[len - 1] == '\n') value[len - 1] = '\0'; } if (tmppath) { unlink(tmppath); rmdir(tmpdir); } if (choice == EDIT_USERNAME) account_set_username(editable, value, key); else if (choice == EDIT_PASSWORD) account_set_password(editable, value, key); else if (choice == EDIT_URL) account_set_url(editable, value, key, &session->feature_flag); else if (choice == EDIT_NAME) account_set_fullname(editable, value, key); else if (choice == EDIT_NOTES) account_set_note(editable, value, key); else if (choice == EDIT_FIELD) { if (!value || !strlen(value)) { list_del(&editable_field->list); field_free(editable_field); } else field_set_value(editable, editable_field, value, key); } if (notes_expansion && notes_collapsed) { editable = notes_collapsed; notes_collapsed = notes_collapse(notes_expansion); 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); } account_assign_share(blob, editable, key, &session->feature_flag); if (old_share != editable->share) { die("Use lpass mv to move items to/from shared folders"); } lastpass_update_account(sync, key, session, editable, blob); blob_save(blob, key, &session->feature_flag); session_free(session); blob_free(blob); return 0; } int edit_new_account(struct session *session, struct blob *blob, enum blobsync sync, const char *name, enum edit_choice choice, const char *field, bool non_interactive, bool is_app, enum note_type note_type, unsigned char key[KDF_HASH_LEN]) { struct app *app; struct account *account; if (note_type != NOTE_TYPE_NONE && choice != EDIT_NOTES && choice != EDIT_ANY) { die("Note type may only be used with secure notes"); } if (is_app) { app = new_app(); account = &app->account; } else { account = new_account(); } account->id = xstrdup("0"); account->attachkey = xstrdup(""); account->attachkey_encrypted = xstrdup(""); account_set_password(account, xstrdup(""), key); account_set_fullname(account, xstrdup(name), key); account_set_username(account, xstrdup(""), key); account_set_note(account, xstrdup(""), key); if (choice == EDIT_NOTES || note_type != NOTE_TYPE_NONE) { account_set_url(account, xstrdup("http://sn"), key, &session->feature_flag); } else { account_set_url(account, xstrdup(""), key, &session->feature_flag); } account_assign_share(blob, account, key, &session->feature_flag); list_add(&account->list, &blob->account_head); if (note_type != NOTE_TYPE_NONE) { char *note_type_str = NULL; xasprintf(¬e_type_str, "NoteType:%s\n", notes_get_name(note_type)); account_set_note(account, note_type_str, key); } return edit_account(session, blob, sync, account, choice, field, non_interactive, key); } lastpass-cli-1.5.0/endpoints-login.c000066400000000000000000000257101462143212600173770ustar00rootroot00000000000000/* * https endpoints for logging into LastPass * * Copyright (C) 2014-2018 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 "endpoints.h" #include "http.h" #include "xml.h" #include "password.h" #include "config.h" #include "util.h" #include "upload-queue.h" #include "version.h" #include "terminal.h" #include #include struct multifactor_type { const char *name; const char *error_str; const char *error_failure_str; const char *post_var; }; static struct multifactor_type multifactor_types[] = { { .name = "Google Authenticator Code", .error_str = "googleauthrequired", .error_failure_str = "googleauthfailed", .post_var = "otp" }, { .name = "YubiKey OTP", .error_str = "otprequired", .error_failure_str = "otpfailed", .post_var = "otp" }, { .name = "Sesame OTP", .error_str = "sesameotprequired", .error_failure_str = "sesameotpfailed", .post_var = "sesameotp" }, { .name = "Out-of-Band OTP", .error_str = "outofbandrequired", .error_failure_str = "multifactorresponsefailed", .post_var = "otp" }, { .name = "Microsoft Authenticator Code", .error_str = "microsoftauthrequired", .error_failure_str = "microsoftauthfailed", .post_var = "otp" } }; static void filter_error_message(char *message) { char *nullit; nullit = strstr(message, " Upgrade your browser extension so you can enter it."); if (nullit) *nullit = '\0'; } static inline void append_post(char **args, const char *name, const char *val) { char **last = args; while (*last && strcmp(*last, name)) ++last; *last = (char *)name; *(last + 1) = (char *)val; } static char *calculate_trust_id(bool force) { char *trusted_id; trusted_id = config_read_string("trusted_id"); if (force && !trusted_id) { trusted_id = xcalloc(33, 1); for (size_t i = 0; i < 32; ++i) trusted_id[i] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$"[range_rand(0, 66)]; config_write_string("trusted_id", trusted_id); } return trusted_id; } static char *calculate_trust_label(void) { char *trusted_label; struct utsname utsname; if (uname(&utsname) < 0) die_errno("Failed to determine uname."); xasprintf(&trusted_label, "%s - %s %s", utsname.nodename, utsname.sysname, utsname.release); return trusted_label; } static bool error_post(char **message, struct session **session) { *session = NULL; if (message) *message = xstrdup("Unable to post login request."); return true; } static bool error_other(char **message, struct session **session, const char *txt) { *session = NULL; if (message) *message = xstrdup(txt); return true; } static bool error_message(char **message, struct session **session, const char *reply) { *session = NULL; if (message) { *message = xml_error_cause(reply, "message"); if (*message) filter_error_message(*message); else *message = xstrdup("Could not parse error message to login request."); } return true; } static bool ordinary_login(const char *login_server, const unsigned char key[KDF_HASH_LEN], char **args, char **cause, char **message, char **reply, struct session **session, char **ret_login_server) { char *server; free(*reply); *reply = http_post_lastpass_v(login_server, "login.php", NULL, NULL, args); if (!*reply) return error_post(message, session); *session = xml_ok_session(*reply, key); if (*session) { (*session)->server = xstrdup(login_server); return true; } /* handle server redirection if requested for lastpass.eu */ server = xml_error_cause(*reply, "server"); if (server && strcmp(server, "lastpass.eu") == 0) return ordinary_login(server, key, args, cause, message, reply, session, ret_login_server); *cause = xml_error_cause(*reply, "cause"); if (!*cause) return error_other(message, session, "Unable to determine login failure cause."); *ret_login_server = xstrdup(login_server); return false; } static inline bool has_capabilities(const char *capabilities, const char *capability) { _cleanup_free_ char *caps = xstrdup(capabilities); char *token; for (token = strtok(caps, ","); token; token = strtok(NULL, ",")) { if (!strcmp(capability, token)) return true; } return false; } static bool oob_login(const char *login_server, const unsigned char key[KDF_HASH_LEN], char **args, char **message, char **reply, char **oob_name, struct session **session) { _cleanup_free_ char *oob_capabilities = NULL; _cleanup_free_ char *cause = NULL; _cleanup_free_ char *retryid = NULL; bool can_do_passcode; bool ret; *oob_name = xml_error_cause(*reply, "outofbandname"); oob_capabilities = xml_error_cause(*reply, "capabilities"); if (!*oob_name || !oob_capabilities) return error_other(message, session, "Could not determine out-of-band type."); can_do_passcode = has_capabilities(oob_capabilities, "passcode"); if (can_do_passcode && !has_capabilities(oob_capabilities, "outofband")) { xstrappend(oob_name, " OTP"); goto failure; } terminal_fprintf(stderr, TERMINAL_FG_YELLOW TERMINAL_BOLD "Waiting for approval of out-of-band %s login%s" TERMINAL_NO_BOLD "...", *oob_name, can_do_passcode ? ", or press Ctrl+C to enter a passcode" : ""); append_post(args, "outofbandrequest", "1"); for (;;) { free(*reply); *reply = http_post_lastpass_v(login_server, "login.php", NULL, NULL, args); if (!*reply) { if (can_do_passcode) { append_post(args, "outofbandrequest", "0"); append_post(args, "outofbandretry", "0"); append_post(args, "outofbandretryid", ""); xstrappend(oob_name, " OTP"); goto failure; } else { error_post(message, session); goto success; } } *session = xml_ok_session(*reply, key); if (*session) { (*session)->server = xstrdup(login_server); goto success; } free(cause); cause = xml_error_cause(*reply, "cause"); if (cause && !strcmp(cause, "outofbandrequired")) { free(retryid); retryid = xml_error_cause(*reply, "retryid"); append_post(args, "outofbandretry", "1"); append_post(args, "outofbandretryid", retryid); fprintf(stderr, "."); continue; } error_message(message, session, *reply); goto success; } success: ret = true; goto out; failure: ret = false; goto out; out: terminal_fprintf(stderr, TERMINAL_RESET "\n" TERMINAL_UP_CURSOR(1) TERMINAL_CLEAR_DOWN); return ret; } static bool otp_login(const char *login_server, const unsigned char key[KDF_HASH_LEN], char **args, char **message, char **reply, const char *otp_name, const char *cause, const char *username, struct session **session) { struct multifactor_type *replied_multifactor = NULL; _cleanup_free_ char *multifactor = NULL; _cleanup_free_ char *next_cause = NULL; char *multifactor_error = NULL; for (size_t i = 0; i < ARRAY_SIZE(multifactor_types); ++i) { if (!strcmp(multifactor_types[i].error_str, cause)) { replied_multifactor = &multifactor_types[i]; break; } } if (!replied_multifactor) return error_message(message, session, *reply); for (;;) { free(multifactor); multifactor = password_prompt("Code", multifactor_error, "Please enter your %s for <%s>.", otp_name ? otp_name : replied_multifactor->name, username); if (!multifactor) return error_other(message, session, "Aborted multifactor authentication."); append_post(args, replied_multifactor->post_var, multifactor); free(*reply); *reply = http_post_lastpass_v(login_server, "login.php", NULL, NULL, args); if (!*reply) return error_post(message, session); *session = xml_ok_session(*reply, key); if (*session) { (*session)->server = xstrdup(login_server); return true; } free(next_cause); next_cause = xml_error_cause(*reply, "cause"); if (next_cause && !strcmp(next_cause, replied_multifactor->error_failure_str)) multifactor_error = "Invalid multifactor code; please try again."; else return error_message(message, session, *reply); } } struct session *lastpass_login(const char *username, const char hash[KDF_HEX_LEN], const unsigned char key[KDF_HASH_LEN], int iterations, char **error_message, bool trust) { char *args[33]; _cleanup_free_ char *user_lower = NULL; _cleanup_free_ char *iters = NULL; _cleanup_free_ char *trusted_id = NULL; _cleanup_free_ char *trusted_label = NULL; _cleanup_free_ char *cause = NULL; _cleanup_free_ char *reply = NULL; _cleanup_free_ char *otp_name = NULL; _cleanup_free_ char *login_server = NULL; struct session *session = NULL; iters = xultostr(iterations); user_lower = xstrlower(username); trusted_id = calculate_trust_id(trust); memset(args, 0, sizeof(args)); append_post(args, "xml", "2"); append_post(args, "username", user_lower); append_post(args, "hash", hash); append_post(args, "iterations", iters); append_post(args, "includeprivatekeyenc", "1"); append_post(args, "method", "cli"); append_post(args, "outofbandsupported", "1"); if (trusted_id) append_post(args, "uuid", trusted_id); if (ordinary_login(LASTPASS_SERVER, key, args, &cause, error_message, &reply, &session, &login_server)) return session; if (trust) { trusted_label = calculate_trust_label(); append_post(args, "trustlabel", trusted_label); } 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, "token", session->token, "uuid", trusted_id, "trustlabel", trusted_label, NULL); return session; } if (otp_login(login_server, key, args, error_message, &reply, otp_name, cause, user_lower, &session)) return session; error_other(error_message, &session, "An unspecified error occurred."); return NULL; } lastpass-cli-1.5.0/endpoints-share.c000066400000000000000000000302051462143212600173640ustar00rootroot00000000000000/* * https endpoints for shared folder manipulation * * Copyright (C) 2014-2024 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 "endpoints.h" #include "http.h" #include "version.h" #include "xml.h" #include "config.h" #include "util.h" #include "upload-queue.h" #include #include #include int lastpass_share_getinfo(const struct session *session, const char *shareid, struct list_head *users) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "sharejs", "1", "getinfo", "1", "id", shareid, "xmlr", "1", NULL); if (!reply) return -EPERM; xml_parse_share_getinfo(reply, users); return 0; } static int lastpass_share_get_user_by_uid(const struct session *session, const char *uid, struct share_user *user) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *uid_param; size_t len; xasprintf(&uid_param, "{\"%s\":{}}", uid); /* get the pubkey for the user/group */ reply = http_post_lastpass("share.php", session, &len, "token", session->token, "getpubkey", "1", "uid", uid_param, "xmlr", "1", NULL); return xml_parse_share_getpubkey(reply, user); } static int lastpass_share_get_users_by_username(const struct session *session, const char *username, struct list_head *users) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *uid_param; size_t len; xasprintf(&uid_param, "{\"%s\":{}}", username); /* get the pubkey for the user/group */ reply = http_post_lastpass("share.php", session, &len, "token", session->token, "getpubkey", "1", "uid", uid_param, "xmlr", "1", NULL); return xml_parse_share_getpubkeys(reply, users); } int lastpass_share_user_add(const struct session *session, struct share *share, struct share_user *user) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *enc_share_name = NULL; _cleanup_free_ char *hex_share_key = NULL; _cleanup_free_ unsigned char *enc_share_key = NULL; _cleanup_free_ char *hex_enc_share_key = NULL; int ret; size_t len; struct list_head user_list; struct share_user *share_user, *tmp; INIT_LIST_HEAD(&user_list); ret = lastpass_share_get_users_by_username(session, user->username, &user_list); if (ret) die("Unable to lookup user %s (%d)\n", user->username, ret); list_for_each_entry_safe(share_user, tmp, &user_list, list) { /* encrypt sharename with sharekey */ enc_share_name = encrypt_and_base64(share->name, share->key); /* encrypt sharekey with user's pubkey */ bytes_to_hex(share->key, &hex_share_key, sizeof(share->key)); size_t enc_share_key_len = share_user->sharing_key.len; enc_share_key = xmalloc(enc_share_key_len); ret = cipher_rsa_encrypt(hex_share_key, &share_user->sharing_key, enc_share_key, &enc_share_key_len); if (ret) die("Unable to encrypt sharing key with pubkey (%d)\n", ret); bytes_to_hex(enc_share_key, &hex_enc_share_key, enc_share_key_len); reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "update", "1", "add", "1", "notify", "1", "username0", share_user->username, "cgid0", share_user->cgid ? share_user->cgid : "", "sharekey0", hex_enc_share_key, "sharename", enc_share_name, "name", share->name, "readonly", bool_str(user->read_only), "give", bool_str(!user->hide_passwords), "canadminister", bool_str(user->admin), "xmlr", "1", NULL); free(share_user); } if (!reply) return -EPERM; return 0; } int lastpass_share_user_mod(const struct session *session, struct share *share, struct share_user *user) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "up", "1", "edituser", "1", "uid", user->uid, "readonly", user->read_only ? "on" : "", "give", !user->hide_passwords ? "on" : "", "canadminister", user->admin ? "on" : "", "xmlr", "1", NULL); if (!reply) return -EPERM; return 0; } int lastpass_share_user_del(const struct session *session, const char *shareid, struct share_user *user) { char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", shareid, "update", "1", "delete", "1", "uid", user->uid, "xmlr", "1", NULL); free(reply); return 0; } int lastpass_share_create(const struct session *session, const char *sharename) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *sf_username; _cleanup_free_ char *enc_share_name = NULL; _cleanup_free_ char *hex_share_key = NULL; _cleanup_free_ unsigned char *enc_share_key = NULL; _cleanup_free_ char *sf_fullname = NULL; _cleanup_free_ char *hex_enc_share_key = NULL; unsigned char pw[KDF_HASH_LEN * 2]; unsigned char key[KDF_HASH_LEN]; char hash[KDF_HEX_LEN]; struct share_user user; size_t len; unsigned int i; int ret; /* strip off "Shared-" part if included, we add it later */ if (!strncmp(sharename, "Shared-", 7)) sharename += 7; ret = lastpass_share_get_user_by_uid(session, session->uid, &user); if (ret) die("Unable to get pubkey for your user (%d)\n", ret); xasprintf(&sf_fullname, "Shared-%s", sharename); xasprintf(&sf_username, "%s-%s", user.username, sf_fullname); for (i=0; i < strlen(sf_username); i++) if (sf_username[i] == ' ') sf_username[i] = '_'; /* * generate random sharing key. kdf_decryption_key wants a string so * we remove any zeroes except the terminator. */ get_random_bytes(pw, sizeof(pw)); pw[sizeof(pw)-1] = 0; for (i=0; i < sizeof(pw)-1; i++) { if (!pw[i]) pw[i] = (unsigned char) range_rand(1, 256); } kdf_decryption_key(sf_username, (char *) pw, 1, key); kdf_login_key(sf_username, (char *) pw, 1, hash); bytes_to_hex(key, &hex_share_key, sizeof(key)); /* * Sharing key is hex-encoded then RSA-encrypted with our pubkey. * Shared folder name is AES-encrypted with the sharing key. */ size_t enc_share_key_len = user.sharing_key.len; enc_share_key = xmalloc(enc_share_key_len); ret = cipher_rsa_encrypt(hex_share_key, &user.sharing_key, enc_share_key, &enc_share_key_len); if (ret) die("Unable to RSA encrypt the sharing key (%d)", ret); bytes_to_hex(enc_share_key, &hex_enc_share_key, enc_share_key_len); enc_share_name = encrypt_and_base64(sf_fullname, key); reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", "0", "update", "1", "newusername", sf_username, "newhash", hash, "sharekey", hex_enc_share_key, "name", sf_fullname, "sharename", enc_share_name, "xmlr", "1", NULL); if (!reply) return -EPERM; return 0; } int lastpass_share_delete(const struct session *session, struct share *share) { char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "delete", "1", "xmlr", "1", NULL); free(reply); return 0; } /* * Move a site into or out of a shared folder. * * account should already be encrypted with the new share key. * orig_folder or account->share may be null, indicating the * transition to or from a regular site and a shared folder. */ int lastpass_share_move(const struct session *session, struct account *account, struct share *orig_folder) { _cleanup_free_ char *url = NULL; _cleanup_free_ char *reply = NULL; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; if (!account->share && !orig_folder) return 0; bytes_to_hex((unsigned char *) account->url, &url, strlen(account->url)); if (session->feature_flag.url_encryption_enabled) { http_post_add_params(¶ms, "token", session->token, "cmd", "uploadaccounts", "aid0", account->id, "name0", account->name_encrypted, "grouping0", account->group_encrypted, "url0", account->url_encrypted, "username0", account->username_encrypted, "password0", account->password_encrypted, "pwprotect0", account->pwprotect ? "on" : "off", "extra0", account->note_encrypted, "todelete", account->id, NULL); } else { http_post_add_params(¶ms, "token", session->token, "cmd", "uploadaccounts", "aid0", account->id, "name0", account->name_encrypted, "grouping0", account->group_encrypted, "url0", url, "username0", account->username_encrypted, "password0", account->password_encrypted, "pwprotect0", account->pwprotect ? "on" : "off", "extra0", account->note_encrypted, "todelete", account->id, NULL); } if (account->share) { http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); } if (orig_folder) { http_post_add_params(¶ms, "origsharedfolderid", orig_folder->id, NULL); } if (session->feature_flag.url_logging_enabled) { http_post_add_params(¶ms, "recordUrl", url, NULL); } reply = http_post_lastpass_param_set("lastpass/api.php", session, NULL, ¶ms); free(params.argv); if (!reply) return -EINVAL; return xml_api_err(reply); } int lastpass_share_get_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *ret_limit) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "limit", "1", "uid", user->uid, "xmlr", "1", NULL); xml_parse_share_get_limits(reply, ret_limit); return 0; } int lastpass_share_set_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *limit) { char *reply = NULL; _cleanup_free_ char *aid_buf = NULL; char numaids_str[30] = {0}; struct share_limit_aid *aid; int numaids = 0; size_t alloc_len = 0; size_t len; list_for_each_entry(aid, &limit->aid_list, list) { alloc_len += strlen(aid->aid) + 1 /* comma or null */; numaids++; } aid_buf = xcalloc(alloc_len, 1); list_for_each_entry(aid, &limit->aid_list, list) { strlcat(aid_buf, aid->aid, alloc_len); strlcat(aid_buf, ",", alloc_len); } aid_buf[alloc_len-1] = '\0'; snprintf(numaids_str, sizeof(numaids_str), "%d", numaids); reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "limit", "1", "edit", "1", "uid", user->uid, "numaids", numaids_str, "hidebydefault", bool_str(limit->whitelist), "aids", aid_buf, "xmlr", "1", NULL); free(reply); return 0; } lastpass-cli-1.5.0/endpoints.c000066400000000000000000000367401462143212600162760ustar00rootroot00000000000000/* * https endpoints for LastPass services * * Copyright (C) 2014-2024 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 "endpoints.h" #include "http.h" #include "version.h" #include "xml.h" #include "config.h" #include "util.h" #include "upload-queue.h" #include #include #include unsigned int lastpass_iterations(const char *username) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *user_lower = NULL; user_lower = xstrlower(username); reply = http_post_lastpass("iterations.php", NULL, NULL, "email", user_lower, NULL); if (!reply) return 0; return strtoul(reply, NULL, 10); } void lastpass_logout(const struct session *session) { free(http_post_lastpass("logout.php", session, NULL, "method", "cli", "noredirect", "1", "token", session->token, NULL)); } struct blob *lastpass_get_blob(const struct session *session, const unsigned char key[KDF_HASH_LEN]) { size_t len; _cleanup_free_ char *blob = http_post_lastpass("getaccts.php", session, &len, "mobile", "1", "requestsrc", "cli", "hasplugin", LASTPASS_CLI_VERSION, NULL); if (!blob || !len) return NULL; config_write_encrypted_buffer("blob", blob, len, key); return blob_parse((unsigned char *) blob, len, key, &session->private_key); } void lastpass_remove_account(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const struct account *account, struct blob *blob) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; http_post_add_params(¶ms, "extjs", "1", "token", session->token, "delete", "1", "aid", account->id, NULL); if (account->share) http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); _cleanup_free_ char *url = NULL; if (session->feature_flag.url_logging_enabled) { bytes_to_hex((unsigned char *) account->url, &url, strlen(account->url)); http_post_add_params(¶ms, "recordUrl", url, NULL); } ++blob->version; 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) { int index = 0; struct field *field; list_for_each_entry(field, &account->field_head, list) { char *id_name, *type_name, *value_name; xasprintf(&id_name, "fieldid%d", index); xasprintf(&type_name, "fieldtype%d", index); xasprintf(&value_name, "fieldvalue%d", index); http_post_add_params(params, id_name, field->name, type_name, field->type, value_name, field->value_encrypted, NULL); index++; } } void lastpass_update_account(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const struct account *account, struct blob *blob) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; _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; http_post_add_params(¶ms, "extjs", "1", "token", session->token, "method", "cli", "name", account->name_encrypted, "grouping", account->group_encrypted, "pwprotect", account->pwprotect ? "on" : "off", NULL); if (account->share) { http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); } if (account->is_app) { struct app *app = account_to_app(account); http_post_add_params(¶ms, "ajax", "1", "cmd", "updatelpaa", "appname", app->appname, NULL); add_app_fields(account, ¶ms); if (strcmp(account->id, "0")) http_post_add_params(¶ms, "appaid", account->id, NULL); upload_queue_enqueue(sync, key, session, "addapp.php", ¶ms); goto out_free_params; } if (session->feature_flag.url_encryption_enabled) { http_post_add_params(¶ms, "aid", account->id, "url", account->url_encrypted, "username", account->username_encrypted, "password", account->password_encrypted, "extra", account->note_encrypted, NULL); } else { http_post_add_params(¶ms, "aid", account->id, "url", url, "username", account->username_encrypted, "password", account->password_encrypted, "extra", account->note_encrypted, NULL); } if (strlen(fields)) { http_post_add_params(¶ms, "save_all", "1", "data", fields, NULL); } if (session->feature_flag.url_logging_enabled) { http_post_add_params(¶ms, "recordUrl", url, NULL); } upload_queue_enqueue(sync, key, session, "show_website.php", ¶ms); out_free_params: free(params.argv); } unsigned long long lastpass_get_blob_version(struct session *session, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *reply = NULL; unsigned long long version; reply = http_post_lastpass("login_check.php", session, NULL, "method", "cli", NULL); if (!reply) return 0; version = xml_login_check(reply, session); if (version) session_save(session, key); return version; } void lastpass_log_access(enum blobsync sync, const struct session *session, unsigned const char key[KDF_HASH_LEN], const struct account *account) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; if (!strcmp(account->id, "0")) return; http_post_add_params(¶ms, "id", account->id, "method", "cli", NULL); if (account->share) http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); _cleanup_free_ char *url = NULL; if (session->feature_flag.url_logging_enabled) { bytes_to_hex((unsigned char *) account->url, &url, strlen(account->url)); http_post_add_params(¶ms, "recordUrl", url, NULL); } upload_queue_enqueue(sync, key, session, "loglogin.php", ¶ms); free(params.argv); } int lastpass_pwchange_start(const struct session *session, const char *username, const char hash[KDF_HEX_LEN], struct pwchange_info *info) { _cleanup_free_ char *reply = NULL; reply = http_post_lastpass("lastpass/api.php", session, NULL, "cmd", "getacctschangepw", "username", username, "hash", hash, "changepw", "1", "changepw2", "1", "includersaprivatekeyenc", "1", "changeun", "", "resetrsakeys", "0", "includeendmarker", "1", NULL); if (!reply) return -ENOENT; return xml_parse_pwchange(reply, info); } int lastpass_pwchange_complete(const struct session *session, const char *username, const char *enc_username, const char new_hash[KDF_HEX_LEN], int new_iterations, struct pwchange_info *info) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; struct pwchange_field *field; struct pwchange_su_key *su_key; _cleanup_free_ char *iterations_str = xultostr(new_iterations); _cleanup_free_ char *sukeycnt_str = NULL; _cleanup_free_ char *reencrypt_string = NULL; _cleanup_free_ char *reply = NULL; size_t len; int su_key_ind; char suuid_str[30] = {0}; char sukey_str[30] = {0}; unsigned int i; /* build reencrypt string from change pw info */ len = strlen(info->reencrypt_id) + 1; list_for_each_entry(field, &info->fields, list) { len += strlen(field->old_ctext) + strlen(field->new_ctext) + 1 /* ':' */ + 1 /* '\n' */; } reencrypt_string = xcalloc(len + 1, 1); strlcat(reencrypt_string, info->reencrypt_id, len); strlcat(reencrypt_string, "\n", len); list_for_each_entry(field, &info->fields, list) { strlcat(reencrypt_string, field->old_ctext, len); strlcat(reencrypt_string, ":", len); strlcat(reencrypt_string, field->new_ctext, len); strlcat(reencrypt_string, "\n", len); } http_post_add_params(¶ms, "cmd", "updatepassword", "pwupdate", "1", "email", username, "token", info->token, "reencrypt", reencrypt_string, "newprivatekeyenc", info->new_privkey_encrypted, "newuserkeyhexhash", info->new_key_hash, "newprivatekeyenchexhash", info->new_privkey_hash, "newpasswordhash", new_hash, "key_iterations", iterations_str, "encrypted_username", enc_username, "origusername", username, NULL); su_key_ind = 0; list_for_each_entry(su_key, &info->su_keys, list) { snprintf(suuid_str, sizeof(suuid_str), "suuid%d", su_key_ind); snprintf(sukey_str, sizeof(sukey_str), "sukey%d", su_key_ind); http_post_add_params(¶ms, xstrdup(suuid_str), su_key->uid, xstrdup(sukey_str), su_key->new_enc_key, NULL); su_key_ind++; } sukeycnt_str = xultostr(su_key_ind); http_post_add_params(¶ms, xstrdup("sukeycnt"), sukeycnt_str, NULL); reply = http_post_lastpass_param_set("lastpass/api.php", session, NULL, ¶ms); for (i=0; i < params.n_alloced && params.argv[i]; i++) { if (starts_with(params.argv[i], "sukey") || starts_with(params.argv[i], "suuid")) { free(params.argv[i]); } } if (!reply) return -EINVAL; if (!strstr(reply, "pwchangeok")) return -EINVAL; return 0; } /* * Upload a set of accounts, used for import. */ int lastpass_upload(const struct session *session, struct list_head *accounts) { _cleanup_free_ char *reply = NULL; struct account *account; int index; unsigned int i; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; if (list_empty(accounts)) return 0; http_post_add_params(¶ms, "token", session->token, "cmd", "uploadaccounts", NULL); index = 0; list_for_each_entry(account, accounts, list) { char *name_param, *grouping_param; char *url_param, *username_param, *password_param; char *fav_param, *extra_param, *record_url_param; char *url = NULL; bytes_to_hex((unsigned char *) account->url, &url, strlen(account->url)); xasprintf(&name_param, "name%d", index); xasprintf(&grouping_param, "grouping%d", index); xasprintf(&url_param, "url%d", index); xasprintf(&username_param, "username%d", index); xasprintf(&password_param, "password%d", index); xasprintf(&fav_param, "fav%d", index); xasprintf(&extra_param, "extra%d", index); xasprintf(&record_url_param, "recordUrl%d", index); if (session->feature_flag.url_encryption_enabled) { http_post_add_params(¶ms, name_param, account->name_encrypted, grouping_param, account->group_encrypted, url_param, account->url_encrypted, username_param, account->username_encrypted, password_param, account->password_encrypted, fav_param, account->fav ? "1" : "0", extra_param, account->note_encrypted, NULL); } else { http_post_add_params(¶ms, name_param, account->name_encrypted, grouping_param, account->group_encrypted, url_param, url, username_param, account->username_encrypted, password_param, account->password_encrypted, fav_param, account->fav ? "1" : "0", extra_param, account->note_encrypted, NULL); } if (session->feature_flag.url_logging_enabled) { http_post_add_params(¶ms, record_url_param, url, NULL); } index++; } reply = http_post_lastpass_param_set("lastpass/api.php", session, NULL, ¶ms); for (i=0; i < params.n_alloced && params.argv[i]; i++) { if (starts_with(params.argv[i], "name") || starts_with(params.argv[i], "grouping") || starts_with(params.argv[i], "username") || starts_with(params.argv[i], "password") || starts_with(params.argv[i], "fav") || starts_with(params.argv[i], "extra") || starts_with(params.argv[i], "recordUrl")) { free(params.argv[i]); } else if (starts_with(params.argv[i], "url")) { free(params.argv[i]); if (i < params.n_alloced) { free(params.argv[i+1]); i++; } } } free(params.argv); if (!reply) return -EINVAL; return xml_api_err(reply); } /* * Get the attachment for a given attachment id. The crypttext is returned * and should be decrypted with account->attachkey. The pointer returned * in *result should be freed by the caller. */ int lastpass_load_attachment(const struct session *session, const char *shareid, struct attach *attach, char **result) { char *reply = NULL; char *p; *result = NULL; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; http_post_add_params(¶ms, "token", session->token, "getattach", attach->storagekey, NULL); if (shareid) { http_post_add_params(¶ms, "sharedfolderid", shareid, NULL); } reply = http_post_lastpass_param_set("getattach.php", session, NULL, ¶ms); free(params.argv); if (!reply) return -ENOENT; /* returned string is json-encoded base64 string; unescape it */ if (reply[0] == '"') memmove(reply, reply+1, strlen(reply)); if (reply[strlen(reply)-1] == '"') reply[strlen(reply)-1] = 0; p = reply; while (*p) { if (*p == '\\') { memmove(p, p + 1, strlen(p)); } else { p++; } } *result = reply; return 0; } lastpass-cli-1.5.0/endpoints.h000066400000000000000000000050771462143212600163020ustar00rootroot00000000000000#ifndef ENDPOINTS_H #define ENDPOINTS_H #include "session.h" #include "blob.h" #include "kdf.h" #include unsigned int lastpass_iterations(const char *username); struct session *lastpass_login(const char *username, const char hash[KDF_HEX_LEN], const unsigned char key[KDF_HASH_LEN], int iterations, char **error_message, bool trust); void lastpass_logout(const struct session *session); struct blob *lastpass_get_blob(const struct session *session, const unsigned char key[KDF_HASH_LEN]); unsigned long long lastpass_get_blob_version(struct session *session, unsigned const char key[KDF_HASH_LEN]); void lastpass_remove_account(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const struct account *account, struct blob *blob); void lastpass_update_account(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const struct account *account, struct blob *blob); void lastpass_log_access(enum blobsync sync, const struct session *session, unsigned const char key[KDF_HASH_LEN], const struct account *account); int lastpass_share_getinfo(const struct session *session, const char *shareid, struct list_head *users); int lastpass_share_user_add(const struct session *session, struct share *share, struct share_user *user); int lastpass_share_user_del(const struct session *session, const char *shareid, struct share_user *user); int lastpass_share_user_mod(const struct session *session, struct share *share, struct share_user *user); int lastpass_share_move(const struct session *session, struct account *account, struct share *orig_folder); int lastpass_share_create(const struct session *session, const char *sharename); int lastpass_share_delete(const struct session *session, struct share *share); int lastpass_share_get_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *ret_limit); int lastpass_share_set_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *limit); int lastpass_pwchange_start(const struct session *session, const char *username, const char hash[KDF_HEX_LEN], struct pwchange_info *pwchange_info); int lastpass_pwchange_complete(const struct session *session, const char *username, const char *enc_username, const char new_hash[KDF_HEX_LEN], int new_iterations, struct pwchange_info *pwchange_info); int lastpass_upload(const struct session *session, struct list_head *accounts); int lastpass_load_attachment(const struct session *session, const char *shareid, struct attach *attach, char **result); #endif lastpass-cli-1.5.0/feature-flag.c000066400000000000000000000062501462143212600166260ustar00rootroot00000000000000/* * feature flag handling routines * * Copyright (C) 2024 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 "xml.h" #include "util.h" #include "config.h" #include #define SESSION_FF_URL_ENCRYPTION "session_ff_url_encryption" #define SESSION_FF_URL_LOGGING "session_ff_url_logging" void feature_flag_load_xml_attr(struct feature_flag *feature_flag, xmlDoc *doc, xmlAttrPtr attr) { if (!xmlStrcmp(attr->name, BAD_CAST "url_encryption")) { feature_flag->url_encryption_enabled = !strcmp((char *)xmlNodeListGetString(doc, attr->children, 1), "1"); } if (!xmlStrcmp(attr->name, BAD_CAST "url_logging")) { feature_flag->url_logging_enabled = !strcmp((char *)xmlNodeListGetString(doc, attr->children, 1), "1"); } } void feature_flag_save(const struct feature_flag *feature_flag, unsigned const char key[KDF_HASH_LEN]) { config_write_encrypted_string(SESSION_FF_URL_ENCRYPTION, feature_flag->url_encryption_enabled ? "1" : "0", key); config_write_encrypted_string(SESSION_FF_URL_LOGGING, feature_flag->url_logging_enabled ? "1" : "0", key); } void feature_flag_load(struct feature_flag *feature_flag, unsigned const char key[KDF_HASH_LEN]) { char *ff_url_encryption = config_read_encrypted_string(SESSION_FF_URL_ENCRYPTION, key); if (ff_url_encryption != NULL) { feature_flag->url_encryption_enabled = !strcmp(ff_url_encryption, "1"); } char *ff_url_logging = config_read_encrypted_string(SESSION_FF_URL_LOGGING, key); if (ff_url_logging != NULL) { feature_flag->url_logging_enabled = !strcmp(ff_url_logging, "1"); } } void feature_flag_cleanup() { config_unlink(SESSION_FF_URL_ENCRYPTION); config_unlink(SESSION_FF_URL_LOGGING); } lastpass-cli-1.5.0/feature-flag.h000066400000000000000000000010001462143212600166170ustar00rootroot00000000000000#ifndef FEATUREFLAG_H #define FEATUREFLAG_H #include #include struct feature_flag { bool url_encryption_enabled; bool url_logging_enabled; }; void feature_flag_load_xml_attr(struct feature_flag *feature_flag, xmlDoc *doc, xmlAttrPtr attr); void feature_flag_save(const struct feature_flag *feature_flag, unsigned const char key[KDF_HASH_LEN]); void feature_flag_load(struct feature_flag *feature_flag, unsigned const char key[KDF_HASH_LEN]); void feature_flag_cleanup(); #endif lastpass-cli-1.5.0/format.c000066400000000000000000000124541462143212600155570ustar00rootroot00000000000000/* * printf-like formatting routines * * Copyright (C) 2014-2018 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 "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include #include #include #include #include char *get_display_fullname(struct account *account) { char *fullname = NULL; if (account->share || strcmp(account->group, "")) fullname = xstrdup(account->fullname); else xasprintf(&fullname, "(none)/%s", account->fullname); return fullname; } char *format_timestamp(char *timestamp, bool utc) { char temp[60]; struct tm *ts_tm; if (!timestamp) return xstrdup(""); time_t ts_time_t = (time_t) strtoul(timestamp, NULL, 10); if (ts_time_t == 0) return xstrdup(""); if (utc) ts_tm = gmtime(&ts_time_t); else ts_tm = localtime(&ts_time_t); strftime(temp, sizeof(temp), "%Y-%m-%d %H:%M", ts_tm); return xstrdup(temp); } static void append_str(struct buffer *buf, char *str, bool add_slash) { if (!str || !strlen(str)) return; buffer_append_str(buf, str); if (add_slash) buffer_append_char(buf, '/'); } static void format_account_item(struct buffer *buf, char fmt, struct account *account, bool add_slash) { _cleanup_free_ char *name = NULL; _cleanup_free_ char *ts = NULL; switch (fmt) { case 'i': /* id */ append_str(buf, account->id, add_slash); break; case 'n': /* shortname */ append_str(buf, account->name, add_slash); break; case 'N': /* fullname */ name = get_display_fullname(account); append_str(buf, name, add_slash); break; case 'u': /* username */ append_str(buf, account->username, add_slash); break; case 'p': /* password */ append_str(buf, account->password, add_slash); break; case 'm': /* mtime */ ts = format_timestamp(account->last_modified_gmt, true); append_str(buf, ts, add_slash); break; case 'U': /* last touch time */ ts = format_timestamp(account->last_touch, false); append_str(buf, ts, add_slash); break; case 's': /* sharename */ if (account->share) append_str(buf, account->share->name, add_slash); break; case 'g': /* group name */ append_str(buf, account->group, add_slash); break; case 'l': /* URL */ append_str(buf, account->url, add_slash); break; default: break; } } void format_field_item(struct buffer *buf, char fmt, char *field_name, char *field_value, bool add_slash) { if (fmt == 'n' && field_name) { append_str(buf, field_name, add_slash); } else if (fmt == 'v' && field_value) { append_str(buf, field_value, add_slash); } } void format_field(struct buffer *buf, const char *format_str, struct account *account, char *field_name, char *field_value) { const char *p = format_str; bool in_format = false; bool add_slash = false; while (*p) { char ch = *p++; if (!in_format) { if (ch == '%') in_format = true; else buffer_append_char(buf, ch); continue; } /* expand format specifiers */ switch (ch) { case '%': /* %% escape */ buffer_append_char(buf, ch); break; case '/': /* append trailing slash, if nonempty */ add_slash = true; continue; case 'f': /* field name/value */ if (!*p) { buffer_append_char(buf, '%'); buffer_append_char(buf, ch); break; } ch = *p++; format_field_item(buf, ch, field_name, field_value, add_slash); break; case 'a': /* account item */ if (!*p) { buffer_append_char(buf, '%'); buffer_append_char(buf, ch); break; } ch = *p++; format_account_item(buf, ch, account, add_slash); break; default: buffer_append_char(buf, '%'); buffer_append_char(buf, ch); } add_slash = false; in_format = false; } } void format_account(struct buffer *buf, const char *fmt_str, struct account *account) { format_field(buf, fmt_str, account, NULL, NULL); } lastpass-cli-1.5.0/format.h000066400000000000000000000005531462143212600155610ustar00rootroot00000000000000#ifndef FORMAT_H #define FORMAT_H char *get_display_fullname(struct account *account); char *format_timestamp(char *timestamp, bool utc); void format_account(struct buffer *buf, char *format_str, struct account *account); void format_field(struct buffer *buf, char *format_str, struct account *account, char *field_name, char *field_value); #endif lastpass-cli-1.5.0/http.c000066400000000000000000000226061462143212600152460ustar00rootroot00000000000000/* * http posting routines * * Copyright (C) 2014-2018 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 "http.h" #include "log.h" #include "util.h" #include "version.h" #include "pins.h" #include "cipher.h" #include #include #include #include #include #include #include struct mem_chunk { char *ptr; size_t len; }; #ifndef TEST_BUILD static bool interrupted = false; static sig_t previous_handler = SIG_DFL; static void interruption_detected(int signal) { UNUSED(signal); interrupted = true; } static void set_interrupt_detect(void) { interrupted = false; previous_handler = signal(SIGINT, interruption_detected); } static void unset_interrupt_detect(void) { interrupted = false; signal(SIGINT, previous_handler); } static int check_interruption(void *p, double dltotal, double dlnow, double ultotal, double ulnow) { UNUSED(p); UNUSED(dltotal); UNUSED(dlnow); UNUSED(ultotal); UNUSED(ulnow); return interrupted; } static size_t write_data(char *ptr, size_t size, size_t nmemb, void *data) { size_t len, new_len; struct mem_chunk *mem = (struct mem_chunk *)data; if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return 0; } len = size * nmemb; new_len = len + mem->len + 1; if (new_len <= mem->len || new_len <= len || new_len < 1) { errno = ENOMEM; return 0; } mem->ptr = xrealloc(mem->ptr, new_len); memcpy(mem->ptr + mem->len, ptr, len); mem->len += len; mem->ptr[mem->len] = '\0'; return len; } static char *hash_subject_pubkey_info(X509 *cert) { _cleanup_free_ unsigned char *spki = NULL; char *hash = NULL; EVP_PKEY *pkey; int len; pkey = X509_get_pubkey(cert); if (!pkey) return NULL; len = i2d_PUBKEY(pkey, &spki); if (len <= 0) goto free_pkey; hash = cipher_sha256_b64(spki, len); free_pkey: EVP_PKEY_free(pkey); return hash; } static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { int i, j; /* * Preverify checks the platform's certificate store; don't * allow any chain that doesn't already validate according to * that. */ if (!preverify_ok) return 0; /* check each certificate in the chain against our built-in pinlist. */ STACK_OF(X509) *chain = X509_STORE_CTX_get_chain(ctx); if (!chain) die("No certificate chain available"); bool found = false; for (i=0; i < sk_X509_num(chain); i++) { _cleanup_free_ char *spki_hash = NULL; spki_hash = hash_subject_pubkey_info(sk_X509_value(chain, i)); if (!spki_hash) continue; for (j=0; j < (int) ARRAY_SIZE(PK_PINS); j++) { if (strcmp(PK_PINS[j], spki_hash) == 0) { found = true; break; } } } return found; } static CURLcode pin_keys(CURL *curl, void *sslctx, void *parm) { UNUSED(curl); UNUSED(parm); SSL_CTX_set_verify((SSL_CTX *)sslctx, SSL_VERIFY_PEER, verify_callback); return CURLE_OK; } #endif static void vhttp_post_add_params(struct http_param_set *param_set, va_list args) { char **argv_ptr; char *arg; size_t count = 0; if (!param_set->argv) { param_set->n_alloced = 2; param_set->argv = xcalloc(param_set->n_alloced, sizeof(char *)); } argv_ptr = param_set->argv; while (*argv_ptr) { argv_ptr++; count++; } while ((arg = va_arg(args, char *))) { if (count == param_set->n_alloced - 1) { param_set->n_alloced += 2; param_set->argv = xreallocarray(param_set->argv, param_set->n_alloced, sizeof(char *)); argv_ptr = ¶m_set->argv[count]; } *argv_ptr++ = arg; count++; } *argv_ptr = 0; } int http_init() { curl_global_cleanup(); return curl_global_init(CURL_GLOBAL_DEFAULT); } void http_post_add_params(struct http_param_set *param_set, ...) { va_list args; va_start(args, param_set); vhttp_post_add_params(param_set, args); va_end(args); } char *http_post_lastpass(const char *page, const struct session *session, size_t *final_len, ...) { va_list args; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; va_start(args, final_len); vhttp_post_add_params(¶ms, args); char *result = http_post_lastpass_param_set(page, session, final_len, ¶ms); free(params.argv); 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; _cleanup_free_ char *postdata = NULL; _cleanup_free_ char *cookie = NULL; _cleanup_fclose_ FILE *logstream = NULL; char *param, *encoded_param; CURL *curl = NULL; char separator; size_t len, new_len; int ret; struct mem_chunk result; const char *login_server; /* if we have a session, use that server, otherwise use whatever was passed */ login_server = session ? session->server : server; /* if nothing passed, use lastpass */ if (!login_server) login_server = LASTPASS_SERVER; xasprintf(&url, "https://%s/%s", login_server, page); lpass_log(LOG_DEBUG, "Making request to %s\n", url); curl = curl_easy_init(); if (!curl) die("Could not init curl"); len = 0; for (separator = '=', param = *argv; param; separator = (separator == '=') ? '&' : '=', param = *(++argv)) { encoded_param = curl_easy_escape(curl, param, 0); if (!encoded_param) die("Could not escape %s with curl", param); new_len = strlen(encoded_param) + 1 /* separator */; postdata = xrealloc(postdata, len + new_len + 1 /* null */); snprintf(postdata + len, new_len + 1, "%s%c", encoded_param, separator); len += new_len; curl_free(encoded_param); } if (len && postdata) postdata[len - 1] = '\0'; memset(&result, 0, sizeof(result)); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_USERAGENT, LASTPASS_CLI_USERAGENT); /* TODO: Make this optional via either env vars and/or an option for * lpass -4 or lpass -6 */ curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); if (lpass_log_level() >= LOG_VERBOSE) { logstream = lpass_log_open(); if (logstream) { curl_easy_setopt(curl, CURLOPT_STDERR, logstream); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); } } #if defined(DO_NOT_ENABLE_ME_MITM_PROXY_FOR_DEBUGGING_ONLY) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_PROXY, "http://localhost:8080"); #else curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, pin_keys); #endif curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, check_interruption); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); if (postdata) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata); if (session) { xasprintf(&cookie, "PHPSESSID=%s", session->sessionid); curl_easy_setopt(curl, CURLOPT_COOKIE, cookie); } set_interrupt_detect(); ret = curl_easy_perform(curl); unset_interrupt_detect(); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code); curl_easy_cleanup(curl); *curl_ret = ret; if (ret != CURLE_OK) { result.len = 0; free(result.ptr); result.ptr = NULL; } else if (!result.ptr) result.ptr = xstrdup(""); if (final_len) *final_len = result.len; 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) { char *result; int ret; long http_code; result = http_post_lastpass_v_noexit(server, page, session, final_len, argv, &ret, &http_code); if (ret != CURLE_OK && ret != CURLE_ABORTED_BY_CALLBACK) die("%s.", curl_easy_strerror(ret)); return result; } char *http_post_lastpass_param_set(const char *page, const struct session *session, size_t *final_len, struct http_param_set *param_set) { return http_post_lastpass_v(NULL, page, session, final_len, param_set->argv); } lastpass-cli-1.5.0/http.h000066400000000000000000000016371462143212600152540ustar00rootroot00000000000000#ifndef HTTP_H #define HTTP_H #include #include #include #include "session.h" struct http_param_set { char **argv; size_t n_alloced; }; #define LASTPASS_SERVER "lastpass.com" #define HTTP_ERROR_CODE CURLE_HTTP_RETURNED_ERROR #define HTTP_ERROR_CONNECT CURLE_SSL_CONNECT_ERROR int http_init(); void http_post_add_params(struct http_param_set *params, ...); char *http_post_lastpass(const char *page, const struct session *session, size_t *len, ...); char *http_post_lastpass_v(const char *server, const char *page, const struct session *session, size_t *len, char **argv); char *http_post_lastpass_param_set(const char *page, const struct session *session, size_t *len, struct http_param_set *params); 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); #endif lastpass-cli-1.5.0/json-format.c000066400000000000000000000142641462143212600165270ustar00rootroot00000000000000/* * json formatting routines * * Copyright (C) 2018 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 "blob.h" #include "util.h" #include "list.h" #include "json-format.h" #define INDENT_SPACES 2 static void json_format(struct json_field *field, int level, bool is_last); static void print_json_quoted_string(const char *str) { const char *ptr = NULL; putchar ('"'); for (ptr = str; *ptr; ptr++) { /* escape some characters according to http://www.ietf.org/rfc/rfc4627.txt */ switch (*ptr) { case '\b': putchar('\\'); putchar('b'); break; case '\f': putchar('\\'); putchar('f'); break; case '\n': putchar('\\'); putchar('n'); break; case '\r': putchar('\\'); putchar('r'); break; case '\t': putchar('\\'); putchar('t'); break; case '\\': putchar('\\'); putchar('\\'); break; case '"': putchar('\\'); putchar('"'); break; default: if ((*ptr) < ' ') { printf("\\u%04x", *ptr); } else { putchar(*ptr); } break; } } putchar ('"'); } static void indent(int level) { for (int i = 0; i < level * INDENT_SPACES; i++) putchar(' '); } static void json_format_string(struct json_field *field, int level, bool is_last) { indent(level); if (field->name) { print_json_quoted_string(field->name); printf(": "); } print_json_quoted_string(field->u.string_value); printf("%c\n", is_last ? ' ' : ','); } static void json_format_array(struct json_field *field, int level, bool is_last) { struct json_field *child; struct json_field *last; if (field->type != JSON_ARRAY) return; last = list_last_entry_or_null(&field->children, struct json_field, siblings); indent(level); if (field->name) { print_json_quoted_string(field->name); printf(": "); } printf ("[\n"); list_for_each_entry(child, &field->children, siblings) { json_format(child, level + 1, child == last); } indent(level); printf ("]%c\n", is_last ? ' ' : ','); } static void json_format_object(struct json_field *field, int level, bool is_last) { struct json_field *child; struct json_field *last; if (field->type != JSON_OBJECT) return; last = list_last_entry_or_null(&field->children, struct json_field, siblings); indent(level); if (field->name) { print_json_quoted_string(field->name); printf(": "); } printf ("{\n"); list_for_each_entry(child, &field->children, siblings) { json_format(child, level + 1, child == last); } indent(level); printf ("}%c\n", is_last ? ' ' : ','); } static void json_format(struct json_field *field, int level, bool is_last) { switch (field->type) { case JSON_OBJECT: json_format_object(field, level, is_last); break; case JSON_STRING: json_format_string(field, level, is_last); break; case JSON_ARRAY: json_format_array(field, level, is_last); break; default: printf("unhandled type: %d\n", field->type); } } static void json_add_string_field(struct json_field *object, const char *name, const char *value) { if (!value) return; struct json_field *field = xmalloc(sizeof(struct json_field)); field->name = name; field->type = JSON_STRING; field->u.string_value = value; list_add_tail(&field->siblings, &object->children); } static void account_to_json_field(struct account *account, struct json_field *obj) { obj->name = NULL; obj->type = JSON_OBJECT; json_add_string_field(obj, "id", account->id); json_add_string_field(obj, "name", account->name); json_add_string_field(obj, "fullname", account->fullname); json_add_string_field(obj, "username", account->username); json_add_string_field(obj, "password", account->password); json_add_string_field(obj, "last_modified_gmt", account->last_modified_gmt); json_add_string_field(obj, "last_touch", account->last_touch); if (account->share) json_add_string_field(obj, "share", account->share->name); json_add_string_field(obj, "group", account->group); json_add_string_field(obj, "url", account->url); json_add_string_field(obj, "note", account->note); } static void json_free_account_fields(struct json_field *obj) { struct json_field *field, *tmp; list_for_each_entry_safe(field, tmp, &obj->children, siblings) { free(field); } } void json_format_account_list(struct list_head *accounts) { struct account *account; struct json_field *child, *tmp; struct json_field array = { .type = JSON_ARRAY }; INIT_LIST_HEAD(&array.children); list_for_each_entry(account, accounts, match_list) { struct json_field *object = xmalloc(sizeof(*object)); object->name = NULL; object->type = JSON_OBJECT; INIT_LIST_HEAD(&object->children); account_to_json_field(account, object); list_add_tail(&object->siblings, &array.children); } json_format(&array, 0, true); list_for_each_entry_safe(child, tmp, &array.children, siblings) { json_free_account_fields(child); free(child); } } lastpass-cli-1.5.0/json-format.h000066400000000000000000000014211462143212600165230ustar00rootroot00000000000000#ifndef JSON_FORMAT_H #define JSON_FORMAT_H enum json_field_type { JSON_STRING, JSON_ARRAY, JSON_OBJECT }; /* * Stores a JSON record to be formatted. * * Type field dictates which union is used for primitive types. * For both objects and arrays, children holds the items that are * assigned to the container, and those fields are linked via * siblings pointer. The only difference between an array and an * object is that the children will not have names in the case of * an array. */ struct json_field { const char *name; enum json_field_type type; /* list of properties */ struct list_head children; struct list_head siblings; union { const char *string_value; } u; }; void json_format_account_list(struct list_head *accounts); #endif /* JSON_FORMAT_H */ lastpass-cli-1.5.0/kdf.c000066400000000000000000000103551462143212600150310ustar00rootroot00000000000000/* * key derivation routines * * Copyright (C) 2014-2018 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 "kdf.h" #include "util.h" #include #include #include #include #include #if defined(__APPLE__) && defined(__MACH__) #include #include static void pbkdf2_hash(const char *username, size_t username_len, const char *password, size_t password_len, int iterations, unsigned char hash[KDF_HASH_LEN]) { if (CCKeyDerivationPBKDF(kCCPBKDF2, password, password_len, (const uint8_t *)username, username_len, kCCPRFHmacAlgSHA256, iterations, hash, KDF_HASH_LEN) == kCCParamError) die("Failed to compute PBKDF2 for %s", username); } #else #include "pbkdf2.h" static void pbkdf2_hash(const char *username, size_t username_len, const char *password, size_t password_len, int iterations, unsigned char hash[KDF_HASH_LEN]) { if (!PKCS5_PBKDF2_HMAC(password, password_len, (const unsigned char *)username, username_len, iterations, EVP_sha256(), KDF_HASH_LEN, hash)) die("Failed to compute PBKDF2 for %s", username); } #endif static void sha256_hash(const char *username, size_t username_len, const char *password, size_t password_len, unsigned char hash[KDF_HASH_LEN]) { SHA256_CTX sha256; if (!SHA256_Init(&sha256)) goto die; if (!SHA256_Update(&sha256, username, username_len)) goto die; if (!SHA256_Update(&sha256, password, password_len)) goto die; if (!SHA256_Final(hash, &sha256)) goto die; return; die: die("Failed to compute SHA256 for %s", username); } void kdf_login_key(const char *username, const char *password, int iterations, char hex[KDF_HEX_LEN]) { unsigned char hash[KDF_HASH_LEN]; size_t password_len; _cleanup_free_ char *user_lower = xstrlower(username); password_len = strlen(password); if (iterations < 1) iterations = 1; if (iterations == 1) { sha256_hash(user_lower, strlen(user_lower), password, password_len, hash); bytes_to_hex(hash, &hex, KDF_HASH_LEN); sha256_hash(hex, KDF_HEX_LEN - 1, password, password_len, hash); } else { pbkdf2_hash(user_lower, strlen(user_lower), password, password_len, iterations, hash); pbkdf2_hash(password, password_len, (char *)hash, KDF_HASH_LEN, 1, hash); } bytes_to_hex(hash, &hex, KDF_HASH_LEN); mlock(hex, KDF_HEX_LEN); } void kdf_decryption_key(const char *username, const char *password, int iterations, unsigned char hash[KDF_HASH_LEN]) { _cleanup_free_ char *user_lower = xstrlower(username); if (iterations < 1) iterations = 1; if (iterations == 1) sha256_hash(user_lower, strlen(user_lower), password, strlen(password), hash); else pbkdf2_hash(user_lower, strlen(user_lower), password, strlen(password), iterations, hash); mlock(hash, KDF_HASH_LEN); } lastpass-cli-1.5.0/kdf.h000066400000000000000000000006111462143212600150300ustar00rootroot00000000000000#ifndef KDF_H #define KDF_H #include #include #define KDF_HASH_LEN SHA256_DIGEST_LENGTH #define KDF_HEX_LEN (KDF_HASH_LEN * 2 + 1) void kdf_login_key(const char *username, const char *password, int iterations, char hex[KDF_HEX_LEN]); void kdf_decryption_key(const char *username, const char *password, int iterations, unsigned char hash[KDF_HASH_LEN]); #endif lastpass-cli-1.5.0/list.h000066400000000000000000000311671462143212600152510ustar00rootroot00000000000000#ifndef _LIST_H #define _LIST_H /* Stripped down implementation of linked list taken * from the Linux Kernel. */ /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ struct list_head { struct list_head *next, *prev; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } #ifndef __clang_analyzer__ #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) #else #define LIST_POISON1 NULL #define LIST_POISON2 NULL #endif /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(const struct list_head *head) { return head->next == head; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty() on entry does not return true after this, the entry is * in an undefined state. */ static inline void __list_del_entry(struct list_head *entry) { __list_del(entry->prev, entry->next); } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } /** * list_entry - get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_first_entry - get the first 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 list is expected to be not empty. */ #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) /** * list_last_entry - 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 list is expected to be not empty. */ #define list_last_entry(ptr, type, member) \ list_entry((ptr)->prev, type, member) /** * list_first_entry_or_null - get the first 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_first_entry_or_null(ptr, type, member) \ (!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. */ #define list_next_entry(pos, member) \ list_entry((pos)->member.next, typeof(*(pos)), member) /** * list_prev_entry - get the prev element in list * @pos: the type * to cursor * @member: the name of the list_struct within the struct. */ #define list_prev_entry(pos, member) \ list_entry((pos)->member.prev, typeof(*(pos)), member) /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) /** * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_prev_safe(pos, n, head) \ for (pos = (head)->prev, n = pos->prev; \ pos != (head); \ pos = n, n = pos->prev) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member); \ &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_last_entry(head, typeof(*pos), member); \ &pos->member != (head); \ pos = list_prev_entry(pos, member)) /** * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() * @pos: the type * to use as a start point * @head: the head of the list * @member: the name of the list_struct within the struct. * * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). */ #define list_prepare_entry(pos, head, member) \ ((pos) ? : list_entry(head, typeof(*pos), member)) /** * list_for_each_entry_continue - continue iteration over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Continue to iterate over list of given type, continuing after * the current position. */ #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_next_entry(pos, member); \ &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_continue_reverse - iterate backwards from the given point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Start to iterate over list of given type backwards, continuing after * the current position. */ #define list_for_each_entry_continue_reverse(pos, head, member) \ for (pos = list_prev_entry(pos, member); \ &pos->member != (head); \ pos = list_prev_entry(pos, member)) /** * list_for_each_entry_from - iterate over list of given type from the current point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing from current position. */ #define list_for_each_entry_from(pos, head, member) \ for (; &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member), \ n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_continue - continue list iteration safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing after current point, * safe against removal of list entry. */ #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_next_entry(pos, member), \ n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_from - iterate over list from current point safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type from current point, safe against * removal of list entry. */ #define list_for_each_entry_safe_from(pos, n, head, member) \ for (n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate backwards over list of given type, safe against removal * of list entry. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_last_entry(head, typeof(*pos), member), \ n = list_prev_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_prev_entry(n, member)) /** * list_safe_reset_next - reset a stale list_for_each_entry_safe loop * @pos: the loop cursor used in the list_for_each_entry_safe loop * @n: temporary storage used in list_for_each_entry_safe * @member: the name of the list_struct within the struct. * * list_safe_reset_next is not safe to use in general if the list may be * modified concurrently (eg. the lock is dropped in the loop body). An * exception to this is if the cursor element (pos) is pinned in the list, * and list_safe_reset_next is called after re-taking the lock and before * completing the current iteration of the loop body. */ #define list_safe_reset_next(pos, n, member) \ n = list_next_entry(pos, member) #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #ifndef __clang_analyzer__ #define container_of(ptr, type, member) __extension__({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #else #define container_of(ptr, type, member) __extension__({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - (char *)offsetof(type,member) );}) #endif #endif lastpass-cli-1.5.0/log.c000066400000000000000000000051241462143212600150440ustar00rootroot00000000000000/* * logging functions * * Copyright (C) 2016 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 "util.h" #include "log.h" #include "config.h" #include #include #define TIME_FMT "%lld.%06lld" #define TIME_ARGS(tv) ((long long)(tv)->tv_sec), ((long long)(tv)->tv_usec) int lpass_log_level() { char *log_level_str; int level; log_level_str = getenv("LPASS_LOG_LEVEL"); if (!log_level_str) return LOG_NONE; level = strtoul(log_level_str, NULL, 10); return (enum log_level) level; } void lpass_log(enum log_level level, char *fmt, ...) { struct timeval tv; struct timezone tz; va_list ap; _cleanup_fclose_ FILE *fp = NULL; int req_level = lpass_log_level(); if (req_level < level) return; fp = lpass_log_open(); if (!fp) return; gettimeofday(&tv, &tz); fprintf(fp, "<%d> [" TIME_FMT "] ", level, TIME_ARGS(&tv)); va_start(ap, fmt); vfprintf(fp, fmt, ap); va_end(ap); fflush(fp); } FILE *lpass_log_open() { _cleanup_free_ char *upload_log_path = NULL; if (lpass_log_level() < 0) return NULL; upload_log_path = config_path("lpass.log"); return fopen(upload_log_path, "a"); } lastpass-cli-1.5.0/log.h000066400000000000000000000011751462143212600150530ustar00rootroot00000000000000#ifndef __LOG_H #define __LOG_H /* * Loglevels for ~/.lpass/lpass.log. By default, nothing is logged, but * setting LPASS_LOG_LEVEL to a positive value will turn on logging. * * NOTE: debug and verbose logs can include sensitive information such as * session IDs in the clear. Do NOT post logs in public without * scrubbing them first! */ enum log_level { LOG_NONE = -1, LOG_ERROR = 3, LOG_WARNING = 4, LOG_INFO = 6, LOG_DEBUG = 7, LOG_VERBOSE = 8, /* _everything_ including CURL verbose logs */ }; int lpass_log_level(); void lpass_log(enum log_level level, char *fmt, ...); FILE *lpass_log_open(); #endif lastpass-cli-1.5.0/lpass.1.txt000066400000000000000000000336621462143212600161510ustar00rootroot00000000000000:man source: lpass :man manual: lpass LPASS(1) ======== NAME ---- lpass - command line interface for LastPass SYNOPSIS -------- [verse] *lpass* [ --version, -v | --help, -h ] *lpass* [] DESCRIPTION ----------- 'lpass' is a simple command line interface to LastPass. It is comprised of several subcommands: [verse] lpass *login* [--trust] [--plaintext-key [--force, -f]] [--color=auto|never|always] USERNAME lpass *logout* [--force, -f] [--color=auto|never|always] lpass *passwd* lpass *show* [--sync=auto|now|no] [--clip, -c] [--quiet, -q] [--expand-multi, -x] [--json, -j] [--all|--username|--password|--url|--notes|--field=FIELD|--id|--name|--attach=ATTACHID] [--basic-regexp, -G|--fixed-strings, -F] [--color=auto|never|always] {NAME|UNIQUEID}* lpass *ls* [--sync=auto|now|no] [--long, -l] [-m] [-u] [--color=auto|never|always] [GROUP] lpass *mv* [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID} GROUP lpass *add* [--sync=auto|now|no] [--non-interactive] {--name|--username, -u|--password, -p|--url|--notes|--field=FIELD|--note-type=NOTETYPE} [--color=auto|never|always] {NAME|UNIQUEID} lpass *edit* [--sync=auto|now|no] [--non-interactive] {--name|--username, -u|--password, -p|--url|--notes|--field=FIELD} [--color=auto|never|always] {NAME|UNIQUEID} lpass *generate* [--sync=auto|now|no] [--clip, -c] [--username=USERNAME] [--url=URL] [--no-symbols] [--color=auto|never|always] {NAME|UNIQUEID} LENGTH lpass *duplicate* [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID} lpass *rm* [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID} lpass *status* [--quiet, -q] [--color=auto|never|always] lpass *sync* [--background, -b] [--color=auto|never|always] lpass *import* [--sync=auto|now|no] [--keep-dupes] [FILENAME] 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 lpass *share* *userdel* SHARE USERNAME lpass *share* *create* SHARE lpass *share* *rm* SHARE lpass *share* *limit* [--deny|--allow] [--add|--rm|--clear] SHARE USERNAME [sites] Synchronization ~~~~~~~~~~~~~~~ The '--sync' options control when the current operation involves a synchronization with the server. If 'now' is set, and the command makes a change, the change is synchronized before the command exits. If 'now' is set, and the command displays a value, the local cache is synchronized before the value is shown. If 'now' is set, and the command is otherwise successful, but synchronization fails, the command will return an error. If 'auto' is set, and the command makes a change, the change is synchronized to the server in the background. If 'auto' is set, and the command displays a value, the local cache is synchronized before the value is shown only if the local cache is more than 5 seconds (or 'LPASS_AUTO_SYNC_TIME' seconds, if set) old. If 'no' is set, the command will not interact with the server, unless there is a current upload queue being processed. Any local changes that are not synchronized with the server will exist in a queue of timestamped requests which will be synchronized on the next occurring synchronization. The 'sync' command forces a synchronization of the local cache with the LastPass servers, and does not exit until the local cache is synchronized or until an error occurs. Alternatively, if '--background' is specified, the synchronization occurs in a daemonized process. Agent ~~~~~ An agent process will be spawned in the background on a first successful command, and all subsequent commands will use the agent for decryption, instead of asking a user for a password. The agent will quit after one hour, unless the 'LPASS_AGENT_TIMEOUT' environment variable is set to an alternative number of seconds in which to quit, or 0 to never quit. If the environment variable 'LPASS_AGENT_DISABLE' is set to 1, the agent will not be used. Password Entry ~~~~~~~~~~~~~~ The *pinentry* program, part of *gpg2*(1), may be used for inputting passwords if it is installed. A custom path to the *pinentry* program can be provided by the 'LPASS_PINENTRY' environment variable. If *pinentry* program is unavailable, or if the 'LPASS_DISABLE_PINENTRY' environment variable is set to 1, passwords will be read from standard input and a prompt will be displayed on standard error. The program used for inputting passwords may also be configured by setting the 'LPASS_ASKPASS' environment variable. 'LPASS_ASKPASS' is expected to be a binary that produces a prompt using its first command-line argument, and outputs the entered password to standard out. ssh-askpass implements this protocol, as does the following shell script: [verse] #!/bin/bash echo -n "$*: " >/dev/stderr stty -echo read answer stty echo echo $answer Entry Specification ~~~~~~~~~~~~~~~~~~~ Commands that take a 'UNIQUENAME' will fail if the provided name is used multiple times, and return an error. Commands may alternatively take a 'UNIQUEID', which will be the integer 'ID' provided by LastPass for identifying entries uniquely. Commands that take either a 'NAME' or a 'UNIQUEID' will create a new entry if a 'NAME' is specified and otherwise overwrite an existing entry if 'UNIQUEID' is specified. Logging In ~~~~~~~~~~ The 'login' subcommand will initialize a local cache and configuration folder, then attempt to authenticate itself with the LastPass servers, using the provided command line credentials or by interactively prompting (in the case of multifactor or an unprovided password). The '--trust' option will cause subsequent logins to not require multifactor authentication. If the '--plaintext-key' option is specified, the decryption key will be saved to the hard disk in plaintext. Please note that use of this option is discouraged except in limited situations, as it greatly decreases the security of data. The 'logout' subcommand will remove the local cache and stored encryption keys. It will prompt the user to confirm, unless '--force' is specified. The 'passwd' subcommand may be used to change your LastPass password: it will prompt for the old and new password and then re-encrypt all records with the newly derived key. Viewing ~~~~~~~ The 'show' subcommand will display a password or selected field. By default, the site you specify with the 'show' subcommand must *exactly* match the name of the site. If the '--fixed-strings' or '-F' option is set, then the 'show' subcommand will find a site containing that *exact* substring; if the '--basic-regexp' or '-G' option is set, then the 'show' subcommand will find a site matching a case-insensitive regular expression. By default if your 'show' subcommand matches more than one site, then the 'show' subcommand will generate a warning and display the names of matching sites but no other information. The '--expand-multi' or '-x' option will instead show the requested information from all of the matching sites. The 'ls' subcommand will list names in groups in a tree structure. If the '--long' or '-l' option is set, then also list the last modification time. The '-u' option may be passed to show the last use (last touch) time instead, if available. Both times are in GMT. Passing '--json' to 'show' will generate json output instead of human-readable text. In addition to using the built-in formats, both 'show' and 'ls' subcommands support printf-style format strings by using the '--format' option with the following placeholders: * %ai: account id * %an: account name * %aN: account name including path * %au: account user * %ap: account password * %am: account modification time * %aU: account last touch time * %as: account share name * %ag: account group name * %al: account URL * %fn: field name (for 'show') * %fv: field value (for 'show') A slash can be added between the '%' and the placeholder to indicate that a slash should be appended, only if the printed value is expanded to a non-empty string. For example, this command will properly show the full path to an account: `lpass ls --format="%/as%/ag%an"`. Modifying ~~~~~~~~~ The 'edit' subcommand will edit the selected field. If '--non-interactive' is not set, the selected field will be edited using 'EDITOR'; otherwise the command will accept data until EOF or, unless the notes field is being edited, the first new line. Please note that when editing interactively, the contents of the field may be saved on disk in tmp files or in editor swap files, depending on your system configuration. The 'generate' subcommand will create a randomly generated password for the chosen key name, and optionally add a url and username while inserting the generated password. The 'rm' command will remove the specified entry, and the 'duplicate' command will create a duplicate entry of the one specified, but with a different 'ID'. Backup ~~~~~~ The 'export' subcommand will dump all account information including 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. It is recommended that such backups be encrypted at rest, for example by piping to and from gpg. Shared Folder Commands ~~~~~~~~~~~~~~~~~~~~~~ The 'share' command and its accompanying subcommands can be used to manipulate shared folders, if available to the (enterprise or premium) user. The 'userls', 'useradd', 'usermod', and 'userdel' subcommands may be used to query and modify membership of the shared folder, while the 'create' and 'rm' share subcommands may be used to add new, or delete existing shared folders. The normal 'generate' and 'edit' commands may be used to edit accounts within the shared folder. The 'share limit' command may be used to manipulate account access lists on the share for a specific user. Running with no arguments will display the current access levels for a user. The '--add', '--rm', and '--clear' options may be used to add to, remove from, or reset the list. Passing '--allow' or '--deny' will make the list a whitelist or blacklist, respectively. Clipboard ~~~~~~~~~ Commands that take a '-c' or '--clip' option will copy the output to the clipboard, using *xclip*(1) or *xsel*(1) on X11-based systems, *pbcopy*(1) on OSX, or *putclip* on Cygwin. The command to be used can be overridden by specifying the `LPASS_CLIPBOARD_COMMAND` environment variable. Color Output ~~~~~~~~~~~~ The '--color' option controls colored output to the terminal. By default, commands will use '--color=auto', in which color output is used unless the output is not a tty (for example, when passed to a pipe or file). If 'always' is used, colors are produced regardless of the output detection. If 'never' is used, no color escape sequences are emitted. Configuration ~~~~~~~~~~~~~ 'lpass' stores configuration in the following locations, in descending order of precedence: * The directory '$LPASS_HOME', if set * '$XDG_CONFIG_HOME/lpass', '$XDG_DATA_HOME/lpass', and '$XDG_RUNTIME_DIR/lpass' (or equivalent defaults), if at least '$XDG_RUNTIME_DIR' is set * '$HOME/.lpass' All configuration may be specified via environment variables. Alternatively, a set of environment variable overrides may be specified in '$LPASS_HOME/env' in the form of: [verse] VARIABLE1=VALUE1 VARIABLE2=VALUE2 ... Aliases ~~~~~~~ Default options can be specified for any command by creating command aliases. Command aliases are stored in the configuration directory (see previous section) with files named 'alias.*command*'. For example, to force the 'ls' subcommand to never use color, make an 'alias.ls' file with the appropriate option: ---- echo 'ls --color=never' > ~/.config/lpass/alias.ls ---- Similarly, new subcommands can be created based on built-in subcommands with options. Thus, ---- echo 'show --password -c' > ~/.config/lpass/alias.passclip ---- would create a 'passclip' subcommand that copies your password onto the clipboard. ENVIRONMENT VARIABLES --------------------- The following environment variables may be used for configuration as described in the section above: * 'LPASS_HOME' * 'LPASS_AUTO_SYNC_TIME' * 'LPASS_AGENT_TIMEOUT' * 'LPASS_AGENT_DISABLE' * 'LPASS_PINENTRY' * 'LPASS_DISABLE_PINENTRY' * 'LPASS_ASKPASS' * 'LPASS_CLIPBOARD_COMMAND' EXAMPLES -------- In the following examples, "$" indicates a shell prompt while "#" indicates a comment. ---- # login, generate and retrieve a new password $ lpass login user@example.com $ lpass generate work/email 20 G #include #include #if (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #endif #define CMD(name) { #name, cmd_##name##_usage, cmd_##name } static struct { const char *name; const char *usage; int (*cmd)(int, char**); } commands[] = { CMD(login), CMD(logout), CMD(passwd), CMD(show), CMD(ls), CMD(mv), CMD(add), CMD(edit), CMD(generate), CMD(duplicate), CMD(rm), CMD(status), CMD(sync), CMD(export), CMD(import), CMD(share) }; #undef CMD static void version(void) { terminal_printf("LastPass CLI v" LASTPASS_CLI_VERSION "\n"); } static void help(void) { terminal_printf("Usage:\n"); printf(" %s {--help|--version}\n", ARGV[0]); for (size_t i = 0; i < ARRAY_SIZE(commands); ++i) printf(" %s %s\n", basename(ARGV[0]), commands[i].usage); } static int global_options(int argc, char *argv[]) { static struct option long_options[] = { {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; int option; int option_index; while ((option = getopt_long(argc, argv, "vh", long_options, &option_index)) != -1) { switch (option) { case 'v': version(); return 0; case 'h': version(); printf("\n"); help(); return 0; case '?': help(); return option == 'h'; } } help(); return 1; } static void expand_aliases(int *argc, char ***argv) { int i; const char *alias = (*argv)[0]; char **new_argv = NULL; int argv_alloced; int new_argc = 0; _cleanup_free_ char *config_name; xasprintf(&config_name, "alias.%s", alias); _cleanup_free_ char *alias_val = config_read_string(config_name); if (!alias_val) return; trim(alias_val); /* split commandline and prepend to argv */ argv_alloced = 0; new_argv = xcalloc(*argc + 1, sizeof(*new_argv)); char *tok = strtok(alias_val, " \t"); while (tok) { if (new_argc >= argv_alloced) { argv_alloced += 16; new_argv = xreallocarray(new_argv, argv_alloced + *argc + 1, sizeof(*new_argv)); } new_argv[new_argc++] = xstrdup(tok); tok = strtok(NULL, " \t"); } /* copy in remaining items from argc */ for (i=1; i < *argc; i++) { new_argv[new_argc++] = xstrdup((*argv)[i]); } new_argv[new_argc] = 0; *argv = new_argv; *argc = new_argc; } static int process_command(int argc, char *argv[]) { expand_aliases(&argc, &argv); for (size_t i = 0; i < ARRAY_SIZE(commands); ++i) { if (argc && !strcmp(argv[0], commands[i].name)) return commands[i].cmd(argc, argv); } help(); return 1; } static void load_saved_environment(void) { _cleanup_free_ char *env = NULL; env = config_read_string("env"); if (!env) return; for (char *tok = strtok(env, "\n"); tok; tok = strtok(NULL, "\n")) { char *equals = strchr(tok, '='); if (!equals || !*equals) { warn("The environment line '%s' is invalid.", tok); continue; } *equals = '\0'; if (setenv(tok, equals + 1, true)) warn_errno("The environment line '%s' is invalid.", tok); } } int main(int argc, char *argv[]) { /* For process.h to function. */ ARGC = argc; ARGV = argv; /* Do not remove this umask. Always keep at top. */ umask(0077); if (http_init()) die("Unable to initialize curl"); load_saved_environment(); if (argc >= 2 && argv[1][0] != '-') return process_command(argc - 1, argv + 1); return global_options(argc, argv); } lastpass-cli-1.5.0/notes.c000066400000000000000000000152401462143212600154130ustar00rootroot00000000000000/* * routines for classifying secure notes * * Copyright (C) 2014-2018 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 #include "notes.h" #include "util.h" /* Templates for shared note types */ struct note_template note_templates[] = { [ NOTE_TYPE_AMEX ] = { .name = "American Express", .shortname = "amex", .fields = { NULL }}, [ NOTE_TYPE_BANK ] = { .name = "Bank Account", .shortname = "bank", .fields = { "Bank Name", "Account Type", "Routing Number", "Account Number", "SWIFT Code", "IBAN Number", "Pin", "Branch Address", "Branch Phone", NULL }}, [ NOTE_TYPE_CREDIT ] = { .name = "Credit Card", .shortname = "credit-card", .fields = { "Name on Card", "Type", "Number", "Security Code", "Start Date", "Expiration Date", NULL }}, [ NOTE_TYPE_DATABASE ] = { .name = "Database", .shortname = "database", .fields = { "Type", "Hostname", "Port", "Database", "Username", "Password", "SID", "Alias", NULL }}, [ NOTE_TYPE_DRIVERS_LICENSE ] = { .name = "Driver's License", .shortname = "drivers-license", .fields = { "Number", "Expiration Date", "License Class", "Name", "Address", "City / Town", "State", "ZIP / Postal Code", "Country", "Date of Birth", "Sex", "Height", NULL }}, [ NOTE_TYPE_EMAIL ] = { .name = "Email Account", .shortname = "email", .fields = { "Username", "Password", "Server", "Port", "Type", "SMTP Server", "SMTP Port", NULL }}, [ NOTE_TYPE_HEALTH_INSURANCE ] = { .name = "Health Insurance", .shortname = "health-insurance", .fields = { "Company", "Company Phone", "Policy Type", "Policy Number", "Group ID", "Member Name", "Member ID", "Physician Name", "Physician Phone", "Physician Address", "Co-pay", NULL }}, [ NOTE_TYPE_IM ] = { .name = "Instant Messenger", .shortname = "im", .fields = { "Type", "Username", "Password", "Server", "Port", NULL }}, [ NOTE_TYPE_INSURANCE ] = { .name = "Insurance", .shortname = "insurance", .fields = { "Company", "Policy Type", "Policy Number", "Expiration", "Agent Name", "Agent Phone", "URL", NULL }}, [ NOTE_TYPE_MASTERCARD ] = { .name = "Mastercard", .shortname = "mastercard", .fields = { NULL }}, [ NOTE_TYPE_MEMBERSHIP ] = { .name = "Membership", .shortname = "membership", .fields = { "Organization", "Membership Number", "Member Name", "Start Date", "Expiration Date", "Website", "Telephone", "Password", NULL }}, [ NOTE_TYPE_PASSPORT ] = { .name = "Passport", .shortname = "passport", .fields = { "Type", "Name", "Country", "Number", "Sex", "Nationality", "Date of Birth", "Issued Date", "Expiration Date", NULL }}, [ NOTE_TYPE_SERVER ] = { .name = "Server", .shortname = "server", .fields = { "Hostname", "Username", "Password", NULL }}, [ NOTE_TYPE_SSN ] = { .name = "Social Security", .shortname = "ssn", .fields = { "Name", "Number", NULL }}, [ NOTE_TYPE_SOFTWARE_LICENSE ] = { .name = "Software License", .shortname = "software-license", .fields = { "License Key", "Licensee", "Version", "Publisher", "Support Email", "Website", "Price", "Purchase Date", "Order Number", "Number of Licenses", "Order Total", NULL }}, [ NOTE_TYPE_SSH_KEY ] = { .name = "SSH Key", .shortname = "ssh-key", .fields = { "Bit Strength", "Format", "Passphrase", "Private Key", "Public Key", "Hostname", "Date", NULL }}, [ NOTE_TYPE_VISA ] = { .name = "VISA", .shortname = "visa", .fields = { NULL }}, [ NOTE_TYPE_WIFI ] = { .name = "Wi-Fi Password", .shortname = "wifi", .fields = { "SSID", "Password", "Connection Type", "Connection Mode", "Authentication", "Encryption", "Use 802.1X", "FIPS Mode", "Key Type", "Protected", "Key Index", NULL }}, }; const char *notes_get_name(enum note_type note_type) { if (note_type <= NOTE_TYPE_NONE || note_type >= NUM_NOTE_TYPES) return ""; return note_templates[note_type].name; } bool note_field_is_multiline(enum note_type note_type, const char *field) { return note_type == NOTE_TYPE_SSH_KEY && !strcmp(field, "Private Key"); } 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); size_t i; for (i = 0; i < NUM_NOTE_TYPES; i++) { if (!strcasecmp(type_str, note_templates[i].shortname)) return i; } return NOTE_TYPE_NONE; } enum note_type notes_get_type_by_name(const char *type_str) { size_t i; for (i = 0; i < NUM_NOTE_TYPES; i++) { if (!strcasecmp(type_str, note_templates[i].name)) return i; } return NOTE_TYPE_NONE; } char *note_type_usage() { int i; char *start = "--note-type=TYPE\n\nValid values for TYPE:\n"; size_t alloc_len = strlen(start) + 1; char *usage_str; for (i = 0; i < NUM_NOTE_TYPES; i++) alloc_len += strlen(note_templates[i].shortname) + 2; usage_str = xcalloc(1, alloc_len); strlcat(usage_str, start, alloc_len); for (i = 0; i < NUM_NOTE_TYPES; i++) { strlcat(usage_str, "\t", alloc_len); strlcat(usage_str, note_templates[i].shortname, alloc_len); if (i != NUM_NOTE_TYPES - 1) strlcat(usage_str, "\n", alloc_len); } return usage_str; } lastpass-cli-1.5.0/notes.h000066400000000000000000000020461462143212600154200ustar00rootroot00000000000000#ifndef NOTE_TYPES #define NOTE_TYPES #include enum note_type { NOTE_TYPE_NONE = -1, NOTE_TYPE_AMEX, NOTE_TYPE_BANK, NOTE_TYPE_CREDIT, NOTE_TYPE_DATABASE, NOTE_TYPE_DRIVERS_LICENSE, NOTE_TYPE_EMAIL, NOTE_TYPE_HEALTH_INSURANCE, NOTE_TYPE_IM, NOTE_TYPE_INSURANCE, NOTE_TYPE_MASTERCARD, NOTE_TYPE_MEMBERSHIP, NOTE_TYPE_PASSPORT, NOTE_TYPE_SERVER, NOTE_TYPE_SOFTWARE_LICENSE, NOTE_TYPE_SSH_KEY, NOTE_TYPE_SSN, NOTE_TYPE_VISA, NOTE_TYPE_WIFI, NUM_NOTE_TYPES, /* keep last */ }; #define MAX_FIELD_CT 12 struct note_template { const char *shortname; const char *name; const char *fields[MAX_FIELD_CT + 1]; }; extern struct note_template note_templates[]; const char *notes_get_name(enum note_type note_type); bool note_field_is_multiline(enum note_type note_type, const char *field); 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(); #endif /* NOTE_TYPES */ lastpass-cli-1.5.0/password.c000066400000000000000000000231301462143212600161220ustar00rootroot00000000000000/* * queue for changes uploaded to LastPass * * Copyright (C) 2014-2018 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 "password.h" #include "util.h" #include "terminal.h" #include #include #include #include #include #include #include #include #include #include static char *password_prompt_askpass(const char *askpass, const char *prompt, const char *error, const char *descfmt, va_list params) { int status; int write_fds[2], read_fds[2]; pid_t child; FILE *output; char *password = NULL, *lastlf; size_t len; UNUSED(error); UNUSED(descfmt); UNUSED(params); if (pipe(write_fds) < 0 || pipe(read_fds) < 0) die_errno("pipe"); child = fork(); if (child == -1) die_errno("fork"); if (child == 0) { dup2(read_fds[1], STDOUT_FILENO); close(read_fds[0]); close(read_fds[1]); close(write_fds[0]); close(write_fds[1]); execlp(askpass, "lpass-askpass", prompt, NULL); _exit(76); } close(read_fds[1]); close(write_fds[0]); close(write_fds[1]); output = fdopen(read_fds[0], "r"); if (!output) die_errno("fdopen"); if (getline(&password, &len, output) < 0) { free(password); die("Unable to retrieve password from askpass (no reply)"); } lastlf = strrchr(password, '\n'); if (lastlf) *lastlf = '\0'; waitpid(child, &status, 0); if (WEXITSTATUS(status) == 76) { die("Unable to execute askpass %s", askpass); } else if (WEXITSTATUS(status)) { die("There was an unspecified problem with askpass (%d)", WEXITSTATUS(status)); } return password; } static char *password_prompt_fallback(const char *prompt, const char *error, const char *descfmt, va_list params) { struct termios old_termios, mask_echo; char *password = NULL, *lastlf; size_t len = 0; terminal_fprintf(stderr, TERMINAL_FG_YELLOW TERMINAL_BOLD); vfprintf(stderr, descfmt, params); terminal_fprintf(stderr, TERMINAL_RESET "\n\n"); if (error) terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "%s" TERMINAL_RESET "\n", error); terminal_fprintf(stderr, TERMINAL_BOLD "%s" TERMINAL_RESET ": ", prompt); if (isatty(STDIN_FILENO)) { if (tcgetattr(STDIN_FILENO, &old_termios) < 0) die_errno("tcgetattr"); mask_echo = old_termios; mask_echo.c_lflag &= ~(ICANON | ECHO); if (tcsetattr(STDIN_FILENO, TCSANOW, &mask_echo) < 0) die_errno("tcsetattr"); } if (getline(&password, &len, stdin) < 0) { free(password); password = NULL; goto out; } fprintf(stderr, "\n"); lastlf = strrchr(password, '\n'); if (lastlf) *lastlf = '\0'; out: if (isatty(STDIN_FILENO)) { if (tcsetattr(STDIN_FILENO, TCSANOW, &old_termios) < 0) die_errno("tcsetattr"); } terminal_fprintf(stderr, "%s" TERMINAL_CLEAR_DOWN, error ? TERMINAL_UP_CURSOR(4) : TERMINAL_UP_CURSOR(3)); return password; } char *pinentry_escape(const char *str) { int len, new_len; char *escaped; if (!str) return NULL; new_len = len = strlen(str); for (int i = 0; i < len; ++i) { if (str[i] == '%' || str[i] == '\r' || str[i] == '\n') new_len += 2; } escaped = xcalloc(new_len + 1, 1); for (int i = 0, j = 0; i < len; ++i, ++j) { if (str[i] == '%') { escaped[j] = '%'; escaped[j + 1] = '2'; escaped[j + 2] = '5'; j += 2; } else if (str[i] == '\r') { escaped[j] = '%'; escaped[j + 1] = '0'; escaped[j + 2] = 'd'; j += 2; } else if (str[i] == '\n') { escaped[j] = '%'; escaped[j + 1] = '0'; escaped[j + 2] = 'a'; j += 2; } else escaped[j] = str[i]; } return escaped; } char *pinentry_unescape(const char *str) { char *unescaped; char hex[3]; int len; if (!str) return NULL; len = strlen(str); unescaped = xcalloc(len + 1, 1); for (int i = 0, j = 0; i < len; ++i, ++j) { if (str[i] == '%') { if (i + 2 >= len) break; hex[0] = str[i + 1]; hex[1] = str[i + 2]; hex[2] = '\0'; i += 2; unescaped[j] = strtoul(hex, NULL, 16); } else unescaped[j] = str[i]; } return unescaped; } char *password_prompt(const char *prompt, const char *error, const char *descfmt, ...) { int status; int write_fds[2], read_fds[2]; pid_t child; size_t len = 0, total_len, new_len; _cleanup_fclose_ FILE *input = NULL; _cleanup_fclose_ FILE *output = NULL; _cleanup_free_ char *line = NULL; _cleanup_free_ char *desc = NULL; _cleanup_free_ char *prompt_colon = NULL; _cleanup_free_ char *password = NULL; char *password_fallback; char *askpass; char *pinentry_fallback = "pinentry"; char *pinentry; char *ret; va_list params; int devnull; askpass = getenv("LPASS_ASKPASS"); if (askpass) { va_start(params, descfmt); askpass = password_prompt_askpass(askpass, prompt, error, descfmt, params); va_end(params); return askpass; } password_fallback = getenv("LPASS_DISABLE_PINENTRY"); if (password_fallback && !strcmp(password_fallback, "1")) { va_start(params, descfmt); password_fallback = password_prompt_fallback(prompt, error, descfmt, params); va_end(params); return password_fallback; } pinentry = getenv("LPASS_PINENTRY"); if (!pinentry) { pinentry = pinentry_fallback; } if (pipe(write_fds) < 0 || pipe(read_fds) < 0) die_errno("pipe"); child = fork(); if (child == -1) die_errno("fork"); if (child == 0) { dup2(read_fds[1], STDOUT_FILENO); dup2(write_fds[0], STDIN_FILENO); devnull = open("/dev/null", O_WRONLY); dup2(devnull, STDERR_FILENO); close(read_fds[0]); close(read_fds[1]); close(write_fds[0]); close(write_fds[1]); execlp(pinentry, pinentry, NULL); _exit(76); } close(read_fds[1]); close(write_fds[0]); input = fdopen(write_fds[1], "w"); output = fdopen(read_fds[0], "r"); if (!input || !output) die_errno("fdopen"); #define nextline() do { \ if (len) \ secure_clear(line, len); \ len = 0; \ free(line); \ line = NULL; \ if (getline(&line, &len, output) < 0) \ goto dead_pinentry; \ len = strlen(line); \ } while (0) #define check() do { \ nextline(); \ if (!starts_with(line, "OK")) \ goto dead_pinentry; \ } while (0) #define send(command, argument) do { \ if (argument == NULL) \ fprintf(input, command "\n"); \ else { \ _cleanup_free_ char *cleaned = pinentry_escape(argument); \ fprintf(input, command " %s\n", cleaned); \ } \ fflush(input); \ } while (0) #define option(name, val) do { \ char *var = val, *option, *key = name; \ if (var) { \ var = pinentry_escape(var); \ xasprintf(&option, "%s=%s", key, var); \ send("OPTION", option); \ free(var); \ free(option); \ check(); \ } \ } while(0) check(); send("SETTITLE", "LastPass CLI"); check(); if (prompt) { xasprintf(&prompt_colon, "%s:", prompt); prompt = prompt_colon; } send("SETPROMPT", prompt); check(); if (error) { send("SETERROR", error); check(); } va_start(params, descfmt); xvasprintf(&desc, descfmt, params); va_end(params); send("SETDESC", desc); check(); option("ttytype", getenv("TERM")); option("ttyname", ttyname(0)); option("display", getenv("DISPLAY")); send("GETPIN", NULL); total_len = 1; password = xcalloc(total_len, 1); for (;;) { nextline(); if (starts_with(line, "D")) { if (len >= 3) { new_len = total_len + len - 3; password = secure_resize(password, total_len, new_len); total_len = new_len; strlcat(password, line + 2, total_len); } } else if (starts_with(line, "OK")) break; else { free(password); password = NULL; break; } } send("BYE", NULL); #undef nextline #undef check #undef send #undef option waitpid(child, NULL, 0); if (len) secure_clear(line, len); ret = pinentry_unescape(password); secure_clear_str(password); return ret; dead_pinentry: if (waitpid(child, &status, WNOHANG) <= 0) { sleep(1); if (waitpid(child, &status, WNOHANG) <= 0) { kill(child, SIGTERM); sleep(1); if (waitpid(child, &status, WNOHANG) <= 0) { kill(child, SIGKILL); waitpid(child, &status, 0); } } } if (WEXITSTATUS(status) == 0) return NULL; else if (WEXITSTATUS(status) == 76) { va_start(params, descfmt); password_fallback = password_prompt_fallback(prompt, error, descfmt, params); va_end(params); return password_fallback; } else die("There was an unspecified problem with pinentry."); } lastpass-cli-1.5.0/password.h000066400000000000000000000003311462143212600161250ustar00rootroot00000000000000#ifndef PASSWORD_H #define PASSWORD_H char *password_prompt(const char *prompt, const char *error, const char *descfmt, ...); char *pinentry_unescape(const char *str); char *pinentry_escape(const char *str); #endif lastpass-cli-1.5.0/pbkdf2.c000066400000000000000000000060041462143212600154310ustar00rootroot00000000000000/* * Copyright (c) 2014-2016 Thomas Hurst. * Copyright (c) 2016-2018 LastPass. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "pbkdf2.h" #include #include #ifndef min #define min(a, b) (((a) < (b)) ? (a) : (b)) #endif #if OPENSSL_VERSION_NUMBER >= 0x10000000L #define ERR_IFZERO(x) if (!(x)) goto err #define ERR_LABEL err: #else #define ERR_IFZERO(x) (x) #define ERR_LABEL #endif int fallback_pkcs5_pbkdf2_hmac(const char *pass, size_t pass_len, const unsigned char *salt, size_t salt_len, unsigned int iterations, const EVP_MD *digest, size_t key_len, unsigned char *output) { HMAC_CTX *ctx; unsigned char *out = output; unsigned int iter = 1, count = 1; unsigned int cp_len, i, ret = 0; unsigned int key_left = key_len; unsigned int md_len = EVP_MD_size(digest); if (md_len == 0) return 0; unsigned char tmp_md[md_len]; #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) HMAC_CTX real_ctx; ctx = &real_ctx; HMAC_CTX_init(ctx); #else ctx = HMAC_CTX_new(); if (!ctx) return 0; #endif ERR_IFZERO(HMAC_Init_ex(ctx, pass, pass_len, digest, NULL)); while (key_left) { cp_len = min(key_left, md_len); unsigned char c[4]; c[0] = (count >> 24) & 0xff; c[1] = (count >> 16) & 0xff; c[2] = (count >> 8) & 0xff; c[3] = (count) & 0xff; ERR_IFZERO(HMAC_Init_ex(ctx, NULL, 0, digest, NULL)); ERR_IFZERO(HMAC_Update(ctx, salt, salt_len)); ERR_IFZERO(HMAC_Update(ctx, c, 4)); ERR_IFZERO(HMAC_Final(ctx, tmp_md, NULL)); memcpy(out, tmp_md, cp_len); for (iter=1; iter < iterations; iter++) { ERR_IFZERO(HMAC_Init_ex(ctx, NULL, 0, digest, NULL)); ERR_IFZERO(HMAC_Update(ctx, tmp_md, md_len)); ERR_IFZERO(HMAC_Final(ctx, tmp_md, NULL)); for (i = 0; i < cp_len; i++) { out[i] ^= tmp_md[i]; } } key_left -= cp_len; out += cp_len; count++; } ret = 1; ERR_LABEL #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) HMAC_CTX_cleanup(ctx); #else HMAC_CTX_free(ctx); #endif return ret; } lastpass-cli-1.5.0/pbkdf2.h000066400000000000000000000027251462143212600154440ustar00rootroot00000000000000/* * Copyright (c) 2014-2017 Thomas Hurst. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef PBKDF2_H #define PBKDF2_H #include #if OPENSSL_VERSION_NUMBER < 0x10000000L #define PKCS5_PBKDF2_HMAC fallback_pkcs5_pbkdf2_hmac #endif int fallback_pkcs5_pbkdf2_hmac(const char *pass, size_t pass_len, const unsigned char *salt, size_t salt_len, unsigned int iterations, const EVP_MD *digest, size_t key_len, unsigned char *output); #endif lastpass-cli-1.5.0/pins.h000066400000000000000000000011761462143212600152440ustar00rootroot00000000000000#ifndef PINS_H #define PINS_H const char *PK_PINS[] = { /* future lastpass root CA (GlobalSign R1) */ "K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q=", /* future lastpass root CA (GlobalSign R2) */ "iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0=", /* future lastpass root CA (GlobalSign R3) */ "cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A=", /* current lastpass.com primary (leaf) */ "YDjIAXSYj+mh+25FGifAiKN4oNOAj+as6gQv4naQG0M=", /* current lastpass.eu primary (leaf) */ "SjMnNhjAyVM5Yv6O5JaQgNygBTU0wdb8Jz3mfQfTc28=", /* GlobalSign ECC OV SSL CA 2018 intermediate CA */ "OD/WDbD3VsfMwwNzzy9MWd9JXppKB77Vb3ST2wn9meg=" }; #endif lastpass-cli-1.5.0/process.c000066400000000000000000000112261462143212600157410ustar00rootroot00000000000000/* * lpass process settings * * Copyright (C) 2014-2018 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 "process.h" #include "util.h" #include #include #include #include #include #if defined(__linux__) #include #define USE_PRCTL #elif defined(__APPLE__) && defined(__MACH__) #include #include #define USE_PTRACE #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) #include #include #elif defined(__OpenBSD__) #include #include #include #include #endif #ifndef USE_PRCTL #undef PR_SET_DUMPABLE #define PR_SET_DUMPABLE 0 #define PR_SET_NAME 0 static void prctl(__attribute__((unused)) int x, __attribute__((unused)) unsigned long y) {} #endif #ifndef USE_PTRACE #undef PT_DENY_ATTACH #define PT_DENY_ATTACH 0 static void ptrace(__attribute__((unused)) int x, __attribute__((unused)) int y, __attribute__((unused)) int z, __attribute__((unused)) int w) {} #endif #if defined(__linux__) || defined(__CYGWIN__) || (defined(__NetBSD__) && !defined(KERN_PROC_PATHNAME)) static int pid_to_cmd(pid_t pid, char *cmd, size_t cmd_size) { _cleanup_free_ char *proc; xasprintf(&proc, "/proc/%lu/exe", (unsigned long)pid); return readlink(proc, cmd, cmd_size - 1); } #elif defined(__APPLE__) && defined(__MACH__) static int pid_to_cmd(pid_t pid, char *cmd, size_t cmd_size) { int result; result = proc_pidpath(pid, cmd, cmd_size); return (result <= 0) ? -1 : 0; } #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) static int pid_to_cmd(pid_t pid, char *cmd, size_t cmd_size) { int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid }; return sysctl(mib, 4, cmd, &cmd_size, NULL, 0); } #elif defined(__OpenBSD__) static int pid_to_cmd(pid_t pid, char *cmd, size_t cmd_size) { int cnt, ret; kvm_t *kd; struct kinfo_proc *kp; ret = -1; if ((kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL)) == NULL) return ret; if ((kp = kvm_getprocs(kd, KERN_PROC_PID, (int)pid, sizeof(*kp), &cnt)) == NULL) goto out; if ((kp->p_flag & P_SYSTEM) != 0) goto out; if (cnt != 1) goto out; if (strlcpy(cmd, kp[0].p_comm, cmd_size) >= cmd_size) goto out; ret = 0; out: kvm_close(kd); return ret; } #else #error "Please provide a pid_to_cmd for your platform" #endif int ARGC; char **ARGV; void process_set_name(const char *name) { size_t argslen = 0; prctl(PR_SET_NAME, (unsigned long) name); if (!ARGC || !ARGV) return; for (int i = 0; i < ARGC; ++i) { argslen += strlen(ARGV[i]) + 1; for (char *p = ARGV[i]; *p; ++p) *p = '\0'; } strlcpy(ARGV[0], name, argslen); } bool process_is_same_executable(pid_t pid) { char resolved_them[PATH_MAX + 1] = { 0 }, resolved_me[PATH_MAX + 1] = { 0 }; if (pid_to_cmd(pid, resolved_them, sizeof(resolved_them)) < 0 || pid_to_cmd(getpid(), resolved_me, sizeof(resolved_me)) < 0) return false; return strcmp(resolved_them, resolved_me) == 0; } void process_disable_ptrace(void) { prctl(PR_SET_DUMPABLE, 0); ptrace(PT_DENY_ATTACH, 0, 0, 0); struct rlimit limit = { 0, 0 }; setrlimit(RLIMIT_CORE, &limit); } lastpass-cli-1.5.0/process.h000066400000000000000000000003701462143212600157440ustar00rootroot00000000000000#ifndef PROCESS_H #define PROCESS_H #include #include extern int ARGC; extern char **ARGV; void process_set_name(const char *name); void process_disable_ptrace(void); bool process_is_same_executable(pid_t pid); #endif lastpass-cli-1.5.0/session.c000066400000000000000000000100421462143212600157410ustar00rootroot00000000000000/* * session handling routines * * Copyright (C) 2014-2024 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 "xml.h" #include "config.h" #include "util.h" #include "cipher.h" #include "agent.h" #include "upload-queue.h" #include #include struct session *session_new(void) { return new0(struct session, 1); } void session_free(struct session *session) { if (!session) return; free(session->uid); free(session->sessionid); free(session->token); free(session->private_key.key); free(session->server); free(session); } bool session_is_valid(struct session *session) { return session && session->uid && session->sessionid && session->token; } void session_set_private_key(struct session *session, unsigned const char key[KDF_HASH_LEN], const char *key_hex) { cipher_decrypt_private_key(key_hex, key, &session->private_key); } void session_save(struct session *session, unsigned const char key[KDF_HASH_LEN]) { config_write_encrypted_string("session_uid", session->uid, key); config_write_encrypted_string("session_sessionid", session->sessionid, key); config_write_encrypted_string("session_token", session->token, key); config_write_encrypted_buffer("session_privatekey", (char *)session->private_key.key, session->private_key.len, key); feature_flag_save(&session->feature_flag, key); /* * existing sessions may not have a server yet; they will fall back * to lastpass.com. */ if (session->server) config_write_string("session_server", session->server); } struct session *session_load(unsigned const char key[KDF_HASH_LEN]) { struct session *session = session_new(); session->uid = config_read_encrypted_string("session_uid", key); session->sessionid = config_read_encrypted_string("session_sessionid", key); session->token = config_read_encrypted_string("session_token", key); session->server = config_read_string("session_server"); session->private_key.len = config_read_encrypted_buffer("session_privatekey", &session->private_key.key, key); mlock(session->private_key.key, session->private_key.len); feature_flag_load(&session->feature_flag, key); if (session_is_valid(session)) return session; else { session_free(session); return NULL; } } void session_kill() { config_unlink("verify"); config_unlink("username"); config_unlink("session_sessionid"); config_unlink("iterations"); config_unlink("blob"); config_unlink("session_token"); config_unlink("session_uid"); config_unlink("session_privatekey"); config_unlink("session_server"); config_unlink("plaintext_key"); feature_flag_cleanup(); agent_kill(); upload_queue_kill(); } lastpass-cli-1.5.0/session.h000066400000000000000000000014201462143212600157460ustar00rootroot00000000000000#ifndef SESSION_H #define SESSION_H #include "kdf.h" #include "feature-flag.h" #include struct public_key { unsigned char *key; size_t len; }; struct private_key { unsigned char *key; size_t len; }; struct session { char *uid; char *sessionid; char *token; char *server; struct private_key private_key; struct feature_flag feature_flag; }; struct session *session_new(); void session_free(struct session *session); bool session_is_valid(struct session *session); struct session *session_load(unsigned const char key[KDF_HASH_LEN]); void session_save(struct session *session, unsigned const char key[KDF_HASH_LEN]); void session_set_private_key(struct session *session, unsigned const char key[KDF_HASH_LEN], const char *key_hex); void session_kill(); #endif lastpass-cli-1.5.0/terminal.c000066400000000000000000000054451462143212600161040ustar00rootroot00000000000000/* * terminal printing routines * * Copyright (C) 2014-2018 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 "terminal.h" #include "util.h" #include #include #include #include #include static enum color_mode color_mode = COLOR_MODE_AUTO; static void filter_ansi(FILE *file, const char *fmt, va_list args) { _cleanup_free_ char *str = NULL; size_t len, i, j; if (color_mode == COLOR_MODE_ALWAYS || (color_mode == COLOR_MODE_AUTO && isatty(fileno(file)))) { vfprintf(file, fmt, args); return; } len = xvasprintf(&str, fmt, args); for (i = 0; len >= 2 && i < len - 2; ++i) { if (str[i] == '\x1b' && str[i + 1] == '[') { str[i] = str[i + 1] = '\0'; for (j = i + 2; j < len; ++j) { if (isalpha(str[j])) break; str[j] = '\0'; } str[j] = '\0'; } } for (i = 0; i < len; i = j) { fputs(&str[i], file); for (j = i + strlen(&str[i]); j < len; ++j) { if (str[j] != '\0') break; } } } void terminal_set_color_mode(enum color_mode mode) { color_mode = mode; } void terminal_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); filter_ansi(stdout, fmt, args); va_end(args); } void terminal_fprintf(FILE *file, const char *fmt, ...) { va_list args; va_start(args, fmt); filter_ansi(file, fmt, args); va_end(args); } lastpass-cli-1.5.0/terminal.h000066400000000000000000000032211462143212600160770ustar00rootroot00000000000000#ifndef TERMINAL_H #define TERMINAL_H #include "util.h" #define TERMINAL_FG_BLACK "\x1b[30m" #define TERMINAL_FG_RED "\x1b[31m" #define TERMINAL_FG_GREEN "\x1b[32m" #define TERMINAL_FG_YELLOW "\x1b[33m" #define TERMINAL_FG_BLUE "\x1b[34m" #define TERMINAL_FG_MAGENTA "\x1b[35m" #define TERMINAL_FG_CYAN "\x1b[36m" #define TERMINAL_FG_WHITE "\x1b[37m" #define TERMINAL_FG_DEFAULT "\x1b[39m" #define TERMINAL_BG_BLACK "\x1b[40m" #define TERMINAL_BG_RED "\x1b[41m" #define TERMINAL_BG_GREEN "\x1b[42m" #define TERMINAL_BG_YELLOW "\x1b[43m" #define TERMINAL_BG_BLUE "\x1b[44m" #define TERMINAL_BG_MAGENTA "\x1b[45m" #define TERMINAL_BG_CYAN "\x1b[46m" #define TERMINAL_BG_WHITE "\x1b[47m" #define TERMINAL_BG_DEFAULT "\x1b[49m" #define TERMINAL_BOLD "\x1b[1m" #define TERMINAL_NO_BOLD "\x1b[22m" #define TERMINAL_UNDERLINE "\x1b[4m" #define TERMINAL_NO_UNDERLINE "\x1b[24m" #define TERMINAL_RESET "\x1b[0m" #define TERMINAL_SAVE_CURSOR "\x1b[s" #define TERMINAL_RESTORE_CURSOR "\x1b[u" #define TERMINAL_UP_CURSOR(l) "\x1b[" #l "A" #define TERMINAL_DOWN_CURSOR(l) "\x1b[" #l "B" #define TERMINAL_RIGHT_CURSOR(c) "\x1b[" #c "C" #define TERMINAL_LEFT_CURSOR(c) "\x1b[" #c "D" #define TERMINAL_CLEAR_DOWN "\x1b[0J" #define TERMINAL_CLEAR_UP "\x1b[1J" #define TERMINAL_CLEAR_RIGHT "\x1b[0K" #define TERMINAL_CLEAR_LEFT "\x1b[1K" #define TERMINAL_CLEAR_LINE "\x1b[2K" #define TERMINAL_CLEAR_ALL "\x1b[2J" enum color_mode { COLOR_MODE_AUTO, COLOR_MODE_NEVER, COLOR_MODE_ALWAYS }; void terminal_set_color_mode(enum color_mode color_mode); void terminal_printf(const char *fmt, ...) _printf_(1, 2); void terminal_fprintf(FILE *file, const char *fmt, ...) _printf_(2, 3); #endif lastpass-cli-1.5.0/test/000077500000000000000000000000001462143212600150745ustar00rootroot00000000000000lastpass-cli-1.5.0/test/askpass-wrong.sh000077500000000000000000000002661462143212600202360ustar00rootroot00000000000000#!/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 lastpass-cli-1.5.0/test/askpass.sh000077500000000000000000000000701462143212600170750ustar00rootroot00000000000000#!/bin/bash . $(dirname $0)/include.sh echo $TEST_PASS lastpass-cli-1.5.0/test/assert.sh000077500000000000000000000013031462143212600167310ustar00rootroot00000000000000#! /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 { local s1="$(echo $1 | sed -e "s/ *$//g")" local s2="$(echo $2 | sed -e "s/ *$//g")" if [[ "$s1" == "$s2" ]]; then echo "FAIL: \"$1 != $2\" $3" return 1 fi } function assert_str_eq { local s1="$(echo $1 | sed -e "s/ *$//g")" local s2="$(echo $2 | sed -e "s/ *$//g")" if [[ "$s1" != "$s2" ]]; then echo "FAIL: \"$s1 == $s2\" $3" return 1 fi } function assertz { assert_eq $1 0 $2 } function assert { assert_ne $1 0 $2 } lastpass-cli-1.5.0/test/http_mock.c000066400000000000000000000165671462143212600172470ustar00rootroot00000000000000/* * mock http server for testing * * Copyright (C) 2014-2024 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 #include #include #include "../util.h" #include "../blob.h" #define TEST_USER "user@example.com" #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; struct feature_flag *feature_flag = new0(struct feature_flag, 1); 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, "xyz@example.com", key); account_set_password(account, "test-account-password", key); account_set_url(account, "https://test-url.example.com/", key, &feature_flag); 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, &feature_flag); 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, "xyz@example.com", key); account_set_password(account, "test-account-password", key); account_set_url(account, "https://test-url.example.com/", key, &feature_flag); 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, &feature_flag); 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; struct feature_flag *feature_flag = new0(struct feature_flag, 1); if (len) *len = blob_write(&test_data.blob, NULL, &data, &feature_flag); 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("" "" ""); } else { response = xstrdup("" "" ""); } if (len) *len = strlen(response); return response; } static char *login_check(char **argv, size_t *len) { UNUSED(argv); char *response = xstrdup("" "" ""); 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(""); if (final_len) *final_len = strlen(response); return response; } lastpass-cli-1.5.0/test/include.sh000077500000000000000000000014311462143212600170550ustar00rootroot00000000000000#!/bin/bash function setup() { cd `dirname $0` . assert.sh export LPASS_ASKPASS=./askpass.sh export TEST_USER="user@example.com" 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 lastpass-cli-1.5.0/test/tests000077500000000000000000000227411462143212600161720ustar00rootroot00000000000000#!/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_site_note { login || return 1 local name="test-add-site-note" local url="https://example.com" local username="user27" local password="999999999" local notes="here are some notes!!" cat<<__EOM__ | lpass add --sync=no --non-interactive $name Name: $name URL: $url Username: $username Password: $password Notes: $notes __EOM__ assertz $? || return 1 assert_str_eq "$(lpass show --sync=no --note $name)" "$notes" } 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_note_with_field { login || return 1 local name="test-add-note-with-notes" cat<<__EOM__ | lpass add --sync=no --note-type=ssn --non-interactive $name Name: $name Number: 000-00-0000 Field: This is my SSN 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 Private Key: $privkey Public Key: $pubkey __EOM__ assert_str_eq "$(lpass show --sync=no --field='Private Key' $name)" "$privkey" || return 1 assert_str_eq "$(lpass show --sync=no --field='Public Key' $name)" "$pubkey" } function test_edit_ssh_key { login || return 1 local name="test-edit-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" | 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" || return 1 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)" "" || return 1 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: xyz@example.com 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_json { login || return 1 read -r -d '' expected <<__EOM__ [ { "id": "0001", "name": "test-account", "fullname": "test-group/test-account", "username": "xyz@example.com", "password": "test-account-password", "last_modified_gmt": "skipped", "last_touch": "skipped", "group": "test-group", "url": "https://test-url.example.com/", "note": "" } ] __EOM__ local out=$(lpass show --sync=no --json test-account) assert_str_eq "$expected" "$out" } function test_show_note { login || return 1 # note the extra space here because mock server includes one # normally note fields are trimmed when saved read -r -d '' expected <<__EOM__ test-group/test-note [id: 0002] Username: test-note-user Password: test-note-password Hostname: foo.example.com NoteType: Server __EOM__ local out=$(lpass show --sync=no test-note) 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: xyz@example.com 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/,xyz@example.com,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/,xyz@example.com,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/,xyz@example.com,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/,xyz@example.com,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 "$@" lastpass-cli-1.5.0/upload-queue.c000066400000000000000000000251271462143212600166760ustar00rootroot00000000000000/* * queue for changes uploaded to LastPass * * Copyright (C) 2014-2018 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 "upload-queue.h" #include "session.h" #include "http.h" #include "util.h" #include "config.h" #include "kdf.h" #include "log.h" #include "process.h" #include "password.h" #include "endpoints.h" #include #include #include #include #include #include #include #include #include #include #include #include /* keep around failed updates for a couple of weeks */ #define FAIL_MAX_AGE 86400 * 14 static void make_upload_dir(const char *path) { _cleanup_free_ char *base_path = NULL; struct stat sbuf; int ret; base_path = config_path(path); ret = stat(base_path, &sbuf); if ((ret == -1 && errno == ENOENT) || !S_ISDIR(sbuf.st_mode)) { unlink(base_path); if (mkdir(base_path, 0700) < 0) die_errno("mkdir(%s)", base_path); } else if (ret == -1) die_errno("stat(%s)", base_path); } static void upload_queue_write_entry(const char *entry, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *name = NULL; unsigned long serial; make_upload_dir("upload-queue"); for (serial = 0; serial < ULONG_MAX; ++serial) { free(name); xasprintf(&name, "upload-queue/%lu%04lu", time(NULL), serial); if (!config_exists(name)) break; } if (serial == ULONG_MAX) die("No more upload queue entry slots available."); config_write_encrypted_string(name, entry, key); } static void upload_queue_cleanup_failures() { _cleanup_free_ char *base_path = config_path("upload-fail"); DIR *dir = opendir(base_path); struct dirent *entry; char *p; struct stat sbuf; int ret; if (!dir) return; while ((entry = readdir(dir))) { _cleanup_free_ char *fn = NULL; if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN) continue; for (p = entry->d_name; *p; ++p) { if (!isdigit(*p)) break; } if (*p) continue; xasprintf(&fn, "%s/%s", base_path, entry->d_name); ret = stat(fn, &sbuf); if (ret) continue; if ((time(NULL) - sbuf.st_mtime) > FAIL_MAX_AGE) { unlink(fn); } } closedir(dir); } static void upload_queue_drop(const char *name) { _cleanup_free_ char *newname = NULL; _cleanup_free_ char *old_full = NULL; _cleanup_free_ char *new_full = NULL; char *basename; int ret; lpass_log(LOG_DEBUG, "UQ: dropping %s\n", name); make_upload_dir("upload-fail"); basename = strrchr(name, '/'); if (!basename) { unlink(name); return; } basename += 1; xasprintf(&newname, "upload-fail/%s", basename); old_full = config_path(name); new_full = config_path(newname); ret = rename(old_full, new_full); lpass_log(LOG_DEBUG, "UQ: rename returned %d (errno=%d)\n", ret, errno); upload_queue_cleanup_failures(); } static char *upload_queue_next_entry(unsigned const char key[KDF_HASH_LEN], char **name, char **lock) { unsigned long long smallest = ULLONG_MAX, current; _cleanup_free_ char *smallest_name = NULL; _cleanup_free_ char *base_path = config_path("upload-queue"); _cleanup_free_ char *pidstr = NULL; pid_t pid; char *result, *p; DIR *dir = opendir(base_path); struct dirent *entry; if (!dir) return NULL; while ((entry = readdir(dir))) { if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN) continue; for (p = entry->d_name; *p; ++p) { if (!isdigit(*p)) break; } if (*p) continue; current = strtoull(entry->d_name, NULL, 10); if (!current) continue; if (current < smallest) { smallest = current; free(smallest_name); smallest_name = xstrdup(entry->d_name); } } closedir(dir); if (smallest == ULLONG_MAX) return NULL; xasprintf(name, "upload-queue/%s", smallest_name); xasprintf(lock, "%s.lock", *name); while (config_exists(*lock)) { free(pidstr); pidstr = config_read_encrypted_string(*lock, key); if (!pidstr) { config_unlink(*lock); break; } pid = strtoul(pidstr, NULL, 10); if (!pid) { config_unlink(*lock); break; } if (process_is_same_executable(pid)) sleep(1); else { config_unlink(*lock); break; } } free(pidstr); pidstr = xultostr(getpid()); config_write_encrypted_string(*lock, pidstr, key); result = config_read_encrypted_string(*name, key); if (!result) { /* could not decrypt: drop this file */ lpass_log(LOG_DEBUG, "UQ: unable to decrypt job %s\n", *name); upload_queue_drop(*name); config_unlink(*lock); return NULL; } return result; } static void upload_queue_cleanup(int signal) { UNUSED(signal); config_unlink("uploader.pid"); _exit(EXIT_SUCCESS); } static void upload_queue_upload_all(const struct session *session, unsigned const char key[KDF_HASH_LEN]) { char *entry, *next_entry, *result; int size; char **argv = NULL; char **argv_ptr; char *name, *lock, *p; bool do_break; bool should_fetch_new_blob_after = false; int curl_ret; long http_code; bool http_failed_all; int backoff; int backoff_scale = 8; while ((entry = upload_queue_next_entry(key, &name, &lock))) { lpass_log(LOG_DEBUG, "UQ: processing job %s\n", name); size = 0; for (p = entry; *p; ++p) { if (*p == '\n') ++size; } if (p > entry && p[-1] != '\n') ++size; if (size < 1) { config_unlink(name); config_unlink(lock); goto end; } argv_ptr = argv = xcalloc(size + 1, sizeof(char **)); for (do_break = false, p = entry, next_entry = entry; ; ++p) { if (!*p) do_break = true; if (*p == '\n' || !*p) { *p = '\0'; *(argv_ptr++) = pinentry_unescape(next_entry); next_entry = p + 1; if (do_break) break; } } argv[size] = NULL; http_failed_all = true; backoff = 1; for (int i = 0; i < 5; ++i) { if (i) { lpass_log(LOG_DEBUG, "UQ: attempt %d, sleeping %d seconds\n", i+1, backoff); sleep(backoff); backoff *= backoff_scale; } lpass_log(LOG_DEBUG, "UQ: posting to %s\n", argv[0]); result = http_post_lastpass_v_noexit(session->server, argv[0], session, NULL, &argv[1], &curl_ret, &http_code); http_failed_all &= (curl_ret == HTTP_ERROR_CODE || curl_ret == HTTP_ERROR_CONNECT); lpass_log(LOG_DEBUG, "UQ: result %d (http_code=%ld)\n", curl_ret, http_code); if (http_code == 500) { /* not a rate-limit error; try again with less backoff */ backoff_scale = 2; } else { backoff_scale = 8; } if (result && strlen(result)) should_fetch_new_blob_after = true; free(result); if (result) break; } if (!result) { lpass_log(LOG_DEBUG, "UQ: failed, http_failed_all: %d\n", http_failed_all); /* server failed response 5 times, remove it */ if (http_failed_all) upload_queue_drop(name); config_unlink(lock); } else { lpass_log(LOG_DEBUG, "UQ: succeeded\n"); config_unlink(name); config_unlink(lock); } for (argv_ptr = argv; *argv_ptr; ++argv_ptr) free(*argv_ptr); free(argv); end: free(name); free(lock); free(entry); } if (should_fetch_new_blob_after) blob_free(lastpass_get_blob(session, key)); } static void upload_queue_run(const struct session *session, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *pid = NULL; upload_queue_kill(); pid_t child = fork(); if (child < 0) die_errno("fork(agent)"); if (child == 0) { int null = open("/dev/null", 0); int upload_log = null; if (null >= 0) { dup2(null, 0); dup2(null, 1); dup2(null, 2); close(null); close(upload_log); } setsid(); IGNORE_RESULT(chdir("/")); process_set_name("lpass [upload queue]"); signal(SIGHUP, upload_queue_cleanup); signal(SIGINT, upload_queue_cleanup); signal(SIGQUIT, upload_queue_cleanup); signal(SIGTERM, upload_queue_cleanup); signal(SIGALRM, upload_queue_cleanup); setvbuf(stdout, NULL, _IOLBF, 0); lpass_log(LOG_DEBUG, "UQ: starting queue run\n"); upload_queue_upload_all(session, key); lpass_log(LOG_DEBUG, "UQ: queue run complete\n"); upload_queue_cleanup(0); _exit(EXIT_SUCCESS); } pid = xultostr(child); config_write_string("uploader.pid", pid); } void upload_queue_kill(void) { _cleanup_free_ char *pidstr = NULL; pid_t pid; pidstr = config_read_string("uploader.pid"); if (!pidstr) return; pid = strtoul(pidstr, NULL, 10); if (!pid) return; kill(pid, SIGTERM); } bool upload_queue_is_running(void) { _cleanup_free_ char *pidstr = NULL; pid_t pid; pidstr = config_read_string("uploader.pid"); if (!pidstr) return false; pid = strtoul(pidstr, NULL, 10); if (!pid) return false; return process_is_same_executable(pid); } void upload_queue_enqueue(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const char *page, struct http_param_set *params) { _cleanup_free_ char *sum = xstrdup(page); char *next = NULL; char *escaped = NULL; char *param; char **argv = params->argv; while ((param = *argv++)) { escaped = pinentry_escape(param); xasprintf(&next, "%s\n%s", sum, escaped); free(escaped); free(sum); sum = next; } upload_queue_write_entry(sum, key); if (sync != BLOB_SYNC_NO) upload_queue_ensure_running(key, session); } void upload_queue_ensure_running(unsigned const char key[KDF_HASH_LEN], const struct session *session) { if (!upload_queue_is_running()) upload_queue_run(session, key); } lastpass-cli-1.5.0/upload-queue.h000066400000000000000000000007451462143212600167020ustar00rootroot00000000000000#ifndef UPLOADQUEUE_H #define UPLOADQUEUE_H #include "kdf.h" #include "session.h" #include "blob.h" #include "http.h" #include void upload_queue_enqueue(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const char *page, struct http_param_set *params); bool upload_queue_is_running(void); void upload_queue_kill(void); void upload_queue_ensure_running(unsigned const char key[KDF_HASH_LEN], const struct session *session); #endif lastpass-cli-1.5.0/util.c000066400000000000000000000302131462143212600152350ustar00rootroot00000000000000/* * utility functions * * Copyright (C) 2014-2018 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. * * reallocarray is: * Copyright (c) 2008 Otto Moerbeek . * strlcpy and strlcat are: * Copyright (c) 1998 Todd C. Miller . * For reallocarray, strlcpy, and strlcat: * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "util.h" #include "process.h" #include "terminal.h" #include #include #include #include #include #include #include #include #include void warn(const char *err, ...) { char message[4096]; va_list params; va_start(params, err); vsnprintf(message, sizeof(message), err, params); va_end(params); terminal_fprintf(stderr, TERMINAL_FG_YELLOW TERMINAL_BOLD "Warning" TERMINAL_RESET ": %s\n", message); } void warn_errno(const char *err, ...) { char message[4096], *error_message; va_list params; error_message = strerror(errno); va_start(params, err); vsnprintf(message, sizeof(message), err, params); va_end(params); terminal_fprintf(stderr, TERMINAL_FG_YELLOW TERMINAL_BOLD "WARNING" TERMINAL_RESET ": " TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ": %s\n", error_message, message); } _noreturn_ void die(const char *err, ...) { char message[4096]; va_list params; va_start(params, err); vsnprintf(message, sizeof(message), err, params); va_end(params); terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "Error" TERMINAL_RESET ": %s\n", message); exit(1); } _noreturn_ void die_errno(const char *err, ...) { char message[4096], *error_message; va_list params; error_message = strerror(errno); va_start(params, err); vsnprintf(message, sizeof(message), err, params); va_end(params); terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "Error" TERMINAL_RESET ": " TERMINAL_FG_RED "%s" TERMINAL_RESET ": %s\n", error_message, message); exit(1); } void die_usage(const char *usage) { terminal_fprintf(stderr, "Usage: %s %s\n", ARGV[0], usage); exit(1); } char ask_options(char *options, char def, const char *prompt, ...) { va_list params; _cleanup_free_ char *response = NULL; size_t len = 0; unsigned int i; for (;;) { va_start(params, prompt); terminal_fprintf(stderr, TERMINAL_FG_YELLOW); vfprintf(stderr, prompt, params); terminal_fprintf(stderr, TERMINAL_RESET); va_end(params); terminal_fprintf(stderr, " ["); for (i = 0; i < strlen(options); i++) { if (options[i] == def) { terminal_fprintf(stderr,TERMINAL_BOLD "%c" TERMINAL_RESET, toupper(options[i])); } else { terminal_fprintf(stderr, "%c", options[i]); } if (i < strlen(options)-1) terminal_fprintf(stderr, "/"); else terminal_fprintf(stderr, "] "); } if (getline(&response, &len, stdin) < 0) die("aborted response."); strlower(response); if (!strcmp("\n", response)) return def; if (strlen(response) && strchr(options, response[0])) return response[0]; terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "Error" TERMINAL_RESET ": Response not understood.\n"); free(response); response = NULL; len = 0; } } bool ask_yes_no(bool default_yes, const char *prompt, ...) { va_list params; char message[4096]; va_start(params, prompt); vsnprintf(message, sizeof(message), prompt, params); va_end(params); return ask_options("yn", (default_yes) ? 'y' : 'n', message) == 'y'; } void *xmalloc(size_t size) { void *ret = malloc(size); if (likely(ret)) return ret; die_errno("malloc(%zu)", size); } void *xcalloc(size_t nmemb, size_t size) { void *ret = calloc(nmemb, size); if (likely(ret)) return ret; die_errno("calloc(%zu, %zu)", nmemb, size); } void *xrealloc(void *ptr, size_t size) { void *ret = realloc(ptr, size); if (likely(ret)) return ret; die_errno("realloc(%p, %zu)", ptr, size); } void *reallocarray(void *optr, size_t nmemb, size_t size) { if (!size || !nmemb) { errno = ENOMEM; return NULL; } if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return NULL; } return realloc(optr, size * nmemb); } void *xreallocarray(void *ptr, size_t nmemb, size_t size) { void *ret = reallocarray(ptr, nmemb, size); if (likely(ret)) return ret; die_errno("reallocarray(%p, %zu, %zu)", ptr, nmemb, size); } void *xstrdup(const char *str) { void *ret = strdup(str); if (likely(ret)) return ret; die_errno("strdup(%p)", (void *) str); } void *xstrndup(const char *str, size_t maxlen) { void *ret = strndup(str, maxlen); if (likely(ret)) return ret; die_errno("strndup(%p, %zu)", (void *) str, maxlen); } int xasprintf(char **strp, const char *fmt, ...) { va_list params; int ret; va_start(params, fmt); ret = xvasprintf(strp, fmt, params); va_end(params); return ret; } int xvasprintf(char **strp, const char *fmt, va_list ap) { int ret; ret = vasprintf(strp, fmt, ap); if (ret == -1) die_errno("asprintf(%p, %s, ...)", (void *)strp, fmt); return ret; } #ifdef __GLIBC__ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = *s++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return (s - src - 1); /* count does not include NUL */ } size_t strlcat(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return (dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return (dlen + (s - src)); /* count does not include NUL */ } #else char *strchrnul(const char *s, int c) { char *p = strchr(s, c); if (!p) p = (char *)s + strlen(s); return p; } #endif void strlower(char *str) { for (; *str; ++str) *str = tolower(*str); } void strupper(char *str) { for (; *str; ++str) *str = toupper(*str); } char *xstrlower(const char *str) { char *copy = xstrdup(str); strlower(copy); return copy; } char *xstrupper(const char *str) { char *copy = xstrdup(str); strupper(copy); return copy; } char *xultostr(unsigned long num) { char *str; xasprintf(&str, "%ld", num); return str; } bool starts_with(const char *str, const char *start) { for (; ; ++str, ++start) { if (!*start) return true; else if (*str != *start) return false; } } bool ends_with(const char *str, const char *end) { int str_len = strlen(str); int end_len = strlen(end); if (str_len < end_len) return false; else return !strcmp(str + str_len - end_len, end); } char *trim(char *str) { int start, i; for (start = 0; isspace(str[start]) && str[start]; ++start); for (i = 0; str[i + start]; ++i) str[i] = str[i + start]; str[i] = '\0'; for (--i; i >= 0 && isspace(str[i]); --i) str[i] = '\0'; return str; } void xstrappend(char **str, const char *suffix) { if (!*str) { *str = xstrdup(suffix); return; } size_t len = strlen(*str) + strlen(suffix) + 1; *str = xrealloc(*str, len); strlcat(*str, suffix, len); } void xstrappendf(char **str, const char *suffixfmt, ...) { _cleanup_free_ char *fmt = NULL; va_list args; va_start(args, suffixfmt); xvasprintf(&fmt, suffixfmt, args); va_end(args); xstrappend(str, fmt); } void xstrprepend(char **str, const char *prefix) { if (!*str) { *str = xstrdup(prefix); return; } size_t len = strlen(*str) + strlen(prefix) + 1; char *new = xmalloc(len); strlcpy(new, prefix, len); strlcat(new, *str, len); free(*str); *str = new; } void xstrprependf(char **str, const char *suffixfmt, ...) { _cleanup_free_ char *fmt = NULL; va_list args; va_start(args, suffixfmt); xvasprintf(&fmt, suffixfmt, args); va_end(args); xstrprepend(str, fmt); } void secure_clear(void *ptr, size_t len) { if (!ptr) return; memset(ptr, 0, len); /* prevent GCC / LLVM from optimizing out memset */ asm volatile("" : : "r"(ptr) : "memory"); } void secure_clear_str(char *str) { if (!str) return; secure_clear(str, strlen(str)); } void *secure_resize(void *ptr, size_t oldlen, size_t newlen) { /* open-coded realloc, with a secure memset in the middle */ void *newptr = xmalloc(newlen); if (ptr) { memcpy(newptr, ptr, min(oldlen, newlen)); secure_clear(ptr, oldlen); free(ptr); } return newptr; } static char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; _unroll_ void bytes_to_hex(const unsigned char *bytes, char **hex, size_t len) { if (!*hex) *hex = xmalloc(len * 2 + 1); for (size_t i = 0; i < len; ++i) { (*hex)[i * 2] = hex_digits[(bytes[i] >> 4) & 0xF]; (*hex)[i * 2 + 1] = hex_digits[bytes[i] & 0xF]; } (*hex)[len * 2] = '\0'; } int hex_to_bytes(const char *hex, unsigned char **bytes) { size_t len = strlen(hex); if (len % 2 != 0) { if (!*bytes) *bytes = xcalloc(1, 1); **bytes = '\0'; return -EINVAL; } if (!*bytes) *bytes = xmalloc(len / 2 + 1); for (size_t i = 0; i < len / 2; ++i) { if (sscanf(&hex[i * 2], "%2hhx", (unsigned char *)(*bytes + i)) != 1) { fprintf(stderr, "%s\n", hex); **bytes = '\0'; return -EINVAL; } } (*bytes)[len / 2] = '\0'; return 0; } /* [min, max) */ unsigned long range_rand(unsigned long min, unsigned long max) { unsigned long base_random, range, remainder, bucket; if (!RAND_bytes((unsigned char *)&base_random, sizeof(base_random))) die("Could not generate random bytes."); if (ULONG_MAX == base_random) return range_rand(min, max); range = max - min; remainder = ULONG_MAX % range; bucket = ULONG_MAX / range; if (base_random < ULONG_MAX - remainder) return min + base_random / bucket; return range_rand(min, max); } void get_random_bytes(unsigned char *buf, size_t len) { if (!RAND_bytes(buf, len)) die("Could not generate random bytes."); } const char *bool_str(bool val) { return val ? "1" : "0"; } lastpass-cli-1.5.0/util.h000066400000000000000000000070771462143212600152560ustar00rootroot00000000000000#ifndef UTIL_H #define UTIL_H #include #include #include #include #include #include #include #ifndef min #define min(x,y) (((x) < (y)) ? (x) : (y)) #endif #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #define _noreturn_ __attribute__((noreturn)) #if !defined(__clang__) #define _unroll_ __attribute__((optimize("unroll-loops"))) #else #define _unroll_ #endif #define _printf_(x, y) __attribute__((format(printf, x, y))) #define _cleanup_(x) __attribute__((cleanup(x))) #define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ static inline void func##p(type *p) { \ if (*p) \ func(*p); \ } DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, fclose) DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose) DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir) #undef DEFINE_TRIVIAL_CLEANUP_FUNC static inline void umaskp(mode_t *u) { umask(*u); } static inline void freep(void *p) { free(*(void**) p); } #define _cleanup_free_ _cleanup_(freep) #define _cleanup_umask_ _cleanup_(umaskp) #define _cleanup_fclose_ _cleanup_(fclosep) #define _cleanup_pclose_ _cleanup_(pclosep) #define _cleanup_closedir_ _cleanup_(closedirp) #define new0(t, l) ((t*) xcalloc((l), sizeof(t))) #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #define UNUSED(x) (void)(x) #define IGNORE_RESULT(x) do { int z = x; (void)sizeof(z); } while (0) #define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) void warn(const char *err, ...) _printf_(1, 2); void warn_errno(const char *err, ...) _printf_(1, 2); _noreturn_ void die(const char *err, ...) _printf_(1, 2); _noreturn_ void die_errno(const char *err, ...) _printf_(1, 2); _noreturn_ void die_usage(const char *usage); char ask_options(char *options, char def, const char *prompt, ...); bool ask_yes_no(bool default_yes, const char *prompt, ...); void *xmalloc(size_t size); void *xcalloc(size_t nmemb, size_t size); void *xrealloc(void *ptr, size_t size); void *reallocarray(void *ptr, size_t nmemb, size_t size); void *xreallocarray(void *ptr, size_t nmemb, size_t size); void *xstrdup(const char *str); void *xstrndup(const char *str, size_t maxlen); int xasprintf(char **strp, const char *fmt, ...) _printf_(2, 3); int xvasprintf(char **strp, const char *fmt, va_list ap); #ifdef __GLIBC__ size_t strlcpy(char *dst, const char *src, size_t dstsize); size_t strlcat(char *dst, const char *src, size_t dstsize); #else char *strchrnul(const char *s, int c); #endif void strlower(char *str); void strupper(char *str); char *xstrlower(const char *str); char *xstrupper(const char *str); char *xultostr(unsigned long num); void xstrappend(char **str, const char *suffix); void xstrappendf(char **str, const char *suffixfmt, ...) _printf_(2, 3); void xstrprepend(char **str, const char *suffix); void xstrprependf(char **str, const char *suffixfmt, ...) _printf_(2, 3); bool starts_with(const char *str, const char *start); bool ends_with(const char *str, const char *end); char *trim(char *str); void bytes_to_hex(const unsigned char *bytes, char **hex, size_t len); int hex_to_bytes(const char *hex, unsigned char **bytes); void secure_clear(void *ptr, size_t len); void secure_clear_str(char *str); void *secure_resize(void *ptr, size_t oldlen, size_t newlen); /* [min, max) */ unsigned long range_rand(unsigned long min, unsigned long max); void get_random_bytes(unsigned char *buf, size_t len); const char *bool_str(bool val); #endif lastpass-cli-1.5.0/xml.c000066400000000000000000000404421462143212600150650ustar00rootroot00000000000000/* * xml parsing routines * * Copyright (C) 2014-2024 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 "xml.h" #include "util.h" #include "blob.h" #include "feature-flag.h" #include #include #include #include struct session *xml_ok_session(const char *buf, unsigned const char key[KDF_HASH_LEN]) { struct session *session = NULL; xmlDoc *doc = NULL; xmlNode *root; doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) goto out; root = xmlDocGetRootElement(doc); if (root && !xmlStrcmp(root->name, BAD_CAST "response")) { for (root = root->children; root; root = root->next) { if (!xmlStrcmp(root->name, BAD_CAST "ok")) break; } } if (root && !xmlStrcmp(root->name, BAD_CAST "ok")) { session = session_new(); for (xmlAttrPtr attr = root->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "uid")) session->uid = (char *)xmlNodeListGetString(doc, attr->children, 1); if (!xmlStrcmp(attr->name, BAD_CAST "sessionid")) session->sessionid = (char *)xmlNodeListGetString(doc, attr->children, 1); if (!xmlStrcmp(attr->name, BAD_CAST "token")) session->token = (char *)xmlNodeListGetString(doc, attr->children, 1); if (!xmlStrcmp(attr->name, BAD_CAST "privatekeyenc")) { _cleanup_free_ char *private_key = (char *)xmlNodeListGetString(doc, attr->children, 1); session_set_private_key(session, key, private_key); } feature_flag_load_xml_attr(&session->feature_flag, doc, attr); } } out: if (doc) xmlFreeDoc(doc); if (!session_is_valid(session)) { session_free(session); return NULL; } return session; } unsigned long long xml_login_check(const char *buf, struct session *session) { _cleanup_free_ char *versionstr = NULL; unsigned long long version = 0; xmlDoc *doc = NULL; xmlNode *root, *child = NULL; doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) goto out; root = xmlDocGetRootElement(doc); if (root && !xmlStrcmp(root->name, BAD_CAST "response")) { for (child = root->children; child; child = child->next) { if (!xmlStrcmp(child->name, BAD_CAST "ok")) break; } } if (child) { for (xmlAttrPtr attr = child->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "uid")) { free(session->uid); session->uid = (char *)xmlNodeListGetString(doc, attr->children, 1); } else if (!xmlStrcmp(attr->name, BAD_CAST "sessionid")) { free(session->sessionid); session->sessionid = (char *)xmlNodeListGetString(doc, attr->children, 1); } else if (!xmlStrcmp(attr->name, BAD_CAST "token")) { free(session->token); session->token = (char *)xmlNodeListGetString(doc, attr->children, 1); } else if (!xmlStrcmp(attr->name, BAD_CAST "accts_version")) { versionstr = (char *)xmlNodeListGetString(doc, attr->children, 1); version = strtoull(versionstr, NULL, 10); } feature_flag_load_xml_attr(&session->feature_flag, doc, attr); } } out: if (doc) xmlFreeDoc(doc); return version; } char *xml_error_cause(const char *buf, const char *what) { char *result = NULL; xmlDoc *doc = NULL; xmlNode *root; doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) goto out; root = xmlDocGetRootElement(doc); if (root && !xmlStrcmp(root->name, BAD_CAST "response")) { for (xmlNode *child = root->children; child; child = child->next) { if (!xmlStrcmp(child->name, BAD_CAST "error")) { for (xmlAttrPtr attr = child->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST what)) { result = (char *)xmlNodeListGetString(doc, attr->children, 1); goto out; } } break; } } } out: if (doc) xmlFreeDoc(doc); if (!result) result = xstrdup("unknown"); return result; } /* * Check if node has the tag "name", and interpret as a string * if so. * * Return true and update the string pointed to by ptr if the node * matches name. */ static bool xml_parse_str(xmlDoc *doc, xmlNode *parent, const char *name, char **ptr) { if (xmlStrcmp(parent->name, BAD_CAST name)) return false; *ptr = (char *) xmlNodeListGetString(doc, parent->xmlChildrenNode, 1); if (!*ptr) return false; return true; } /* * Check if node has the tag "name", and interpret as an int if so. * * Return true and update the int pointed to by ptr if the node * matches name. */ static bool xml_parse_int(xmlDoc *doc, xmlNode *parent, const char *name, int *ptr) { if (xmlStrcmp(parent->name, BAD_CAST name)) return false; _cleanup_free_ char *str = (char *) xmlNodeListGetString(doc, parent->xmlChildrenNode, 1); if (!str) return false; *ptr = atoi(str); return true; } /* * Check if node is for the boolean "name", and interpret as a boolean * if so. * * Return true and update the boolean pointed to by ptr if the node * matches name. */ static bool xml_parse_bool(xmlDoc *doc, xmlNode *parent, const char *name, bool *ptr) { int intval; if (!xml_parse_int(doc, parent, name, &intval)) return false; *ptr = intval; return true; } static void xml_parse_share_permissions(xmlDoc *doc, xmlNode *item, struct share_user *user) { bool tmp; for (xmlNode *child = item->children; child; child = child->next) { if (xml_parse_bool(doc, child, "canadminister", &user->admin)) continue; if (xml_parse_bool(doc, child, "readonly", &user->read_only)) continue; if (xml_parse_bool(doc, child, "give", &tmp)) { user->hide_passwords = !tmp; continue; } } } static void xml_parse_share_user(xmlDoc *doc, xmlNode *item, struct share_user *user) { char *tmp; /* process a user item */ for (xmlNode *child = item->children; child; child = child->next) { if (xml_parse_str(doc, child, "realname", &user->realname)) continue; if (xml_parse_str(doc, child, "username", &user->username)) continue; if (xml_parse_str(doc, child, "uid", &user->uid)) continue; if (xml_parse_bool(doc, child, "group", &user->is_group)) continue; if (xml_parse_bool(doc, child, "outsideenterprise", &user->outside_enterprise)) continue; if (xml_parse_bool(doc, child, "accepted", &user->accepted)) continue; if (xml_parse_str(doc, child, "sharingkey", &tmp)) { int ret = hex_to_bytes(tmp, &user->sharing_key.key); if (ret == 0) user->sharing_key.len = strlen(tmp) / 2; free(tmp); continue; } if (!xmlStrcmp(child->name, BAD_CAST "permissions")) xml_parse_share_permissions(doc, child, user); } } static int xml_parse_share_key_entry(xmlDoc *doc, xmlNode *root, struct share_user *user, int idx) { char *tmp; _cleanup_free_ char *pubkey = NULL; _cleanup_free_ char *username = NULL; _cleanup_free_ char *uid = NULL; _cleanup_free_ char *cgid = NULL; xasprintf(&pubkey, "pubkey%d", idx); xasprintf(&username, "username%d", idx); xasprintf(&uid, "uid%d", idx); xasprintf(&cgid, "cgid%d", idx); memset(user, 0, sizeof(*user)); for (xmlNode *item = root->children; item; item = item->next) { if (xml_parse_str(doc, item, pubkey, &tmp)) { int ret = hex_to_bytes(tmp, &user->sharing_key.key); if (ret == 0) user->sharing_key.len = strlen(tmp) / 2; free(tmp); continue; } if (xml_parse_str(doc, item, username, &user->username)) continue; if (xml_parse_str(doc, item, uid, &user->uid)) continue; if (xml_parse_str(doc, item, cgid, &user->cgid)) continue; } if (!user->uid) { free(user->cgid); free(user->username); free(user->sharing_key.key); return -ENOENT; } return 0; } int xml_parse_share_getinfo(const char *buf, struct list_head *users) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) return -EINVAL; /* * XML fields are as follows: * xmlresponse * users * item * realname * uid * group * username * permissions * readonly * canadminister * give * outsideenterprise * accepted * item... */ xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "xmlresponse") || !root->children || xmlStrcmp(root->children->name, BAD_CAST "users")) { ret = -EINVAL; goto free_doc; } xmlNode *usernode = root->children; for (xmlNode *item = usernode->children; item; item = item->next) { if (xmlStrcmp(item->name, BAD_CAST "item")) continue; struct share_user *new_user = xcalloc(1, sizeof(*new_user)); xml_parse_share_user(doc, item, new_user); list_add_tail(&new_user->list, users); } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } int xml_parse_share_getpubkeys(const char *buf, struct list_head *user_list) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) return -EINVAL; /* * XML fields are as follows: * xmlresponse * success * pubkey0 * uid0 * username0 * cgid0 (if group) */ xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "xmlresponse") || !root->children) { ret = -EINVAL; goto free_doc; } for (int count = 0; ; count++) { struct share_user *user = new0(struct share_user, 1); ret = xml_parse_share_key_entry(doc, root, user, count); if (ret) { free(user); break; } list_add(&user->list, user_list); } if (list_empty(user_list)) ret = -ENOENT; else ret = 0; free_doc: xmlFreeDoc(doc); return ret; } static int xml_parse_su_key_entry(xmlDoc *doc, xmlNode *parent, struct pwchange_su_key *su_key, int idx) { char *tmp; _cleanup_free_ char *pubkey = NULL; _cleanup_free_ char *uid = NULL; xasprintf(&pubkey, "sukey%d", idx); xasprintf(&uid, "suuid%d", idx); memset(su_key, 0, sizeof(*su_key)); for (xmlAttrPtr attr = parent->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST pubkey)) { tmp = (char *) xmlNodeListGetString(doc, attr->children, 1); int ret = hex_to_bytes(tmp, &su_key->sharing_key.key); if (ret == 0) su_key->sharing_key.len = strlen(tmp) / 2; free(tmp); continue; } if (!xmlStrcmp(attr->name, BAD_CAST uid)) { tmp = (char *) xmlNodeListGetString(doc, attr->children, 1); su_key->uid = tmp; continue; } } if (!su_key->sharing_key.len || !su_key->uid) { free(su_key->uid); free(su_key->sharing_key.key); return -ENOENT; } return 0; } static int xml_parse_pwchange_su_keys(xmlDoc *doc, xmlNode *parent, struct pwchange_info *info) { for (int count = 0; ; count++) { struct pwchange_su_key *su_key = new0(struct pwchange_su_key,1); int ret = xml_parse_su_key_entry(doc, parent, su_key, count); if (ret) { free(su_key); break; } list_add(&su_key->list, &info->su_keys); } return 0; } static int xml_parse_pwchange_data(char *data, struct pwchange_info *info) { char *token, *end; struct pwchange_field *field; /* * read the first two lines without strtok: in case there are * empty lines we don't want to skip them. */ #define next_line(x) { \ end = strchr(data, '\n'); \ if (!end) \ return -ENOENT; \ *end++ = 0; \ info->x = xstrdup(data); \ data = end; \ } next_line(reencrypt_id); next_line(privkey_encrypted); #undef next_line for (token = strtok(data, "\n"); token; token = strtok(NULL, "\n")) { if (!strncmp(token, "endmarker", 9)) break; field = new0(struct pwchange_field, 1); char *delim = strchr(token, '\t'); if (delim) { *delim = 0; field->optional = *(delim + 1) == '0'; } field->old_ctext = xstrdup(token); list_add_tail(&field->list, &info->fields); } return 0; } int xml_api_err(const char *buf) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "lastpass") || !root->children) { ret = -EINVAL; goto free_doc; } for (xmlAttrPtr attr = root->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "rc")) { _cleanup_free_ char *val = (char *) xmlNodeListGetString(doc, attr->children, 1); if (strcmp(val, "OK") != 0) { ret = -EPERM; goto free_doc; } } } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } int xml_parse_pwchange(const char *buf, struct pwchange_info *info) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); INIT_LIST_HEAD(&info->fields); INIT_LIST_HEAD(&info->su_keys); xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "lastpass") || !root->children) { ret = -EINVAL; goto free_doc; } for (xmlAttrPtr attr = root->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "rc")) { _cleanup_free_ char *val = (char *) xmlNodeListGetString(doc, attr->children, 1); if (strcmp(val, "OK") != 0) { ret = -EPERM; goto free_doc; } } } for (xmlNode *item = root->children; item; item = item->next) { if (xmlStrcmp(item->name, BAD_CAST "data")) continue; for (xmlAttrPtr attr = item->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "xml")) { _cleanup_free_ char *data = (char *) xmlNodeListGetString(doc, attr->children, 1); ret = xml_parse_pwchange_data(data, info); if (ret) goto free_doc; } if (!xmlStrcmp(attr->name, BAD_CAST "token")) info->token = (char *)xmlNodeListGetString(doc, attr->children, 1); } xml_parse_pwchange_su_keys(doc, item, info); } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } int xml_parse_share_getpubkey(const char *buf, struct share_user *user) { struct list_head users; struct share_user *share_user, *tmp; int ret; INIT_LIST_HEAD(&users); ret = xml_parse_share_getpubkeys(buf, &users); if (ret) return ret; if (list_empty(&users)) return -ENOENT; share_user = list_first_entry(&users, struct share_user, list); *user = *share_user; list_for_each_entry_safe(share_user, tmp, &users, list) free(share_user); return 0; } static void xml_parse_share_limit_aids(xmlDoc *doc, xmlNode *parent, struct list_head *list) { for (xmlNode *item = parent->children; item; item = item->next) { if (xmlStrncmp(item->name, BAD_CAST "aid", 3)) continue; struct share_limit_aid *aid = new0(struct share_limit_aid, 1); aid->aid = (char *) xmlNodeListGetString(doc, item->xmlChildrenNode, 1); list_add_tail(&aid->list, list); } } int xml_parse_share_get_limits(const char *buf, struct share_limit *limit) { int ret; memset(limit, 0, sizeof(*limit)); INIT_LIST_HEAD(&limit->aid_list); xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) return -EINVAL; xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "xmlresponse") || !root->children) { ret = -EINVAL; goto free_doc; } for (xmlNode *item = root->children; item; item = item->next) { if (xml_parse_bool(doc, item, "hidebydefault", &limit->whitelist)) continue; if (!xmlStrcmp(item->name, BAD_CAST "aids")) { xml_parse_share_limit_aids(doc, item, &limit->aid_list); } } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } lastpass-cli-1.5.0/xml.h000066400000000000000000000013441462143212600150700ustar00rootroot00000000000000#ifndef XML_H #define XML_H #include "session.h" #include "cipher.h" #include "list.h" #include "blob.h" struct session *xml_ok_session(const char *buf, unsigned const char key[KDF_HASH_LEN]); char *xml_error_cause(const char *buf, const char *what); unsigned long long xml_login_check(const char *buf, struct session *session); int xml_parse_share_getinfo(const char *buf, struct list_head *users); int xml_parse_share_getpubkey(const char *buf, struct share_user *user); int xml_parse_pwchange(const char *buf, struct pwchange_info *info); int xml_api_err(const char *buf); int xml_parse_share_getpubkeys(const char *buf, struct list_head *user_list); int xml_parse_share_get_limits(const char *buf, struct share_limit *limit); #endif