lastpass-cli/0000755000175000017500000000000012635064024013703 5ustar troyhebetroyhebelastpass-cli/password.c0000644000175000017500000002270412634367624015731 0ustar troyhebetroyhebe/* * queue for changes uploaded to LastPass * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 *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; } 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/session.h0000644000175000017500000000126012634367624015551 0ustar troyhebetroyhebe#ifndef SESSION_H #define SESSION_H #include "kdf.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; struct private_key private_key; }; struct session *session_new(); void session_free(struct session *session); bool session_is_valid(struct session *session); struct session *sesssion_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); #endif lastpass-cli/upload-queue.h0000644000175000017500000000067112422211655016464 0ustar troyhebetroyhebe#ifndef UPLOADQUEUE_H #define UPLOADQUEUE_H #include "kdf.h" #include "session.h" #include "blob.h" #include void upload_queue_enqueue(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const char *page, ...); 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/cmd-edit.c0000644000175000017500000002451412634367624015556 0ustar troyhebetroyhebe/* * command for editing vault entries * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 #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); } 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; enum { NONE, USERNAME, PASSWORD, URL, FIELD, NAME, NOTES } choice = NONE; _cleanup_free_ char *field = NULL; _cleanup_free_ char *tmppath = NULL; _cleanup_free_ char *tmpdir = NULL; _cleanup_free_ char *editcmd = NULL; int tmpfd; FILE *tmpfile; char *name; char *value; bool non_interactive = false; enum blobsync sync = BLOB_SYNC_AUTO; struct account *editable; struct account *notes_expansion, *notes_collapsed = NULL; struct field *editable_field = NULL; size_t len, read; bool should_log_read = false; #define ensure_choice() if (choice != NONE) 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 = USERNAME; break; case 'p': ensure_choice(); choice = PASSWORD; break; case 'L': ensure_choice(); choice = URL; break; case 'F': ensure_choice(); choice = FIELD; field = xstrdup(optarg); break; case 'N': ensure_choice(); choice = NAME; break; case 'O': ensure_choice(); choice = 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 == 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) { if (editable->share && editable->share->readonly) die("%s is a readonly shared entry from %s. It cannot be edited.", editable->fullname, editable->share->name); should_log_read = true; } else { editable = new_account(); account_assign_share(blob, editable, name); editable->id = xstrdup("0"); account_set_password(editable, xstrdup(""), key); account_set_fullname(editable, xstrdup(name), key); account_set_username(editable, xstrdup(""), key); account_set_note(editable, xstrdup(""), key); if (choice == NOTES) { editable->url = xstrdup("http://sn"); } else { editable->url = xstrdup(""); } list_add(&editable->list, &blob->account_head); } notes_expansion = notes_expand(editable); if (notes_expansion) { notes_collapsed = editable; editable = notes_expansion; } else if (choice == FIELD) die("Editing fields of entries that are not secure notes is currently not supported."); if (choice == USERNAME) value = editable->username; else if (choice == PASSWORD) value = editable->password; else if (choice == URL) value = editable->url; else if (choice == NAME) value = editable->fullname; else if (choice == FIELD) { list_for_each_entry(editable_field, &editable->field_head, list) { if (!strcmp(editable_field->name, field)) break; } if (!editable_field) { editable_field = new0(struct field, 1); editable_field->type = xstrdup("text"); editable_field->name = xstrdup(field); field_set_value(editable, editable_field, xstrdup(""), key); list_add(&editable_field->list, &editable->field_head); } value = editable_field->value; } else if (choice == NOTES) value = editable->note; 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 (should_log_read) 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 (fprintf(tmpfile, "%s\n", value) < 0) die_unlink_errno("fprintf", tmppath, tmpdir); fclose(tmpfile); xasprintf(&editcmd, "${EDITOR:-vi} '%s'", tmppath); if (system(editcmd) < 0) die_unlink_errno("system($EDITOR)", tmppath, tmpdir); tmpfile = fopen(tmppath, "r"); } else tmpfile = stdin; if (!tmpfile) die_unlink_errno("fopen", tmppath, tmpdir); if (choice == NOTES) { for (len = 0, value = xmalloc(8192 + 1); ; value = xrealloc(value, len + 8192 + 1)) { read = fread(value + len, 1, 8192, tmpfile); len += read; if (read != 8192) { if (ferror(tmpfile)) die_unlink_errno("fread(tmpfile)", tmppath, tmpdir); break; } } value[len] = '\0'; } else { value = NULL; len = 0; if (getline(&value, &len, tmpfile) < 0) die_unlink_errno("getline", tmppath, tmpdir); } fclose(tmpfile); len = strlen(value); if (len && value[len - 1] == '\n') value[len - 1] = '\0'; if (tmppath) { unlink(tmppath); rmdir(tmpdir); } if (choice == USERNAME) account_set_username(editable, value, key); else if (choice == PASSWORD) account_set_password(editable, value, key); else if (choice == URL) { free(editable->url); editable->url = value; } else if (choice == NAME) account_set_fullname(editable, value, key); else if (choice == NOTES) account_set_note(editable, value, key); else if (choice == FIELD) { if (!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); if (choice == NAME) account_set_fullname(editable, xstrdup(notes_collapsed->fullname), key); account_free(notes_collapsed); } lastpass_update_account(sync, key, session, editable, blob); blob_save(blob, key); session_free(session); blob_free(blob); return 0; } lastpass-cli/pbkdf2.h0000644000175000017500000000272512634367503015241 0ustar troyhebetroyhebe/* * Copyright (c) 2014-2015 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/cmd-export.c0000644000175000017500000000773412634367624016157 0ustar troyhebetroyhebe/* * command for exporting vault entries into CSV format * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 static void print_csv_cell(char *cell, bool is_last) { char *ptr; bool needs_quote = false; /* 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(","); } int cmd_export(int argc, char **argv) { static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; enum blobsync sync = BLOB_SYNC_AUTO; struct account *account; while ((option = getopt_long(argc, argv, "c", 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_export_usage); } } unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; 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; } } printf("url,username,password,hostname,name,grouping\r\n"); list_for_each_entry(account, &blob->account_head, list) { /* skip shared notes */ if (!strcmp(account->url, "http://sn")) continue; lastpass_log_access(sync, session, key, account); print_csv_cell(account->url, false); print_csv_cell(account->username, false); print_csv_cell(account->password, false); print_csv_cell(account->fullname, false); print_csv_cell(account->name, false); print_csv_cell(account->group, true); } session_free(session); blob_free(blob); return 0; } lastpass-cli/process.c0000644000175000017500000001052612634367624015544 0ustar troyhebetroyhebe/* * lpass process settings * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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(__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)) int 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__) #define DEVPROC_NAME "exe" #elif defined(__FreeBSD__) || defined(__DragonFly__) #define DEVPROC_NAME "file" #endif #ifdef DEVPROC_NAME static int pid_to_cmd(pid_t pid, char *cmd, size_t cmd_size) { _cleanup_free_ char *proc; xasprintf(&proc, "/proc/%lu/" DEVPROC_NAME, (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(__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 void process_set_name(const char *name) { size_t argslen = 0; prctl(PR_SET_NAME, 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/cmd-generate.c0000644000175000017500000001146112634367624016420 0ustar troyhebetroyhebe/* * command for generating passwords * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 #define ALL_CHARS_LEN 94 #define NICE_CHARS_LEN 62 static char *chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?"; 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} }; char 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) { free(found->url); found->url = url; } 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_assign_share(blob, new, name); 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); new->url = url ? url : xstrdup(""); list_add(&new->list, &blob->account_head); } lastpass_update_account(sync, key, session, found ? found : new, blob); blob_save(blob, key); if (clip) clipboard_open(); printf("%s\n", password); session_free(session); blob_free(blob); return 0; } lastpass-cli/http.c0000644000175000017500000001457412634367624015054 0ustar troyhebetroyhebe/* * http posting routines * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "util.h" #include "version.h" #include "certificate.h" #include #include #include #include #include #include #include struct mem_chunk { char *ptr; size_t len; }; 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 CURLcode pin_certificate(CURL *curl, void *sslctx, void *parm) { UNUSED(curl); UNUSED(parm); X509_STORE *store; X509 *cert = NULL; BIO *bio = NULL; CURLcode ret = CURLE_SSL_CACERT; store = X509_STORE_new(); if (!store) goto out; bio = BIO_new_mem_buf(CERTIFICATE_THAWTE, -1); while ((cert = PEM_read_bio_X509(bio, NULL, 0, NULL))) { if (!X509_STORE_add_cert(store, cert)) { X509_free(cert); goto out; } X509_free(cert); } SSL_CTX_set_cert_store((SSL_CTX *)sslctx, store); ret = CURLE_OK; out: BIO_free(bio); return ret; } char *http_post_lastpass(const char *page, const char *session, size_t *final_len, ...) { va_list params; _cleanup_free_ char **argv = NULL; char **argv_ptr; int count = 0; va_start(params, final_len); while (va_arg(params, char *)) ++count; va_end(params); argv_ptr = argv = xcalloc(count + 1, sizeof(char **)); va_start(params, final_len); while ((*(argv_ptr++) = va_arg(params, char *))); va_end(params); return http_post_lastpass_v(page, session, final_len, argv); } char *http_post_lastpass_v(const char *page, const char *session, size_t *final_len, char **argv) { _cleanup_free_ char *url = NULL; _cleanup_free_ char *postdata = NULL; _cleanup_free_ char *cookie = NULL; char *param, *encoded_param; CURL *curl = NULL; char separator; size_t len, new_len; int ret; struct mem_chunk result; xasprintf(&url, "https://lastpass.com/%s", page); 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); #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_certificate); #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); curl_easy_setopt(curl, CURLOPT_COOKIE, cookie); } set_interrupt_detect(); ret = curl_easy_perform(curl); unset_interrupt_detect(); curl_easy_cleanup(curl); if (ret != CURLE_OK) { result.len = 0; free(result.ptr); result.ptr = NULL; if (ret != CURLE_ABORTED_BY_CALLBACK) die("%s.", curl_easy_strerror(ret)); } else if (!result.ptr) result.ptr = xstrdup(""); if (final_len) *final_len = result.len; return result.ptr; } lastpass-cli/cmd-ls.c0000644000175000017500000001541112634367624015243 0ustar troyhebetroyhebe/* * command for listing the vault * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 static bool long_listing = false; static bool show_mtime = true; struct node { char *name; struct account *account; bool shared; struct node *first_child; struct node *next_sibling; }; static char *format_timestamp(char *timestamp, bool utc) { char temp[60]; struct tm *ts_tm; 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 insert_node(struct node *head, const char *path, struct account *account) { char *slash = NULL; struct node *child; while (*path && (slash = strchr(path, '/')) == path) ++path; if (!path) return; if (!slash) { child = new0(struct node, 1); child->account = account; child->shared = !!account->share; child->name = xstrdup(path); child->next_sibling = head->first_child; head->first_child = child; return; } for (child = head->first_child; child; child = child->next_sibling) { if (!strncmp(child->name, path, slash - path) && strlen(child->name) == (size_t)(slash - path)) break; } if (!child) { child = new0(struct node, 1); child->shared= !!account->share; child->name = xstrndup(path, slash - path); child->next_sibling = head->first_child; head->first_child = child; } insert_node(child, slash + 1, account); } static void free_node(struct node *head) { if (!head) return; for (struct node *node = head, *next_node = NULL; node; node = next_node) { next_node = node->next_sibling; free_node(node->first_child); free(node->name); free(node); } } static void print_node(struct node *head, int level) { struct node *node; for (node = head; node; node = node->next_sibling) { if (node->name) { for (int i = 0; i < level; ++i) printf(" "); if (node->account) { if (long_listing) { _cleanup_free_ char *timestr = show_mtime ? format_timestamp(node->account->last_modified_gmt, true) : format_timestamp(node->account->last_touch, false); terminal_printf(TERMINAL_FG_CYAN "%s ", timestr); } terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "%s" TERMINAL_NO_BOLD " [id: %s]" TERMINAL_RESET "\n", node->name, node->account->id); } 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->first_child, level + 1); } } static 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; } 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'}, {"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; 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 '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); /* '(none)' group -> search for any without group */ if (group && !strcmp(group, "(none)")) group = ""; list_for_each_entry(account, &blob->account_head, list) { if (group) { sub = strstr(account->fullname, group); if (!sub || sub != account->fullname) continue; group_len = strlen(group); sub += group_len; if (group[group_len - 1] != '/' && sub[0] != '\0' && sub[0] != '/') continue; } fullname = get_display_fullname(account); if (print_tree) insert_node(root, fullname, account); else { if (long_listing) { _cleanup_free_ char *timestr = show_mtime ? format_timestamp(account->last_modified_gmt, true) : format_timestamp(account->last_touch, false); printf("%s ", timestr); } printf("%s [id: %s]\n", fullname, account->id); } free(fullname); } if (print_tree) print_node(root, -1); free_node(root); session_free(session); blob_free(blob); return 0; } lastpass-cli/README.md0000644000175000017500000000753612634367503015204 0ustar troyhebetroyhebe# LastPass CLI #### (c) 2014-2015 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 * Binary packages are available in the [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). #### Redhat/Centos * 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 ``` #### Debian/Ubuntu * Install the needed build dependencies, and then follow instructions in the 'Building' section. * For Debian: ``` sudo apt-get install openssl libcurl3 libxml2 libssl-dev libxml2-dev libcurl4-openssl-dev pinentry-curses xclip ``` * For Ubuntu: ``` sudo apt-get install openssl libcurl4-openssl-dev libxml2 libssl-dev libxml2-dev pinentry-curses 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-pinentry ``` Alternatively, if you want to install the documentation as well: ``` brew install lastpass-cli --with-pinentry --with-doc ``` #### 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 gcc-core openssl-devel libcurl-devel libxml2-devel cygutils-extra ``` ## Building $ make ## 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 The `install-doc` target builds and installs the documentation. It requires AsciiDoc as a prerequisite. $ sudo make install-doc Once installed, $ man lpass lastpass-cli/kdf.c0000644000175000017500000001035112634367624014626 0ustar troyhebetroyhebe/* * key derivation routines * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/COPYING0000644000175000017500000004313112634367624014753 0ustar troyhebetroyhebe GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General Public License instead of this License. lastpass-cli/thawte.pem0000644000175000017500000000272512526714664015723 0ustar troyhebetroyhebe-----BEGIN CERTIFICATE----- MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta 3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk 6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 /qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 jVaMaA== -----END CERTIFICATE----- lastpass-cli/endpoints-login.c0000644000175000017500000002363312634367624017202 0ustar troyhebetroyhebe/* * https endpoints for logging into LastPass * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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" } }; 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 unsigned char key[KDF_HASH_LEN], char **args, char **cause, char **message, char **reply, struct session **session) { free(*reply); *reply = http_post_lastpass_v("login.php", NULL, NULL, args); if (!*reply) return error_post(message, session); *session = xml_ok_session(*reply, key); if (*session) return true; *cause = xml_error_cause(*reply, "cause"); if (!*cause) return error_other(message, session, "Unable to determine login failure cause."); 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 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"); 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.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) 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 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.php", NULL, NULL, args); if (!*reply) return error_post(message, session); *session = xml_ok_session(*reply, key); if (*session) 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; 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(key, args, &cause, error_message, &reply, &session)) return session; if (trust) { trusted_label = calculate_trust_label(); append_post(args, "trustlabel", trusted_label); } if (!strcmp(cause, "outofbandrequired") && oob_login(key, args, error_message, &reply, &otp_name, &session)) { if (trust) http_post_lastpass("trust.php", session->sessionid, NULL, "uuid", trusted_id, "trustlabel", trusted_label, NULL); return session; } if (otp_login(key, args, error_message, &reply, otp_name, cause, user_lower, &session)) return session; error_other(error_message, &session, "An unspecified error occured."); return NULL; } lastpass-cli/cmd-logout.c0000644000175000017500000000630212634367624016135 0ustar troyhebetroyhebe/* * command for logging out of LastPass * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 (!config_exists("verify")) die("Not currently logged in."); 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; } init_all(0, key, &session, NULL); if (!config_unlink("verify") || !config_unlink("username") || !config_unlink("session_sessionid") || !config_unlink("iterations")) die_errno("could not log out."); config_unlink("blob"); config_unlink("session_token"); config_unlink("session_uid"); config_unlink("session_privatekey"); config_unlink("plaintext_key"); agent_kill(); upload_queue_kill(); lastpass_logout(session); terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "Log out" TERMINAL_RESET ": complete.\n"); return 0; } lastpass-cli/cmd-duplicate.c0000644000175000017500000000732312634367624016602 0ustar troyhebetroyhebe/* * command for making copies of vault entries * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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); new->url = xstrdup(found->url); 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_free(session); blob_free(blob); return 0; } lastpass-cli/password.h0000644000175000017500000000033112422211655015711 0ustar troyhebetroyhebe#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/endpoints-share.c0000644000175000017500000002043112634367624017165 0ustar troyhebetroyhebe/* * https endpoints for shared folder manipulation * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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->sessionid, &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; size_t len; /* get the pubkey for the user/group */ reply = http_post_lastpass("share.php", session->sessionid, &len, "token", session->token, "getpubkey", "1", "uid", uid, "xmlr", "1", NULL); return xml_parse_share_getpubkey(reply, user); } static int lastpass_share_get_user_by_username(const struct session *session, const char *username, struct share_user *user) { _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->sessionid, &len, "token", session->token, "getpubkey", "1", "uid", uid_param, "xmlr", "1", NULL); return xml_parse_share_getpubkey(reply, user); } 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; ret = lastpass_share_get_user_by_username(session, user->username, user); if (ret) die("Unable to lookup user %s (%d)\n", user->username, ret); /* 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 = 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 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->sessionid, &len, "token", session->token, "id", share->id, "update", "1", "add", "1", "notify", "1", "uid0", user->uid, "sharekey0", hex_enc_share_key, "sharename", enc_share_name, "readonly", bool_str(user->read_only), "give", bool_str(!user->hide_passwords), "canadminister", bool_str(user->admin), "xmlr", "1", NULL); 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->sessionid, &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) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session->sessionid, &len, "token", session->token, "id", shareid, "update", "1", "delete", "1", "uid", user->uid, "xmlr", "1", NULL); 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_ char *hex_hash = 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_username, "%s-%s", user.username, sharename); 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); xasprintf(&sf_fullname, "Shared-%s", sharename); enc_share_name = encrypt_and_base64(sf_fullname, key); reply = http_post_lastpass("share.php", session->sessionid, &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) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session->sessionid, &len, "token", session->token, "id", share->id, "delete", "1", "xmlr", "1", NULL); return 0; } lastpass-cli/blob.h0000644000175000017500000000557312634367624015017 0ustar troyhebetroyhebe#ifndef BLOB_H #define BLOB_H #include "kdf.h" #include "session.h" #include "list.h" #include #include struct share_user { char *uid; char *username; char *realname; 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 { 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; char *username, *username_encrypted; char *password, *password_encrypted; char *note, *note_encrypted; char *last_touch, *last_modified_gmt; bool pwprotect; struct list_head field_head; struct share *share; struct list_head list; struct list_head match_list; }; struct blob { unsigned long long version; bool local_version; /* TODO: extract other data eventually... */ struct list_head account_head; struct list_head share_head; }; 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); 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]); void field_free(struct field *field); 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_note(struct account *account, char *note, unsigned const char key[KDF_HASH_LEN]); void account_assign_share(struct blob *blob, struct account *account, const char *name); 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); #endif lastpass-cli/config.c0000644000175000017500000002060412634367624015331 0ustar troyhebetroyhebe/* * configuration file handling * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 char *config_path(const char *name) { char *home, *path; _cleanup_free_ char *config = NULL; struct stat sbuf; int ret; home = getenv("LPASS_HOME"); if (home) config = xstrdup(home); 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); xasprintf(&path, "%s/%s", config, name); return path; } 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"); 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."); EVP_CIPHER_CTX_init(&ctx); ciphertext = xcalloc(in_len + AES_BLOCK_SIZE * 2 + SHA256_DIGEST_LENGTH, 1); 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; EVP_CIPHER_CTX_cleanup(&ctx); 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; *out = ciphertext; return len; error: EVP_CIPHER_CTX_cleanup(&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]; EVP_CIPHER_CTX_init(&ctx); 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_cleanup(&ctx); *out = plaintext; return len; error: EVP_CIPHER_CTX_cleanup(&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/cipher.h0000644000175000017500000000142512634367624015343 0ustar troyhebetroyhebe#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(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], char **ciphertext); char *cipher_base64(const char *bytes, size_t len); char *encrypt_and_base64(const char *str, unsigned const char key[KDF_HASH_LEN]); #endif lastpass-cli/kdf.h0000644000175000017500000000061112422211655014614 0ustar troyhebetroyhebe#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/agent.h0000644000175000017500000000047312422211655015154 0ustar troyhebetroyhebe#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_load_key(unsigned char key[KDF_HASH_LEN]); #endif lastpass-cli/clipboard.c0000644000175000017500000000646012634367624016027 0ustar troyhebetroyhebe/* * system copy/paste routines * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 `xclip`, `xsel`, `pbcopy`, or `putclip` installed."); } else { 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/xml.h0000644000175000017500000000074312634367624014673 0ustar troyhebetroyhebe#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); #endif lastpass-cli/LICENSE.OpenSSL0000644000175000017500000001547112422452323016177 0ustar troyhebetroyhebeCertain 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 rouines 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/cipher.c0000644000175000017500000002060312634367624015335 0ustar troyhebetroyhebe/* * encryption and decryption routines * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 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; if (p8inf->broken) 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(const char *plaintext, 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(strlen(plaintext), (unsigned char *) plaintext, (unsigned char *) 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; } 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; EVP_CIPHER_CTX_init(&ctx); 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_cleanup(&ctx); return plaintext; error: EVP_CIPHER_CTX_cleanup(&ctx); secure_clear(plaintext, len + AES_BLOCK_SIZE + 1); free(plaintext); return NULL; } size_t cipher_aes_encrypt(const char *plaintext, const unsigned char key[KDF_HASH_LEN], char **out) { EVP_CIPHER_CTX ctx; char *ciphertext; unsigned char iv[AES_BLOCK_SIZE]; int out_len; 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); EVP_CIPHER_CTX_init(&ctx); 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; 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 *)plaintext, in_len)) goto error; len += out_len; if (!EVP_EncryptFinal_ex(&ctx, (unsigned char *)(ciphertext + len), &out_len)) goto error; len += out_len; EVP_CIPHER_CTX_cleanup(&ctx); *out = ciphertext; return len; error: EVP_CIPHER_CTX_cleanup(&ctx); free(ciphertext); die("Failed to encrypt data."); } static char *base64(const 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 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); } static 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."); } char *cipher_aes_decrypt_base64(const char *ciphertext, const unsigned char key[KDF_HASH_LEN]) { _cleanup_free_ char *copy = NULL; _cleanup_free_ unsigned char *iv = NULL; _cleanup_free_ unsigned char *data = NULL; _cleanup_free_ unsigned char *unbase64_ciphertext = NULL; char *pipe; size_t iv_len, data_len, len; if (!strlen(ciphertext)) return NULL; if (ciphertext[0] == '!') { copy = xstrdup(&ciphertext[1]); pipe = strchr(copy, '|'); if (!pipe) return NULL; *pipe = '\0'; iv_len = unbase64(copy, &iv); data_len = unbase64(pipe + 1, &data); len = iv_len + data_len + 1 /* pound */; unbase64_ciphertext = xcalloc(len, 1); unbase64_ciphertext[0] = '!'; memcpy(&unbase64_ciphertext[1], iv, iv_len); memcpy(&unbase64_ciphertext[1 + iv_len], data, data_len); return cipher_aes_decrypt(unbase64_ciphertext, len, key); } else { len = unbase64(ciphertext, &data); return cipher_aes_decrypt(data, len, key); } } char *encrypt_and_base64(const char *str, unsigned const char key[KDF_HASH_LEN]) { char *intermediate = NULL; char *base64 = NULL; size_t len; base64 = trim(xstrdup(str)); if (!*base64) return base64; len = cipher_aes_encrypt(base64, key, &intermediate); free(base64); base64 = cipher_base64(intermediate, len); free(intermediate); return base64; } lastpass-cli/version.h0000644000175000017500000000015112634367503015545 0ustar troyhebetroyhebe#define LASTPASS_CLI_VERSION "0.7.0" #define LASTPASS_CLI_USERAGENT "LastPass-CLI/" LASTPASS_CLI_VERSION lastpass-cli/endpoints.h0000644000175000017500000000323212634367624016072 0ustar troyhebetroyhebe#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_create(const struct session *session, const char *sharename); int lastpass_share_delete(const struct session *session, struct share *share); #endif lastpass-cli/CONTRIBUTING0000644000175000017500000000431712634367503015551 0ustar troyhebetroyhebe 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/cmd-login.c0000644000175000017500000001025212634367624015733 0ustar troyhebetroyhebe/* * command for logging into the service * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 settting 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/cmd-rm.c0000644000175000017500000000600512634367624015242 0ustar troyhebetroyhebe/* * command for removing vault entries * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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); account_free(found); session_free(session); blob_free(blob); return 0; } lastpass-cli/agent.c0000644000175000017500000002132412634367624015162 0ustar troyhebetroyhebe/* * agent for caching decryption key * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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(__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 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(); if (strlen(path) >= sizeof(sa.sun_path)) die("Path too large for agent control socket."); fd = socket(AF_UNIX, SOCK_STREAM, 0); memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strlcpy(sa.sun_path, path, sizeof(sa.sun_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(); if (strlen(path) >= sizeof(sa.sun_path)) die("Path too large for agent control socket."); fd = socket(AF_UNIX, SOCK_STREAM, 0); memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strlcpy(sa.sun_path, path, sizeof(sa.sun_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); } static 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(); if (strlen(path) >= sizeof(sa.sun_path)) die("Path too large for agent control socket."); fd = socket(AF_UNIX, SOCK_STREAM, 0); memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strlcpy(sa.sun_path, path, sizeof(sa.sun_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; 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]) { char *disable_str; 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; disable_str = getenv("LPASS_AGENT_DISABLE"); if (!disable_str || strcmp(disable_str, "1")) { 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/pbkdf2.c0000644000175000017500000000531212634367503015227 0ustar troyhebetroyhebe/* * Copyright (c) 2014-2015 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. */ #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]; HMAC_CTX_init(&ctx); 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 HMAC_CTX_cleanup(&ctx); return ret; } lastpass-cli/cmd.h0000644000175000017500000000517512634367624014642 0ustar troyhebetroyhebe#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), }; 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 blob *blob, const char *name, struct list_head *ret_list); void find_matching_regex(struct blob *blob, const char *pattern, int fields, struct list_head *ret_list); void find_matching_substr(struct blob *blob, 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); #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_show(int argc, char **argv); #define cmd_show_usage "show [--sync=auto|now|no] [--clip, -c] [--all|--username|--password|--url|--notes|--field=FIELD|--id|--name] [--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] " color_usage " [GROUP]" 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_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 int cmd_share(int argc, char **argv); #define cmd_share_usage "share " color_usage " subcommand sharename..." #endif lastpass-cli/terminal.c0000644000175000017500000000542512634367624015703 0ustar troyhebetroyhebe/* * terminal printing routines * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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; 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/Makefile0000644000175000017500000000413112634367503015351 0ustar troyhebetroyhebePREFIX ?= /usr DESTDIR ?= BINDIR ?= $(PREFIX)/bin LIBDIR ?= $(PREFIX)/lib MANDIR ?= $(PREFIX)/share/man CFLAGS ?= -O3 -march=native -fomit-frame-pointer -pipe CFLAGS += -std=gnu99 -D_GNU_SOURCE CFLAGS += -pedantic -Wall -Wextra -Wno-language-extension-token CFLAGS += -MMD UNAME_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(UNAME_S),Darwin) SDKROOT ?= $(shell xcodebuild -version -sdk macosx | sed -n 's/^Path: \(.*\)/\1/p') CFLAGS += -Wno-deprecated-declarations -isysroot $(SDKROOT) -I$(SDKROOT)/usr/include/libxml2 LDLIBS = -lcurl -lxml2 -lssl -lcrypto else CFLAGS += $(shell pkg-config --cflags libxml-2.0 2>/dev/null || echo -I/usr/include/libxml2) -I/usr/local/include LDLIBS = -lcurl $(shell pkg-config --libs libxml-2.0 2>/dev/null || echo -lxml2) -lssl -lcrypto ifeq ($(UNAME_S),OpenBSD) LDLIBS += -lkvm endif endif all: lpass doc-man: lpass.1 doc-html: lpass.1.html doc: doc-man doc-html lpass: $(patsubst %.c,%.o,$(wildcard *.c)) %.1: %.1.txt a2x --no-xmllint -f manpage $< %.1.html: %.1.txt asciidoc -b html5 -a data-uri -a icons -a toc2 $< http.c: certificate.h certificate.h: thawte.pem awk 'BEGIN {printf "#define CERTIFICATE_THAWTE \""} {printf "%s\\n", $$0} END {printf "\"\n"}' thawte.pem > certificate.h || rm -f certificate.h install-doc: doc-man @install -v -d "$(DESTDIR)$(MANDIR)/man1" && install -m 0644 -v lpass.1 "$(DESTDIR)$(MANDIR)/man1/lpass.1" install: all @install -v -d "$(DESTDIR)$(BINDIR)" && install -m 0755 -v lpass "$(DESTDIR)$(BINDIR)/lpass" uninstall: @rm -vrf "$(DESTDIR)$(MANDIR)/man1/lpass.1" "$(DESTDIR)$(BINDIR)/lpass" @rmdir "$(DESTDIR)$(MANDIR)/man1" "$(DESTDIR)$(BINDIR)" 2>/dev/null || true clean: rm -f lpass *.o *.d lpass.1 lpass.1.html certificate.h lpass.exe analyze: clean CFLAGS=-g scan-build -enable-checker alpha.core -enable-checker alpha.deadcode -enable-checker alpha.security -enable-checker alpha.unix -enable-checker security -enable-checker core -enable-checker deadcode -enable-checker unix -disable-checker alpha.core.PointerSub --view --keep-going $(MAKE) lpass .PHONY: all doc doc-man doc-html test-deps clean analyze -include *.d lastpass-cli/blob.c0000644000175000017500000006040612634367624015006 0ustar troyhebetroyhebe/* * encrypted vault parsing * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 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); } struct account *new_account() { struct account *account = new0(struct account, 1); INIT_LIST_HEAD(&account->field_head); return account; } void account_free(struct account *account) { struct field *field, *tmp; if (!account) return; 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->username_encrypted); free(account->password_encrypted); free(account->note_encrypted); list_for_each_entry_safe(field, tmp, &account->field_head, list) { field_free(field); } 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((char *) 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'; } #define entry_plain(var) do { \ char *__entry_val__ = read_plain_string(chunk); \ if (!__entry_val__) \ goto error; \ parsed->var = __entry_val__; \ } while (0) #define entry_hex(var) do { \ char *__entry_val__ = read_hex_string(chunk); \ if (!__entry_val__) \ goto error; \ parsed->var = __entry_val__; \ } while (0) #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(var) do { \ char *__entry_val__ = read_crypt_string(chunk, key, &parsed->var##_encrypted); \ if (!__entry_val__) \ goto error; \ parsed->var = __entry_val__; \ } while (0) #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); entry_hex(url); entry_crypt(note); skip(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); skip(attachkey); skip(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->name) && strlen(parsed->group)) 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 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; } #undef entry_plain #undef entry_hex #undef entry_boolean #undef entry_crypt #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 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); } } if (!versionstr) goto error; if (list_empty(&parsed->account_head)) goto error; return parsed; error: blob_free(parsed); return NULL; } struct buffer { size_t len; size_t max; char *bytes; }; static 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; } 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 *bytes, const unsigned char key[KDF_HASH_LEN]) { _cleanup_free_ char *encrypted = NULL; size_t len; len = cipher_aes_encrypt(bytes, key, &encrypted); if (!encrypted) die("Could not write encrypted string."); write_item(buffer, 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_account_chunk(struct buffer *buffer, struct account *account, const unsigned char key[KDF_HASH_LEN]) { struct buffer accbuf, fieldbuf; struct field *field; memset(&accbuf, 0, sizeof(accbuf)); write_plain_string(&accbuf, account->id); write_crypt_string(&accbuf, account->name, key); write_crypt_string(&accbuf, account->group, key); write_hex_string(&accbuf, account->url); write_crypt_string(&accbuf, account->note, key); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_crypt_string(&accbuf, account->username, key); write_crypt_string(&accbuf, account->password, key); 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, key); 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) { struct buffer buffer; struct share *last_share = NULL; struct account *account; 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, key); } 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, account->share->key); } *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 (local->version < remote_version || (local->local_version && local->version == remote_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_AUTO) { if (!config_exists("blob")) return blob_get_latest(session, key); else if (time(NULL) - config_mtime("blob") <= auto_sync_time()) return local_blob(key, &session->private_key); return blob_get_latest(session, key); } else if (sync == BLOB_SYNC_YES) return blob_get_latest(session, key); else if (sync == BLOB_SYNC_NO) return local_blob(key, &session->private_key); return NULL; } void blob_save(const struct blob *blob, const unsigned char key[KDF_HASH_LEN]) { _cleanup_free_ char *bluffer = NULL; size_t len; len = blob_write(blob, key, &bluffer); 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 { \ set_field(obj, field); \ free(obj->field##_encrypted); \ obj->field##_encrypted = encrypt_and_base64(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 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); } void account_set_fullname(struct account *account, char *fullname, unsigned const char key[KDF_HASH_LEN]) { if (account->share && is_shared_folder_name(fullname)) { char *groupname = strchr(fullname, '/'); if (groupname) { char *tmp = fullname; fullname = xstrdup(groupname + 1); free(tmp); } } char *slash = strrchr(fullname, '/'); if (!slash) { account_set_name(account, xstrdup(fullname), key); account_set_group(account, xstrdup(""), key); } else { account_set_name(account, xstrdup(slash + 1), key); account_set_group(account, xstrndup(fullname, slash - fullname), 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. * * 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, const char *name) { struct share *share; _cleanup_free_ char *shared_name = NULL; if (!is_shared_folder_name(name)) return; /* strip off shared groupname */ char *slash = strchr(name, '/'); if (!slash) die("Shared folder name has improper format"); shared_name = xstrndup(name, slash - name); /* find a share matching group name */ share = find_unique_share(blob, shared_name); if (!share) die("Unable to find shared folder for %s in blob\n", name); account->share = share; } struct account *notes_expand(struct account *acc) { struct account *expand; struct field *field; char *start, *lf, *colon, *name, *value; char *line = NULL; size_t len; if (strcmp(acc->url, "http://sn")) 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; for (start = acc->note; ; ) { lf = strchrnul(start, '\n'); if (lf - start < 0) lf = NULL; if (lf - start <= 0) goto skip; line = xstrndup(start, lf - start); colon = strchr(line, ':'); if (!colon) goto skip; *colon = '\0'; name = line; value = colon + 1; 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(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(""); return expand; } struct account *notes_collapse(struct account *acc) { struct account *collapse; struct field *field; 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; 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/list.h0000644000175000017500000002765212453525127015047 0ustar troyhebetroyhebe#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; } #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) /** * 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_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. * */ #define container_of(ptr, type, member) __extension__({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #endif lastpass-cli/terminal.h0000644000175000017500000000322112453525127015671 0ustar troyhebetroyhebe#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/xml.c0000644000175000017500000002315312634367624014666 0ustar troyhebetroyhebe/* * xml parsing routines * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 #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 = xmlParseMemory(buf, strlen(buf)); 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); } } } 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 = xmlParseMemory(buf, strlen(buf)); 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); } } } 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 = xmlParseMemory(buf, strlen(buf)); 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); 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); *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, "outsideenterpise", &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); } } int xml_parse_share_getinfo(const char *buf, struct list_head *users) { int ret; xmlDoc *doc = xmlParseMemory(buf, strlen(buf)); 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_getpubkey(const char *buf, struct share_user *user) { int ret; xmlDoc *doc = xmlParseMemory(buf, strlen(buf)); char *tmp; if (!doc) return -EINVAL; /* * XML fields are as follows: * xmlresponse * success * pubkey0 * uid0 * username0 */ xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "xmlresponse") || !root->children) { ret = -EINVAL; goto free_doc; } user->sharing_key.key = NULL; user->sharing_key.len = 0; for (xmlNode *item = root->children; item; item = item->next) { if (xml_parse_str(doc, item, "pubkey0", &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, "username0", &user->username)) continue; if (xml_parse_str(doc, item, "uid0", &user->uid)) continue; } if (!user->sharing_key.len) ret = -ENOENT; else ret = 0; free_doc: xmlFreeDoc(doc); return ret; } lastpass-cli/contrib/0000755000175000017500000000000012634367624015356 5ustar troyhebetroyhebelastpass-cli/contrib/specfile/0000755000175000017500000000000012453525127017141 5ustar troyhebetroyhebelastpass-cli/contrib/specfile/lastpass-cli.spec0000644000175000017500000000151312453525127022414 0ustar troyhebetroyhebeName: 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/contrib/examples/0000755000175000017500000000000012453525127017165 5ustar troyhebetroyhebelastpass-cli/contrib/examples/change-ssh-password.sh0000755000175000017500000000434312453525127023410 0ustar troyhebetroyhebe#!/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/contrib/examples/lpass-sudo.sh0000755000175000017500000000027412453525127021621 0ustar troyhebetroyhebe#!/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/contrib/examples/change-mysql-password.sh0000755000175000017500000000213412453525127023754 0ustar troyhebetroyhebe#!/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/contrib/examples/msmtprc0000644000175000017500000000042512422452323020567 0ustar troyhebetroyhebedefaults 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/contrib/examples/lpass-sudo-askpass.sh0000755000175000017500000000042112453525127023256 0ustar troyhebetroyhebe#!/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/util.h0000644000175000017500000000666112634367503015051 0ustar troyhebetroyhebe#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)) 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); 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/config.h0000644000175000017500000000206212634367503015330 0ustar troyhebetroyhebe#ifndef CONFIG_H #define CONFIG_H #include "kdf.h" #include #include #include 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/upload-queue.c0000644000175000017500000001754312634367624016502 0ustar troyhebetroyhebe/* * queue for changes uploaded to LastPass * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "process.h" #include "password.h" #include "endpoints.h" #include #include #include #include #include #include #include #include #include #include #include #include static void upload_queue_write_entry(const char *entry, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *base_path = NULL; _cleanup_free_ char *name = NULL; struct stat sbuf; int ret; unsigned long serial; base_path = config_path("upload-queue"); 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); for (serial = 0; serial < ULONG_MAX; ++serial) { free(name); xasprintf(&name, "upload-queue/%lu%lu", 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 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) 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) { 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; while ((entry = upload_queue_next_entry(key, &name, &lock))) { 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; for (int i = 0; i < 5; ++i) { sleep(i * 2); result = http_post_lastpass_v(argv[0], session->sessionid, NULL, &argv[1]); if (result && strlen(result)) should_fetch_new_blob_after = true; free(result); if (result) break; } if (!result) { sleep(30); config_unlink(lock); } else { 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); if (null >= 0) { dup2(null, 0); dup2(null, 1); dup2(null, 2); close(null); } 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); upload_queue_upload_all(session, key); 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, ...) { _cleanup_free_ char *sum = xstrdup(page); char *next = NULL; char *escaped = NULL; va_list params; char *param; va_start(params, page); while ((param = va_arg(params, char *))) { escaped = pinentry_escape(param); xasprintf(&next, "%s\n%s", sum, escaped); free(escaped); free(sum); sum = next; } va_end(params); 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/lpass.c0000644000175000017500000001141012634367624015201 0ustar troyhebetroyhebe/* * lpass - lastpass command line utility * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "cmd.h" #include "string.h" #include "util.h" #include "config.h" #include "terminal.h" #include "version.h" #include #include #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(show), CMD(ls), CMD(edit), CMD(generate), CMD(duplicate), CMD(rm), CMD(sync), CMD(export), 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", 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"); 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); load_saved_environment(); if (argc >= 2 && argv[1][0] != '-') return process_command(argc - 1, argv + 1); return global_options(argc, argv); } lastpass-cli/cmd.c0000644000175000017500000001434512634367624014634 0ustar troyhebetroyhebe/* * general utility functions used by multiple commands * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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; } 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 = sesssion_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; } static void search_accounts(struct blob *blob, const void *needle, int (*cmp)(const char *haystack, const char *needle), int fields, struct list_head *ret_list) { struct account *account; list_for_each_entry(account, &blob->account_head, 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_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 blob *blob, 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(blob, ®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 blob *blob, const char *pattern, int fields, struct list_head *ret_list) { search_accounts(blob, pattern, cmp_substr, fields, ret_list); } /* * Search blob 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 blob *blob, const char *name, struct list_head *ret_list) { /* look for exact id match */ struct account *account; list_for_each_entry(account, &blob->account_head, list) { if (strcmp(name, "0") && !strcasecmp(account->id, name)) { list_add_tail(&account->match_list, ret_list); /* if id match, stop processing */ return; } } /* search for fullname or name match */ search_accounts(blob, name, strcmp, ACCOUNT_NAME | ACCOUNT_FULLNAME, ret_list); } struct account *find_unique_account(struct blob *blob, const char *name) { struct list_head matches; struct account *account, *last_account; INIT_LIST_HEAD(&matches); find_matching_accounts(blob, 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/endpoints.c0000644000175000017500000001532312634367624016071 0ustar troyhebetroyhebe/* * https endpoints for LastPass services * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 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->sessionid, NULL, "method", "cli", "noredirect", "1", 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->sessionid, &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) { ++blob->version; if (account->share) upload_queue_enqueue(sync, key, session, "show_website.php", "extjs", "1", "token", session->token, "delete", "1", "aid", account->id, "sharedfolderid", account->share->id, NULL); else upload_queue_enqueue(sync, key, session, "show_website.php", "extjs", "1", "token", session->token, "delete", "1", "aid", account->id, NULL); } 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; } 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) { _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; if (account->share) upload_queue_enqueue(sync, key, session, "show_website.php", "extjs", "1", "token", session->token, "aid", account->id, "name", account->name_encrypted, "grouping", account->group_encrypted, "url", url, "username", account->username_encrypted, "password", account->password_encrypted, /* "data", fields, Removing until server-side catches up. */ "pwprotect", account->pwprotect ? "on" : "off", "extra", account->note_encrypted, "sharedfolderid", account->share->id, NULL); else upload_queue_enqueue(sync, key, session, "show_website.php", "extjs", "1", "token", session->token, "aid", account->id, "name", account->name_encrypted, "grouping", account->group_encrypted, "url", url, "username", account->username_encrypted, "password", account->password_encrypted, /* "data", fields, Removing until server-side catches up. */ "pwprotect", account->pwprotect ? "on" : "off", "extra", account->note_encrypted, NULL); } 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->sessionid, 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) { if (!strcmp(account->id, "0")) return; if (!account->share) upload_queue_enqueue(sync, key, session, "loglogin.php", "id", account->id, "method", "cli", NULL); else upload_queue_enqueue(sync, key, session, "loglogin.php", "id", account->id, "method", "cli", "sharedfolderid", account->share->id, NULL); } lastpass-cli/http.h0000644000175000017500000000040012634367624015040 0ustar troyhebetroyhebe#ifndef HTTP_H #define HTTP_H #include #include char *http_post_lastpass(const char *page, const char *session, size_t *len, ...); char *http_post_lastpass_v(const char *page, const char *session, size_t *len, char **argv); #endif lastpass-cli/lpass.1.txt0000644000175000017500000002075112634367624015745 0ustar troyhebetroyhebe: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 *show* [--sync=auto|now|no] [--clip, -c] [--all|--username|--password|--url|--notes|--field=FIELD|--id|--name] [--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 *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 *sync* [--background, -b] [--color=auto|never|always] lpass *export* [--sync=auto|now|no] [--color=auto|never|always] 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 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 ~~~~~~~~~~~~~~ If available, the *pinentry* program, part of *gpg2*(1), may be used for inputting passwords if it is installed. If 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 cache and configuration folder inside the current user's home directory – '~/.lpass' – or in the directory specified by the environment variable 'LPASS_HOME'. It will 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. Viewing ~~~~~~~ The 'show' subcommand will display a password or selected field. 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. The 'export' subcommand will dump all account information including passwords to stdout (unencrypted). 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'. 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. 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 ~~~~~~~~~~~~~ All configuration may be specified via environment variables. Alternatively, a set of environment variable overrides may be specified in '~/.lpass/env' in the form of: [verse] VARIABLE1=VALUE1 VARIABLE2=VALUE2 ... 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_DISABLE_PINENTRY' * 'LPASS_ASKPASS' * 'LPASS_CLIPBOARD_COMMAND' lastpass-cli/process.h0000644000175000017500000000035212422211655015530 0ustar troyhebetroyhebe#ifndef PROCESS_H #define PROCESS_H #include #include int ARGC; 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/session.c0000644000175000017500000001057312634367624015553 0ustar troyhebetroyhebe/* * session handling routines * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 #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); } 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) { #define start_str "LastPassPrivateKey<" #define end_str ">LastPassPrivateKey" size_t len; _cleanup_free_ unsigned char *encrypted_key = NULL; _cleanup_free_ char *decrypted_key = NULL; unsigned char *encrypted_key_start; char *start, *end; len = strlen(key_hex); if (len % 2 != 0) die("Key hex in wrong format."); len /= 2; len += 16 /* IV */ + 1 /* pound 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."); else { start = strstr(decrypted_key, start_str); end = strstr(decrypted_key, end_str); if (!start || !end || end <= start) warn("Could not decode decrypted private key."); else { start += strlen(start_str); *end = '\0'; len = strlen(start); if (len % 2 != 0) die("Invalid private key after decryption and decoding."); len /= 2; hex_to_bytes(start, &session->private_key.key); session->private_key.len = len; mlock(session->private_key.key, len); } } #undef start_str #undef end_str } 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); } struct session *sesssion_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->private_key.len = config_read_encrypted_buffer("session_privatekey", &session->private_key.key, key); mlock(session->private_key.key, session->private_key.len); if (session_is_valid(session)) return session; else { session_free(session); return NULL; } } lastpass-cli/cmd-sync.c0000644000175000017500000000520112634367624015575 0ustar troyhebetroyhebe/* * command to synchronize with LastPass servers * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/cmd-show.c0000644000175000017500000001713412634367624015611 0ustar troyhebetroyhebe/* * command to show the contents of a vault entry * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "kdf.h" #include "endpoints.h" #include "clipboard.h" #include #include #include #include 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 = xstrdup(field->value); return value; } static void print_header(struct account *found) { if (found->share) terminal_printf(TERMINAL_FG_CYAN "%s/" TERMINAL_RESET, found->share->name); if (strlen(found->group)) terminal_printf(TERMINAL_FG_BLUE "%s/" TERMINAL_BOLD "%s" TERMINAL_RESET TERMINAL_FG_GREEN " [id: %s]" TERMINAL_RESET "\n", found->group, found->name, found->id); else terminal_printf(TERMINAL_FG_BLUE TERMINAL_BOLD "%s" TERMINAL_RESET TERMINAL_FG_GREEN " [id: %s]" TERMINAL_RESET "\n", found->name, found->id); } 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'}, {"clip", no_argument, NULL, 'c'}, {"color", required_argument, NULL, 'C'}, {"basic-regexp", no_argument, NULL, 'G'}, {"fixed-strings", no_argument, NULL, 'F'}, {0, 0, 0, 0} }; int option; int option_index; enum { ALL, USERNAME, PASSWORD, URL, FIELD, ID, NAME, NOTES } 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; enum blobsync sync = BLOB_SYNC_AUTO; bool clip = false; struct list_head matches; enum search_type search = SEARCH_EXACT_MATCH; int fields = ACCOUNT_NAME | ACCOUNT_ID; while ((option = getopt_long(argc, argv, "cupFG", 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 'O': choice = NOTES; break; case 'c': clip = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_show_usage); } } if (argc - optind != 1) die_usage(cmd_show_usage); name = argv[optind]; init_all(sync, key, &session, &blob); INIT_LIST_HEAD(&matches); switch (search) { case SEARCH_EXACT_MATCH: find_matching_accounts(blob, name, &matches); break; case SEARCH_BASIC_REGEX: find_matching_regex(blob, name, fields, &matches); break; case SEARCH_FIXED_SUBSTRING: find_matching_substr(blob, name, fields, &matches); break; } if (list_empty(&matches)) die("Could not find specified account '%s'.", name); found = list_first_entry(&matches, struct account, match_list); last_found = list_last_entry(&matches, struct account, match_list); if (found != last_found) { /* 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(found); exit(EXIT_SUCCESS); } 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."); } lastpass_log_access(sync, session, key, found); notes_expansion = notes_expand(found); if (notes_expansion) found = notes_expansion; if (choice == FIELD) { struct field *found_field; list_for_each_entry(found_field, &found->field_head, list) { if (!strcmp(found_field->name, field)) break; } if (!found_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); if (clip) clipboard_open(); if (choice == ALL) { print_header(found); if (strlen(found->username)) terminal_printf(TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ": %s\n", "Username", found->username); if (strlen(found->password)) terminal_printf(TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ": %s\n", "Password", found->password); if (strlen(found->url) && strcmp(found->url, "http://")) terminal_printf(TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ": %s\n", "URL", found->url); list_for_each_entry(found_field, &found->field_head, list) { pretty_field = pretty_field_value(found_field); terminal_printf(TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ": %s\n", found_field->name, pretty_field); free(pretty_field); } if (strlen(found->note)) terminal_printf(TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ":\n%s\n", "Notes", found->note); } else { if (!value) die("Programming error."); printf("%s", value); if (!clip) putchar('\n'); } account_free(notes_expansion); session_free(session); blob_free(blob); return 0; } lastpass-cli/clipboard.h0000644000175000017500000000015012422211655016005 0ustar troyhebetroyhebe#ifndef CLIPBOARD_H #define CLIPBOARD_H void clipboard_open(void); void clipboard_close(void); #endif lastpass-cli/util.c0000644000175000017500000002726712634367624015055 0ustar troyhebetroyhebe/* * utility functions * * Copyright (C) 2014-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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); } bool ask_yes_no(bool default_yes, const char *prompt, ...) { va_list params; _cleanup_free_ char *response = NULL; size_t len = 0; for (;;) { va_start(params, prompt); terminal_fprintf(stderr, TERMINAL_FG_YELLOW); vfprintf(stderr, prompt, params); terminal_fprintf(stderr, TERMINAL_RESET); va_end(params); if (default_yes) terminal_fprintf(stderr, " [" TERMINAL_BOLD "Y" TERMINAL_RESET "/n] "); else terminal_fprintf(stderr, " [y/" TERMINAL_BOLD "N" TERMINAL_RESET "] "); if (getline(&response, &len, stdin) < 0) die("aborted response."); strlower(response); if (!strcmp("y\n", response) || !strcmp("yes\n", response)) return true; else if (!strcmp("n\n", response) || !strcmp("no\n", response)) return false; else if (!strcmp("\n", response)) return default_yes; else { terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "Error" TERMINAL_RESET ": Response not understood.\n"); free(response); response = NULL; len = 0; } } } 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 ((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)", str); } void *xstrndup(const char *str, size_t maxlen) { void *ret = strndup(str, maxlen); if (likely(ret)) return ret; die_errno("strndup(%p, %zu)", 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/cmd-share.c0000644000175000017500000002032312634367624015725 0ustar troyhebetroyhebe/* * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 #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; }; #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_rm_usage "rm SHARE" static char *checkmark(int x) { return (x) ? "x" : "_"; } static int share_userls(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_usage(cmd_share_usage); if (!args->share) die("Share %s not found.", args->sharename); 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(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_usage(cmd_share_usage); 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(int argc, char **argv, struct share_args *args) { struct share_user *user; if (argc != 1) die_usage(cmd_share_usage); 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(int argc, char **argv, struct share_args *args) { struct share_user *found; if (argc != 1) die_usage(cmd_share_usage); found = get_user_from_share(args->session, args->share, argv[0]); lastpass_share_user_del(args->session, args->share->id, found); return 0; } static int share_create(int argc, char **argv, struct share_args *args) { if (argc != 0) die_usage(cmd_share_usage); UNUSED(argv); lastpass_share_create(args->session, args->sharename); return 0; } static int share_rm(int argc, char **argv, struct share_args *args) { if (argc != 0) die_usage(cmd_share_usage); UNUSED(argv); lastpass_share_delete(args->session, args->share); return 0; } #define SHARE_CMD(name) { #name, "share " share_##name##_usage, share_##name } static struct { const char *name; const char *usage; int (*cmd)(int, char **, struct share_args *share); } share_commands[] = { SHARE_CMD(userls), SHARE_CMD(useradd), SHARE_CMD(usermod), SHARE_CMD(userdel), SHARE_CMD(create), SHARE_CMD(rm), }; #undef SHARE_CMD 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'}, {0, 0, 0, 0} }; struct share_args args = { .sync = BLOB_SYNC_AUTO, .read_only = true, .hide_passwords = true, }; /* * 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. * * Although we look up the share based on the supplied sharename, * it may not exist, in the case of commands such as 'add'. Subcmds * should check args.share before using it. */ int option; int option_index; while ((option = getopt_long(argc, argv, "S:C:r:H:a:", 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 '?': default: die_usage(cmd_share_usage); } } if (argc - optind < 2) die_usage(cmd_share_usage); subcmd = argv[optind++]; 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); for (unsigned int i=0; i < ARRAY_SIZE(share_commands); i++) { if (strcmp(subcmd, share_commands[i].name) == 0) { share_commands[i].cmd(argc - optind, &argv[optind], &args); } } session_free(args.session); blob_free(args.blob); return 0; }