lastpass-cli-1.0.0/0000755000175000017500000000000013003405376014334 5ustar troyhebetroyhebelastpass-cli-1.0.0/password.c0000644000175000017500000002271012743671271016355 0ustar troyhebetroyhebe/* * queue for changes uploaded to LastPass * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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-1.0.0/session.h0000644000175000017500000000132412743671271016201 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; char *server; 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); void session_kill(); #endif lastpass-cli-1.0.0/upload-queue.h0000644000175000017500000000074512743671271017132 0ustar troyhebetroyhebe#ifndef UPLOADQUEUE_H #define UPLOADQUEUE_H #include "kdf.h" #include "session.h" #include "blob.h" #include "http.h" #include void upload_queue_enqueue(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const char *page, struct http_param_set *params); bool upload_queue_is_running(void); void upload_queue_kill(void); void upload_queue_ensure_running(unsigned const char key[KDF_HASH_LEN], const struct session *session); #endif lastpass-cli-1.0.0/cmd-edit.c0000644000175000017500000001026112743671271016177 0ustar troyhebetroyhebe/* * command for editing vault entries * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "endpoints.h" #include "agent.h" #include #include #include #include #include int cmd_edit(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"username", no_argument, NULL, 'u'}, {"password", no_argument, NULL, 'p'}, {"url", no_argument, NULL, 'L'}, {"field", required_argument, NULL, 'F'}, {"name", no_argument, NULL, 'N'}, {"notes", no_argument, NULL, 'O'}, {"non-interactive", no_argument, NULL, 'X'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; _cleanup_free_ char *field = NULL; char *name; bool non_interactive = false; enum blobsync sync = BLOB_SYNC_AUTO; struct account *editable; enum edit_choice choice = EDIT_ANY; enum note_type note_type = NOTE_TYPE_NONE; #define ensure_choice() if (choice != EDIT_ANY) goto choice_die; while ((option = getopt_long(argc, argv, "up", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'u': ensure_choice(); choice = EDIT_USERNAME; break; case 'p': ensure_choice(); choice = EDIT_PASSWORD; break; case 'L': ensure_choice(); choice = EDIT_URL; break; case 'F': ensure_choice(); choice = EDIT_FIELD; field = xstrdup(optarg); break; case 'N': ensure_choice(); choice = EDIT_NAME; break; case 'O': ensure_choice(); choice = EDIT_NOTES; break; case 'X': non_interactive = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_edit_usage); } } #undef ensure_choice if (argc - optind != 1) die_usage(cmd_edit_usage); if (choice == EDIT_NONE) choice_die: die_usage("edit ... {--name|--username|--password|--url|--notes|--field=FIELD}"); name = argv[optind]; init_all(sync, key, &session, &blob); editable = find_unique_account(blob, name); if (!editable) return edit_new_account(session, blob, sync, name, choice, field, non_interactive, false, note_type, key); if (editable->share && editable->share->readonly) die("%s is a readonly shared entry from %s. It cannot be edited.", editable->fullname, editable->share->name); return edit_account(session, blob, sync, editable, choice, field, non_interactive, key); } lastpass-cli-1.0.0/pbkdf2.h0000644000175000017500000000272512743671271015674 0ustar troyhebetroyhebe/* * Copyright (c) 2014-2016 Thomas Hurst. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef PBKDF2_H #define PBKDF2_H #include #if OPENSSL_VERSION_NUMBER < 0x10000000L #define PKCS5_PBKDF2_HMAC fallback_pkcs5_pbkdf2_hmac #endif int fallback_pkcs5_pbkdf2_hmac(const char *pass, size_t pass_len, const unsigned char *salt, size_t salt_len, unsigned int iterations, const EVP_MD *digest, size_t key_len, unsigned char *output); #endif lastpass-cli-1.0.0/cmd-passwd.c0000644000175000017500000001655112743671271016563 0ustar troyhebetroyhebe/* * command for changing master password * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include #include #include "blob.h" #include "kdf.h" #include "cmd.h" #include "endpoints.h" #include "config.h" #include "password.h" #include "cipher.h" #include "session.h" static void show_status_bar(const char *operation, unsigned int cur, unsigned int max) { char progress[41] = {0}; size_t len; if (!max) max = 1; if (cur > max) cur = max; len = (cur * (sizeof(progress) - 1)) / max; if (len) memset(progress, '=', len); terminal_fprintf(stderr, TERMINAL_FG_CYAN "%s " TERMINAL_RESET TERMINAL_FG_BLUE "[%-*s] " TERMINAL_RESET TERMINAL_FG_CYAN "%d/%d \r" TERMINAL_RESET, operation, (int) sizeof(progress)-1, progress, cur, max); } static void reencrypt(struct session *session, struct pwchange_info *info, unsigned char key[KDF_HASH_LEN], unsigned char new_key[KDF_HASH_LEN]) { struct pwchange_field *field; struct pwchange_su_key *su_key; struct private_key tmp; unsigned int n_fields = 0; unsigned int i = 0; unsigned int n_required = 0; unsigned int errors = 0; /* count how many things we'll encrypt */ list_for_each_entry(field, &info->fields, list) { n_fields++; } list_for_each_entry(su_key, &info->su_keys, list) { n_fields++; } /* plus sharing key */ n_fields++; show_status_bar("Re-encrypting", i++, n_fields); /* decrypt and re-encrypt RSA sharing key */ cipher_decrypt_private_key(info->privkey_encrypted, key, &tmp); if (tmp.len != session->private_key.len || memcmp(session->private_key.key, tmp.key, session->private_key.len)) { die("Server and session private key don't match! Try lpass sync first."); } info->new_privkey_encrypted = cipher_encrypt_private_key(&tmp, new_key); secure_clear(tmp.key, tmp.len); free(tmp.key); /* reencrypt site info */ list_for_each_entry(field, &info->fields, list) { show_status_bar("Re-encrypting", i++, n_fields); if (!field->optional) n_required++; char *ptext = cipher_aes_decrypt_base64(field->old_ctext, key); if (!ptext) { if (!field->optional) errors++; ptext = " "; } field->new_ctext = encrypt_and_base64(ptext, new_key); } /* * Fail if > 10% decryption errors. This indicates the blob and key * are out of sync somehow, or that user has reverted a password * change but some entries are encrypted with the new key. */ if (errors > n_required / 10) die("Too many decryption failures."); /* encrypt recovery copy of our key */ list_for_each_entry(su_key, &info->su_keys, list) { show_status_bar("Re-encrypting", i++, n_fields); size_t enc_key_len = su_key->sharing_key.len; unsigned char *enc_key = xmalloc(enc_key_len); cipher_rsa_encrypt_bytes(new_key, KDF_HASH_LEN, &su_key->sharing_key, enc_key, &enc_key_len); bytes_to_hex(enc_key, &su_key->new_enc_key, enc_key_len); free(enc_key); } show_status_bar("Re-encrypting", n_fields, n_fields); info->new_privkey_hash = cipher_sha256_hex((unsigned char *) info->new_privkey_encrypted, strlen(info->new_privkey_encrypted)); info->new_key_hash = cipher_sha256_hex(new_key, KDF_HASH_LEN); printf("\n"); } int cmd_passwd(int argc, char **argv) { UNUSED(argc); UNUSED(argv); unsigned char key[KDF_HASH_LEN]; unsigned char new_key[KDF_HASH_LEN]; char hex[KDF_HEX_LEN]; char new_hex[KDF_HEX_LEN]; struct session *session = NULL; struct blob *blob; int ret; _cleanup_free_ char *password = NULL; _cleanup_free_ char *new_password = NULL; _cleanup_free_ char *pw2 = NULL; _cleanup_free_ char *username = NULL; int iterations; bool match; struct pwchange_info info; /* load existing session, if present */ init_all(BLOB_SYNC_YES, key, &session, &blob); username = config_read_string("username"); iterations = lastpass_iterations(username); if (!iterations) die("Unable to fetch iteration count. Check your internet connection and be sure your username is valid."); /* reprompt for old mpw */ password = password_prompt("Current Master Password", NULL, "Please enter the current LastPass master password for <%s>.", username); if (!password) die("Failed to enter password."); kdf_login_key(username, password, iterations, hex); secure_clear_str(password); /* prompt for new pw */ new_password = password_prompt("New Master Password", NULL, "Please enter the new LastPass master password for <%s>.", username); pw2 = password_prompt("Confirm New Master Password", NULL, "Please retype the new LastPass master password for <%s>.", username); if (!new_password || !pw2) die("Failed to enter new password."); match = strcmp(new_password, pw2) == 0; secure_clear_str(pw2); if (!match) die("Bad password: passwords don't match."); if (strlen(new_password) < 8) die("Bad password: too short."); kdf_decryption_key(username, new_password, iterations, new_key); kdf_login_key(username, new_password, iterations, new_hex); secure_clear_str(new_password); /* * Fetch the data to reencrypt. We may learn at this point that the * current password was incorrect, so handle that accordingly. */ terminal_printf(TERMINAL_FG_CYAN "Fetching data...\n" TERMINAL_RESET); ret = lastpass_pwchange_start(session, username, hex, &info); if (ret) { if (ret == -EPERM) die("Incorrect password. Password not changed."); else die("Error changing password (error=%d)", ret); } /* reencrypt */ reencrypt(session, &info, key, new_key); terminal_printf(TERMINAL_FG_CYAN "Uploading...\n" TERMINAL_RESET); _cleanup_free_ char *enc_username = encrypt_and_base64(username, new_key); ret = lastpass_pwchange_complete(session, username, enc_username, new_hex, iterations, &info); if (ret) die("Password change failed."); session_kill(); terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "Success" TERMINAL_RESET ": Password changed and logged out.\n"); return 0; } lastpass-cli-1.0.0/cmd-export.c0000644000175000017500000001056012743671271016575 0ustar troyhebetroyhebe/* * command for exporting vault entries into CSV format * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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(const char *cell, bool is_last) { const 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,extra,name,grouping,fav\r\n"); list_for_each_entry(account, &blob->account_head, list) { _cleanup_free_ char *share_group = NULL; char *groupname = account->group; /* skip groups */ if (!strcmp(account->url, "http://group")) continue; if (account->share) { xasprintf(&share_group, "%s\\%s", account->share->name, account->group); /* trim trailing backslash if no subfolder */ if (!strlen(account->group)) share_group[strlen(share_group)-1] = '\0'; groupname = share_group; } lastpass_log_access(sync, session, key, account); print_csv_cell(account->url, false); print_csv_cell(account->username, false); print_csv_cell(account->password, false); print_csv_cell(account->note, false); print_csv_cell(account->name, false); print_csv_cell(groupname, false); print_csv_cell(bool_str(account->fav), true); } session_free(session); blob_free(blob); return 0; } lastpass-cli-1.0.0/process.c0000644000175000017500000001056412743671271016175 0ustar troyhebetroyhebe/* * lpass process settings * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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)) unsigned long y) {} #endif #ifndef USE_PTRACE #undef PT_DENY_ATTACH #define PT_DENY_ATTACH 0 static void ptrace(__attribute__((unused)) int x, __attribute__((unused)) int y, __attribute__((unused)) int z, __attribute__((unused)) int w) {} #endif #if defined(__linux__) || defined(__CYGWIN__) || defined(__NetBSD__) #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, (unsigned long) name); if (!ARGC || !ARGV) return; for (int i = 0; i < ARGC; ++i) { argslen += strlen(ARGV[i]) + 1; for (char *p = ARGV[i]; *p; ++p) *p = '\0'; } strlcpy(ARGV[0], name, argslen); } bool process_is_same_executable(pid_t pid) { char resolved_them[PATH_MAX + 1] = { 0 }, resolved_me[PATH_MAX + 1] = { 0 }; if (pid_to_cmd(pid, resolved_them, sizeof(resolved_them)) < 0 || pid_to_cmd(getpid(), resolved_me, sizeof(resolved_me)) < 0) return false; return strcmp(resolved_them, resolved_me) == 0; } void process_disable_ptrace(void) { prctl(PR_SET_DUMPABLE, 0); ptrace(PT_DENY_ATTACH, 0, 0, 0); struct rlimit limit = { 0, 0 }; setrlimit(RLIMIT_CORE, &limit); } lastpass-cli-1.0.0/CHANGELOG.md0000644000175000017500000001373612743671271016170 0ustar troyhebetroyhebe# Version 1.0.0 * New command ```lpass status``` shows whether or not the user is logged in with agent, from Nick Knudson * ```lpass add``` can now be passed ```--note-type=X``` in order to add a secure note using a template. Specifying an unknown note template will list the available templates. * ```lpass ls``` now shows username with ```--long```, from Alli Witheford * Bash completions are now installed with make install, from Eli Schwartz * Fish shell completions supplied by Joar Wanboarg * Initial support for adding (```lpass add --app```) and editing applications * Updates to manpage for ```ls```, ```passwd```, ```add```, and basic usage examples * lpass now follows XDG base directory specifications for its files on platforms that use it. Set ```LP_HOME``` to ~/.lpass to keep the previous location * Bugfix: resolved syncing problems on some platforms (notably RHEL/CentOS) related to improper multiprocess usage of libcurl (github #166) * Bugfix: ```lpass show``` no longer crashes when a searched-for field is not found (github #167) * Bugfix: ```lpass``` no longer exits with an error if the blob is empty but otherwise without parsing errors. This fixes the case where a new user could not use the application without first adding a site elsewhere. * ```LPASS_LOG_LEVEL``` learned level=8 with which lpass will also dump libcurl verbose logs showing all traffic for debugging (not recommended for general use due to potentially sensitive headers being logged). # Version 0.9.0 * Add support for accounts in the EU datacenter (lastpass.eu) * ```lpass ls``` now sorts its output and properly displays group folder account entries * ```lpass export``` output has been reworked to match that of the website, from Justen Walker * ```lpass share limit``` subcommand was added which allows displaying and modifying user-specific restrictions for shared folders * The new ```LPASS_LOG_LEVEL``` environment variable can be set to cause the lpass uploader process to log its actions, useful for debugging syncing issues. Set it to 7 to get all debug logs; the logfile will be ~/.lpass/lpass.log. * Bugfix: syncing is fixed on systems that use XFS or other filesystems which do not support setting d_type in readdir() * Bugfix: ```lpass mv``` now works properly with linked accounts # Version 0.8.1, 0.7.2, 0.6.1, 0.5.1 * This update to all recent versions switches to the platform certificate store and adds pinning of LastPass public keys, in preparation for certificate changes at lastpass.com. Upgrade will be needed to avoid "Peer certificate cannot be authenticated with given CA certificates" errors when the cert changes are made. # Version 0.8.0 * New command ```lpass add``` works like ```lpass edit``` for new accounts * New command ```lpass mv``` can be used to move an account into a different (possibly shared) folder * New command ```lpass passwd``` can be used to change master password * Tab-completion for bash is now available; to use, source ```contrib/lpass_bash_completion``` from a bash startup file * ```lpass ls``` now interprets backslash properly for subfolder display * ```lpass edit``` gained the ability to edit all fields of an account at once by using a specially-formatted edit buffer * ```lpass show``` gained the ability to show multiple accounts at once, from Angus Galloway * ```lpass show``` now reformats SSH private key fields in secure notes into a usable form * ```lpass share useradd``` gained the ability to specify group names * ```lpass share``` got better documentation * Bugfix: logins with certain multifactors that support out-of-band authentication will now work correctly * Blob edits no longer reencrypt the entire database, just the changed accounts * Syncing operation is now much more robust in the face of server errors or invalid transactions. * OSX builds fixed for Xcode-less installations, with help from Wael Nasreddine * Corrections to FSF address from Tom Prince # Version 0.7.1 * This bugfix release fixes a build issue on OSX platforms without XCode. It is otherwise identical to 0.7.0. # Version 0.7.0 * ```lpass``` now supports aliases in order to set preferred switches or nicknames for commands. ```echo 'show -G' > ~/.lpass/alias.show```, for example, will turn regex matching on for ```lpass show```. * In addition to pinentry and in-process prompting, the ```LPASS_ASKPASS``` environment variable/config value is now checked for a binary to ask for passwords. It uses the same conventions as ssh-askpass. * ```lpass show``` will now match account id when using regex or substring matching * ```lpass ls``` learned the ```-l [-u]```switches to show mod and use times, from Lloyd Zusman * Secure notes are now created by default when empty sites are edited with --notes, from Lloyd Zusman * The new ```LPASS_CLIPBOARD_COMMAND``` environment variable/config value can be used to configure the helper application for the system clipboard, from Tom Prince. Among other things, you can use this to clear the clipboard after a certain number of pastes with ```xclip -l```. * Various code cleanups and documentation fixes from Tom Prince. * The license has been clarified to GPLv2 or later, plus the OpenSSL exception; please see individual files and the LICENSE.OpenSSL / COPYING files for details. This was the intended license all along but it was not spelled out consistently. # Version 0.6.0 * New share sub-command allows automating some common tasks with shared folders * PBKDF2 speedups from Thomas Hurst * Ungrouped entries now fall under "(none)" heading, from Gordon Celesta * Documentation updates from Eli Young * Cleanups from Björn Ketelaars # Version 0.5.1 * Update Thawte CA cert to support lastpass.com's new SHA-256 cert. # Version 0.5.0 * OpenBSD support * Updated build/install instructions for Cygwin, Debian, and RPM-based distributions * Regex and substring searching for cmd-show * Secure note parsing and field display * Fixes for pinentry errors and hangs lastpass-cli-1.0.0/cmd-generate.c0000644000175000017500000001146412743671271017052 0ustar troyhebetroyhebe/* * command for generating passwords * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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_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(""); account_assign_share(blob, new, key); 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-1.0.0/http.c0000644000175000017500000002225012743671271015471 0ustar troyhebetroyhebe/* * http posting routines * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "http.h" #include "log.h" #include "util.h" #include "version.h" #include "pins.h" #include "cipher.h" #include #include #include #include #include #include #include struct mem_chunk { char *ptr; size_t len; }; static bool interrupted = false; static sig_t previous_handler = SIG_DFL; static void interruption_detected(int signal) { UNUSED(signal); interrupted = true; } static void set_interrupt_detect(void) { interrupted = false; previous_handler = signal(SIGINT, interruption_detected); } static void unset_interrupt_detect(void) { interrupted = false; signal(SIGINT, previous_handler); } static int check_interruption(void *p, double dltotal, double dlnow, double ultotal, double ulnow) { UNUSED(p); UNUSED(dltotal); UNUSED(dlnow); UNUSED(ultotal); UNUSED(ulnow); return interrupted; } static size_t write_data(char *ptr, size_t size, size_t nmemb, void *data) { size_t len, new_len; struct mem_chunk *mem = (struct mem_chunk *)data; if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return 0; } len = size * nmemb; new_len = len + mem->len + 1; if (new_len <= mem->len || new_len <= len || new_len < 1) { errno = ENOMEM; return 0; } mem->ptr = xrealloc(mem->ptr, new_len); memcpy(mem->ptr + mem->len, ptr, len); mem->len += len; mem->ptr[mem->len] = '\0'; return len; } static char *hash_subject_pubkey_info(X509 *cert) { _cleanup_free_ unsigned char *spki = NULL; char *hash = NULL; EVP_PKEY *pkey; int len; pkey = X509_get_pubkey(cert); if (!pkey) return NULL; len = i2d_PUBKEY(pkey, &spki); if (len <= 0) goto free_pkey; hash = cipher_sha256_b64(spki, len); free_pkey: EVP_PKEY_free(pkey); return hash; } static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { int i, j; /* * Preverify checks the platform's certificate store; don't * allow any chain that doesn't already validate according to * that. */ if (!preverify_ok) return 0; /* check each certificate in the chain against our built-in pinlist. */ STACK_OF(X509) *chain = X509_STORE_CTX_get_chain(ctx); if (!chain) die("No certificate chain available"); bool found = false; for (i=0; i < sk_X509_num(chain); i++) { _cleanup_free_ char *spki_hash = NULL; spki_hash = hash_subject_pubkey_info(sk_X509_value(chain, i)); if (!spki_hash) continue; for (j=0; j < (int) ARRAY_SIZE(PK_PINS); j++) { if (strcmp(PK_PINS[j], spki_hash) == 0) { found = true; break; } } } return found; } static CURLcode pin_keys(CURL *curl, void *sslctx, void *parm) { UNUSED(curl); UNUSED(parm); SSL_CTX_set_verify((SSL_CTX *)sslctx, SSL_VERIFY_PEER, verify_callback); return CURLE_OK; } static void vhttp_post_add_params(struct http_param_set *param_set, va_list args) { char **argv_ptr; char *arg; size_t count = 0; if (!param_set->argv) { param_set->n_alloced = 2; param_set->argv = xcalloc(param_set->n_alloced, sizeof(char *)); } argv_ptr = param_set->argv; while (*argv_ptr) { argv_ptr++; count++; } while ((arg = va_arg(args, char *))) { if (count == param_set->n_alloced - 1) { param_set->n_alloced += 2; param_set->argv = xreallocarray(param_set->argv, param_set->n_alloced, sizeof(char *)); argv_ptr = ¶m_set->argv[count]; } *argv_ptr++ = arg; count++; } *argv_ptr = 0; } int http_init() { curl_global_cleanup(); return curl_global_init(CURL_GLOBAL_DEFAULT); } void http_post_add_params(struct http_param_set *param_set, ...) { va_list args; va_start(args, param_set); vhttp_post_add_params(param_set, args); va_end(args); } char *http_post_lastpass(const char *page, const struct session *session, size_t *final_len, ...) { va_list args; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; va_start(args, final_len); vhttp_post_add_params(¶ms, args); char *result = http_post_lastpass_param_set(page, session, final_len, ¶ms); free(params.argv); return result; } char *http_post_lastpass_v_noexit(const char *server, const char *page, const struct session *session, size_t *final_len, char **argv, int *curl_ret, long *http_code) { _cleanup_free_ char *url = NULL; _cleanup_free_ char *postdata = NULL; _cleanup_free_ char *cookie = NULL; _cleanup_fclose_ FILE *logstream = NULL; char *param, *encoded_param; CURL *curl = NULL; char separator; size_t len, new_len; int ret; struct mem_chunk result; const char *login_server; /* if we have a session, use that server, otherwise use whatever was passed */ login_server = session ? session->server : server; /* if nothing passed, use lastpass */ if (!login_server) login_server = LASTPASS_SERVER; xasprintf(&url, "https://%s/%s", login_server, page); lpass_log(LOG_DEBUG, "Making request to %s\n", url); curl = curl_easy_init(); if (!curl) die("Could not init curl"); len = 0; for (separator = '=', param = *argv; param; separator = (separator == '=') ? '&' : '=', param = *(++argv)) { encoded_param = curl_easy_escape(curl, param, 0); if (!encoded_param) die("Could not escape %s with curl", param); new_len = strlen(encoded_param) + 1 /* separator */; postdata = xrealloc(postdata, len + new_len + 1 /* null */); snprintf(postdata + len, new_len + 1, "%s%c", encoded_param, separator); len += new_len; curl_free(encoded_param); } if (len && postdata) postdata[len - 1] = '\0'; memset(&result, 0, sizeof(result)); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_USERAGENT, LASTPASS_CLI_USERAGENT); if (lpass_log_level() >= LOG_VERBOSE) { logstream = lpass_log_open(); if (logstream) { curl_easy_setopt(curl, CURLOPT_STDERR, logstream); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); } } #if defined(DO_NOT_ENABLE_ME_MITM_PROXY_FOR_DEBUGGING_ONLY) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_PROXY, "http://localhost:8080"); #else curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, pin_keys); #endif curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, check_interruption); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); if (postdata) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata); if (session) { xasprintf(&cookie, "PHPSESSID=%s", session->sessionid); curl_easy_setopt(curl, CURLOPT_COOKIE, cookie); } set_interrupt_detect(); ret = curl_easy_perform(curl); unset_interrupt_detect(); curl_easy_cleanup(curl); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code); *curl_ret = ret; if (ret != CURLE_OK) { result.len = 0; free(result.ptr); result.ptr = NULL; } else if (!result.ptr) result.ptr = xstrdup(""); if (final_len) *final_len = result.len; return result.ptr; } char *http_post_lastpass_v(const char *server, const char *page, const struct session *session, size_t *final_len, char **argv) { char *result; int ret; long http_code; result = http_post_lastpass_v_noexit(server, page, session, final_len, argv, &ret, &http_code); if (ret != CURLE_OK && ret != CURLE_ABORTED_BY_CALLBACK) die("%s.", curl_easy_strerror(ret)); return result; } char *http_post_lastpass_param_set(const char *page, const struct session *session, size_t *final_len, struct http_param_set *param_set) { return http_post_lastpass_v(NULL, page, session, final_len, param_set->argv); } lastpass-cli-1.0.0/cmd-ls.c0000644000175000017500000002302012743671271015665 0ustar troyhebetroyhebe/* * command for listing the vault * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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 list_head children; struct list_head list; }; 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); } struct path_component { char *component; struct list_head list; }; /* * Tokenize path and add each component to the components list. * For group names, the path separator is a backslash. The path * string is modified in place and the component list stores * pointers to the modified string. */ static void parse_path(char *path, struct list_head *components) { char *token; struct path_component *pc; for (token = strtok(path, "\\"); token; token = strtok(NULL, "\\")) { pc = new0(struct path_component, 1); pc->component = token; list_add_tail(&pc->list, components); } } static void __insert_node(struct node *head, struct list_head *components, struct account *account) { struct path_component *pc; struct node *child, *tmp; /* iteratively build a tree from all the path components */ list_for_each_entry(pc, components, list) { child = NULL; list_for_each_entry(tmp, &head->children, list) { if (!strcmp(tmp->name, pc->component)) { child = tmp; break; } } if (!child) { child = new0(struct node, 1); child->shared= !!account->share; child->name = xstrdup(pc->component); INIT_LIST_HEAD(&child->children); list_add_tail(&child->list, &head->children); } head = child; } /* skip group display -- we already added the hierarchy for them */ if (account_is_group(account)) return; /* and add the site at the lowest level */ child = new0(struct node, 1); child->account = account; child->shared= !!account->share; child->name = xstrdup(account->name); INIT_LIST_HEAD(&child->children); list_add_tail(&child->list, &head->children); } static void insert_node(struct node *head, const char *path, struct account *account) { struct list_head components; struct path_component *pc, *tmp; _cleanup_free_ char *dirname = xstrdup(path); char *pos; /* remove name portion of fullname; we don't parse that */ if (strlen(dirname) >= strlen(account->name)) { char *tmp = dirname + strlen(dirname) - strlen(account->name); if (strcmp(tmp, account->name) == 0) { *tmp = 0; } } pos = dirname; /* trim trailing slash */ if (strlen(pos)) pos[strlen(pos)-1] = 0; /* * We are left with one of: * * (none)/ * groupname/ * Shared-folder/ * Shared-folder/groupname/ * * If there are embedded backslashes, these are treated as folder * names by parse_path(). */ INIT_LIST_HEAD(&components); if (account->share && strlen(pos) >= strlen(account->share->name)) { pos[strlen(account->share->name)] = 0; parse_path(pos, &components); pos += strlen(account->share->name) + 1; } /* either '(none)/' or group/ or empty string */ parse_path(pos, &components); __insert_node(head, &components, account); list_for_each_entry_safe(pc, tmp, &components, list) { list_del(&pc->list); free(pc); } } static void free_node(struct node *head) { struct node *node, *tmp; if (!head) return; list_for_each_entry_safe(node, tmp, &head->children, list) { free_node(node); } free(head->name); free(head); } static void print_node(struct node *head, int level) { struct node *node; list_for_each_entry(node, &head->children, list) { if (node->name) { for (int i = 0; i < level; ++i) printf(" "); if (node->account) { 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]", node->name, node->account->id); if (long_listing) { terminal_printf(TERMINAL_FG_GREEN " [username: %s]", node->account->username); } terminal_printf(TERMINAL_RESET "\n"); } 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, 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; } static int compare_account(const void *a, const void *b) { struct account * const *acct_a = a; struct account * const *acct_b = b; return strcmp((*acct_a)->fullname, (*acct_b)->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; _cleanup_free_ struct account **account_array = NULL; int i, num_accounts; 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); INIT_LIST_HEAD(&root->children); /* '(none)' group -> search for any without group */ if (group && !strcmp(group, "(none)")) group = ""; num_accounts = 0; list_for_each_entry(account, &blob->account_head, list) { num_accounts++; } i=0; account_array = xcalloc(num_accounts, sizeof(struct account *)); list_for_each_entry(account, &blob->account_head, list) { account_array[i++] = account; } qsort(account_array, num_accounts, sizeof(struct account *), compare_account); for (i=0; i < num_accounts; i++) { struct account *account = account_array[i]; if (group) { sub = strstr(account->fullname, group); if (!sub || sub != account->fullname) continue; group_len = strlen(group); sub += group_len; if (group[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]", fullname, account->id); if (long_listing) { printf(" [username: %s]", account->username); } printf("\n"); } free(fullname); } if (print_tree) print_node(root, 0); free_node(root); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.0.0/cmd-mv.c0000644000175000017500000000724112743671271015700 0ustar troyhebetroyhebe/* * command for moving vault entries * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "endpoints.h" #include "agent.h" #include #include #include #include #include int cmd_mv(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; enum blobsync sync = BLOB_SYNC_AUTO; int option; int option_index; char *name; char *folder; char *new_fullname = NULL; struct account *account; struct share *old_share; while ((option = getopt_long(argc, argv, "SC", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_mv_usage); } } if (argc - optind != 2) die_usage(cmd_mv_usage); name = argv[optind++]; folder = argv[optind++]; init_all(sync, key, &session, &blob); account = find_unique_account(blob, name); if (!account) { die("Unable to find account %s", name); } xasprintf(&new_fullname, "%s/%s", folder, account->name); old_share = account->share; account_set_fullname(account, new_fullname, key); account_assign_share(blob, account, key); if (account->share && account->share->readonly) { die("You do not have access to move %s into %s", account->name, account->share->name); } if (old_share != account->share) { /* * when moving into / out of a shared folder, we need to * reencrypt and make a special api call for that. */ int ret = lastpass_share_move(session, account, old_share); if (ret) { die("Move to/from shared folder failed (%d)\n", ret); } list_del(&account->list); } else { /* standard case: account just changing group name */ lastpass_update_account(sync, key, session, account, blob); } blob_save(blob, key); session_free(session); blob_free(blob); return 0; } lastpass-cli-1.0.0/README.md0000644000175000017500000001014512743671271015625 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). #### Fedora * Packages are available in Fedora 22 and later. ``` sudo dnf install lastpass-cli ``` #### Redhat/Centos * Packages are available in [EPEL](https://fedoraproject.org/wiki/EPEL) for RHEL/CentOS 7 and later. ``` sudo yum install lastpass-cli ``` * For older versions: Install the needed build dependencies, and then follow instructions in the 'Building' section. ``` sudo yum install openssl libcurl libxml2 pinentry xclip openssl-devel libxml2-devel libcurl-devel ``` #### 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-1.0.0/kdf.c0000644000175000017500000001035512743671271015261 0ustar troyhebetroyhebe/* * key derivation routines * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "kdf.h" #include "util.h" #include #include #include #include #include #if defined(__APPLE__) && defined(__MACH__) #include #include static void pbkdf2_hash(const char *username, size_t username_len, const char *password, size_t password_len, int iterations, unsigned char hash[KDF_HASH_LEN]) { if (CCKeyDerivationPBKDF(kCCPBKDF2, password, password_len, (const uint8_t *)username, username_len, kCCPRFHmacAlgSHA256, iterations, hash, KDF_HASH_LEN) == kCCParamError) die("Failed to compute PBKDF2 for %s", username); } #else #include "pbkdf2.h" static void pbkdf2_hash(const char *username, size_t username_len, const char *password, size_t password_len, int iterations, unsigned char hash[KDF_HASH_LEN]) { if (!PKCS5_PBKDF2_HMAC(password, password_len, (const unsigned char *)username, username_len, iterations, EVP_sha256(), KDF_HASH_LEN, hash)) die("Failed to compute PBKDF2 for %s", username); } #endif static void sha256_hash(const char *username, size_t username_len, const char *password, size_t password_len, unsigned char hash[KDF_HASH_LEN]) { SHA256_CTX sha256; if (!SHA256_Init(&sha256)) goto die; if (!SHA256_Update(&sha256, username, username_len)) goto die; if (!SHA256_Update(&sha256, password, password_len)) goto die; if (!SHA256_Final(hash, &sha256)) goto die; return; die: die("Failed to compute SHA256 for %s", username); } void kdf_login_key(const char *username, const char *password, int iterations, char hex[KDF_HEX_LEN]) { unsigned char hash[KDF_HASH_LEN]; size_t password_len; _cleanup_free_ char *user_lower = xstrlower(username); password_len = strlen(password); if (iterations < 1) iterations = 1; if (iterations == 1) { sha256_hash(user_lower, strlen(user_lower), password, password_len, hash); bytes_to_hex(hash, &hex, KDF_HASH_LEN); sha256_hash(hex, KDF_HEX_LEN - 1, password, password_len, hash); } else { pbkdf2_hash(user_lower, strlen(user_lower), password, password_len, iterations, hash); pbkdf2_hash(password, password_len, (char *)hash, KDF_HASH_LEN, 1, hash); } bytes_to_hex(hash, &hex, KDF_HASH_LEN); mlock(hex, KDF_HEX_LEN); } void kdf_decryption_key(const char *username, const char *password, int iterations, unsigned char hash[KDF_HASH_LEN]) { _cleanup_free_ char *user_lower = xstrlower(username); if (iterations < 1) iterations = 1; if (iterations == 1) sha256_hash(user_lower, strlen(user_lower), password, strlen(password), hash); else pbkdf2_hash(user_lower, strlen(user_lower), password, strlen(password), iterations, hash); mlock(hash, KDF_HASH_LEN); } lastpass-cli-1.0.0/COPYING0000644000175000017500000004325412743671271015410 0ustar troyhebetroyhebe GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. lastpass-cli-1.0.0/endpoints-login.c0000644000175000017500000002534712743671271017635 0ustar troyhebetroyhebe/* * https endpoints for logging into LastPass * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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 char *login_server, const unsigned char key[KDF_HASH_LEN], char **args, char **cause, char **message, char **reply, struct session **session, char **ret_login_server) { char *server; free(*reply); *reply = http_post_lastpass_v(login_server, "login.php", NULL, NULL, args); if (!*reply) return error_post(message, session); *session = xml_ok_session(*reply, key); if (*session) { (*session)->server = xstrdup(login_server); return true; } /* handle server redirection if requested for lastpass.eu */ server = xml_error_cause(*reply, "server"); if (server && strcmp(server, "lastpass.eu") == 0) return ordinary_login(server, key, args, cause, message, reply, session, ret_login_server); *cause = xml_error_cause(*reply, "cause"); if (!*cause) return error_other(message, session, "Unable to determine login failure cause."); *ret_login_server = xstrdup(login_server); return false; } static inline bool has_capabilities(const char *capabilities, const char *capability) { _cleanup_free_ char *caps = xstrdup(capabilities); char *token; for (token = strtok(caps, ","); token; token = strtok(NULL, ",")) { if (!strcmp(capability, token)) return true; } return false; } static bool oob_login(const char *login_server, const unsigned char key[KDF_HASH_LEN], char **args, char **message, char **reply, char **oob_name, struct session **session) { _cleanup_free_ char *oob_capabilities = NULL; _cleanup_free_ char *cause = NULL; _cleanup_free_ char *retryid = NULL; bool can_do_passcode; bool ret; *oob_name = xml_error_cause(*reply, "outofbandname"); oob_capabilities = xml_error_cause(*reply, "capabilities"); if (!*oob_name || !oob_capabilities) return error_other(message, session, "Could not determine out-of-band type."); can_do_passcode = has_capabilities(oob_capabilities, "passcode"); if (can_do_passcode && !has_capabilities(oob_capabilities, "outofband")) { xstrappend(oob_name, " OTP"); goto failure; } terminal_fprintf(stderr, TERMINAL_FG_YELLOW TERMINAL_BOLD "Waiting for approval of out-of-band %s login%s" TERMINAL_NO_BOLD "...", *oob_name, can_do_passcode ? ", or press Ctrl+C to enter a passcode" : ""); append_post(args, "outofbandrequest", "1"); for (;;) { free(*reply); *reply = http_post_lastpass_v(login_server, "login.php", NULL, NULL, args); if (!*reply) { if (can_do_passcode) { append_post(args, "outofbandrequest", "0"); append_post(args, "outofbandretry", "0"); append_post(args, "outofbandretryid", ""); xstrappend(oob_name, " OTP"); goto failure; } else { error_post(message, session); goto success; } } *session = xml_ok_session(*reply, key); if (*session) { (*session)->server = xstrdup(login_server); goto success; } free(cause); cause = xml_error_cause(*reply, "cause"); if (cause && !strcmp(cause, "outofbandrequired")) { free(retryid); retryid = xml_error_cause(*reply, "retryid"); append_post(args, "outofbandretry", "1"); append_post(args, "outofbandretryid", retryid); fprintf(stderr, "."); continue; } error_message(message, session, *reply); goto success; } success: ret = true; goto out; failure: ret = false; goto out; out: terminal_fprintf(stderr, TERMINAL_RESET "\n" TERMINAL_UP_CURSOR(1) TERMINAL_CLEAR_DOWN); return ret; } static bool otp_login(const char *login_server, const unsigned char key[KDF_HASH_LEN], char **args, char **message, char **reply, const char *otp_name, const char *cause, const char *username, struct session **session) { struct multifactor_type *replied_multifactor = NULL; _cleanup_free_ char *multifactor = NULL; _cleanup_free_ char *next_cause = NULL; char *multifactor_error = NULL; for (size_t i = 0; i < ARRAY_SIZE(multifactor_types); ++i) { if (!strcmp(multifactor_types[i].error_str, cause)) { replied_multifactor = &multifactor_types[i]; break; } } if (!replied_multifactor) return error_message(message, session, *reply); for (;;) { free(multifactor); multifactor = password_prompt("Code", multifactor_error, "Please enter your %s for <%s>.", otp_name ? otp_name : replied_multifactor->name, username); if (!multifactor) return error_other(message, session, "Aborted multifactor authentication."); append_post(args, replied_multifactor->post_var, multifactor); free(*reply); *reply = http_post_lastpass_v(login_server, "login.php", NULL, NULL, args); if (!*reply) return error_post(message, session); *session = xml_ok_session(*reply, key); if (*session) { (*session)->server = xstrdup(login_server); return true; } free(next_cause); next_cause = xml_error_cause(*reply, "cause"); if (next_cause && !strcmp(next_cause, replied_multifactor->error_failure_str)) multifactor_error = "Invalid multifactor code; please try again."; else return error_message(message, session, *reply); } } struct session *lastpass_login(const char *username, const char hash[KDF_HEX_LEN], const unsigned char key[KDF_HASH_LEN], int iterations, char **error_message, bool trust) { char *args[33]; _cleanup_free_ char *user_lower = NULL; _cleanup_free_ char *iters = NULL; _cleanup_free_ char *trusted_id = NULL; _cleanup_free_ char *trusted_label = NULL; _cleanup_free_ char *cause = NULL; _cleanup_free_ char *reply = NULL; _cleanup_free_ char *otp_name = NULL; _cleanup_free_ char *login_server = NULL; struct session *session = NULL; iters = xultostr(iterations); user_lower = xstrlower(username); trusted_id = calculate_trust_id(trust); memset(args, 0, sizeof(args)); append_post(args, "xml", "2"); append_post(args, "username", user_lower); append_post(args, "hash", hash); append_post(args, "iterations", iters); append_post(args, "includeprivatekeyenc", "1"); append_post(args, "method", "cli"); append_post(args, "outofbandsupported", "1"); if (trusted_id) append_post(args, "uuid", trusted_id); if (ordinary_login(LASTPASS_SERVER, key, args, &cause, error_message, &reply, &session, &login_server)) return session; if (trust) { trusted_label = calculate_trust_label(); append_post(args, "trustlabel", trusted_label); } if (!strcmp(cause, "outofbandrequired") && oob_login(login_server, key, args, error_message, &reply, &otp_name, &session)) { if (trust) http_post_lastpass("trust.php", session, NULL, "uuid", trusted_id, "trustlabel", trusted_label, NULL); return session; } if (otp_login(login_server, key, args, error_message, &reply, otp_name, cause, user_lower, &session)) return session; error_other(error_message, &session, "An unspecified error occured."); return NULL; } lastpass-cli-1.0.0/cmd-logout.c0000644000175000017500000000555412743671271016574 0ustar troyhebetroyhebe/* * command for logging out of LastPass * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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); session_kill(); lastpass_logout(session); terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "Log out" TERMINAL_RESET ": complete.\n"); return 0; } lastpass-cli-1.0.0/cmd-duplicate.c0000644000175000017500000000732712743671271017235 0ustar troyhebetroyhebe/* * command for making copies of vault entries * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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-1.0.0/password.h0000644000175000017500000000033112743671271016355 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-1.0.0/cmd-add.c0000644000175000017500000000770212743671271016010 0ustar troyhebetroyhebe/* * command for adding vault entries * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "endpoints.h" #include "agent.h" #include #include #include #include #include int cmd_add(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"username", no_argument, NULL, 'u'}, {"password", no_argument, NULL, 'p'}, {"url", no_argument, NULL, 'L'}, {"field", required_argument, NULL, 'F'}, {"notes", no_argument, NULL, 'O'}, {"app", no_argument, NULL, 'a'}, {"non-interactive", no_argument, NULL, 'X'}, {"note-type", required_argument, NULL, 'T'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; _cleanup_free_ char *field = NULL; char *name; bool non_interactive = false; enum blobsync sync = BLOB_SYNC_AUTO; enum edit_choice choice = EDIT_ANY; enum note_type note_type = NOTE_TYPE_NONE; bool is_app = false; #define ensure_choice() if (choice != EDIT_ANY) goto choice_die; while ((option = getopt_long(argc, argv, "up", long_options, &option_index)) != -1) { switch (option) { case 'S': sync = parse_sync_string(optarg); break; case 'u': ensure_choice(); choice = EDIT_USERNAME; break; case 'p': ensure_choice(); choice = EDIT_PASSWORD; break; case 'L': ensure_choice(); choice = EDIT_URL; break; case 'F': ensure_choice(); choice = EDIT_FIELD; field = xstrdup(optarg); break; case 'O': ensure_choice(); choice = EDIT_NOTES; break; case 'X': non_interactive = true; break; case 'a': is_app = true; break; case 'T': note_type = parse_note_type_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_add_usage); } } #undef ensure_choice if (argc - optind != 1) die_usage(cmd_add_usage); if (choice == EDIT_NONE) choice_die: die_usage("add ... {--username|--password|--url|--notes|--field=FIELD|--note-type=NOTE_TYPE}"); name = argv[optind]; init_all(sync, key, &session, &blob); return edit_new_account(session, blob, sync, name, choice, field, non_interactive, is_app, note_type, key); } lastpass-cli-1.0.0/endpoints-share.c0000644000175000017500000002676312743671271017632 0ustar troyhebetroyhebe/* * https endpoints for shared folder manipulation * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "endpoints.h" #include "http.h" #include "version.h" #include "xml.h" #include "config.h" #include "util.h" #include "upload-queue.h" #include #include #include int lastpass_share_getinfo(const struct session *session, const char *shareid, struct list_head *users) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "sharejs", "1", "getinfo", "1", "id", shareid, "xmlr", "1", NULL); if (!reply) return -EPERM; xml_parse_share_getinfo(reply, users); return 0; } static int lastpass_share_get_user_by_uid(const struct session *session, const char *uid, struct share_user *user) { _cleanup_free_ char *reply = NULL; size_t len; /* get the pubkey for the user/group */ reply = http_post_lastpass("share.php", session, &len, "token", session->token, "getpubkey", "1", "uid", uid, "xmlr", "1", NULL); return xml_parse_share_getpubkey(reply, user); } static int lastpass_share_get_users_by_username(const struct session *session, const char *username, struct list_head *users) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *uid_param; size_t len; xasprintf(&uid_param, "{\"%s\":{}}", username); /* get the pubkey for the user/group */ reply = http_post_lastpass("share.php", session, &len, "token", session->token, "getpubkey", "1", "uid", uid_param, "xmlr", "1", NULL); return xml_parse_share_getpubkeys(reply, users); } int lastpass_share_user_add(const struct session *session, struct share *share, struct share_user *user) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *enc_share_name = NULL; _cleanup_free_ char *hex_share_key = NULL; _cleanup_free_ unsigned char *enc_share_key = NULL; _cleanup_free_ char *hex_enc_share_key = NULL; int ret; size_t len; struct list_head user_list; struct share_user *share_user, *tmp; INIT_LIST_HEAD(&user_list); ret = lastpass_share_get_users_by_username(session, user->username, &user_list); if (ret) die("Unable to lookup user %s (%d)\n", user->username, ret); list_for_each_entry_safe(share_user, tmp, &user_list, list) { /* encrypt sharename with sharekey */ enc_share_name = encrypt_and_base64(share->name, share->key); /* encrypt sharekey with user's pubkey */ bytes_to_hex(share->key, &hex_share_key, sizeof(share->key)); size_t enc_share_key_len = share_user->sharing_key.len; enc_share_key = xmalloc(enc_share_key_len); ret = cipher_rsa_encrypt(hex_share_key, &share_user->sharing_key, enc_share_key, &enc_share_key_len); if (ret) die("Unable to encrypt sharing key with pubkey (%d)\n", ret); bytes_to_hex(enc_share_key, &hex_enc_share_key, enc_share_key_len); reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "update", "1", "add", "1", "notify", "1", "uid0", share_user->uid, "cgid0", share_user->cgid ? share_user->cgid : "", "sharekey0", hex_enc_share_key, "sharename", enc_share_name, "name", share->name, "readonly", bool_str(user->read_only), "give", bool_str(!user->hide_passwords), "canadminister", bool_str(user->admin), "xmlr", "1", NULL); free(share_user); } if (!reply) return -EPERM; return 0; } int lastpass_share_user_mod(const struct session *session, struct share *share, struct share_user *user) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "up", "1", "edituser", "1", "uid", user->uid, "readonly", user->read_only ? "on" : "", "give", !user->hide_passwords ? "on" : "", "canadminister", user->admin ? "on" : "", "xmlr", "1", NULL); if (!reply) return -EPERM; return 0; } int lastpass_share_user_del(const struct session *session, const char *shareid, struct share_user *user) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", shareid, "update", "1", "delete", "1", "uid", user->uid, "xmlr", "1", NULL); return 0; } int lastpass_share_create(const struct session *session, const char *sharename) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *sf_username; _cleanup_free_ char *enc_share_name = NULL; _cleanup_free_ char *hex_share_key = NULL; _cleanup_free_ unsigned char *enc_share_key = NULL; _cleanup_free_ char *sf_fullname = NULL; _cleanup_free_ char *hex_enc_share_key = NULL; unsigned char pw[KDF_HASH_LEN * 2]; unsigned char key[KDF_HASH_LEN]; char hash[KDF_HEX_LEN]; struct share_user user; size_t len; unsigned int i; int ret; /* strip off "Shared-" part if included, we add it later */ if (!strncmp(sharename, "Shared-", 7)) sharename += 7; ret = lastpass_share_get_user_by_uid(session, session->uid, &user); if (ret) die("Unable to get pubkey for your user (%d)\n", ret); xasprintf(&sf_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, &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, &len, "token", session->token, "id", share->id, "delete", "1", "xmlr", "1", NULL); return 0; } /* * Move a site into or out of a shared folder. * * account should already be encrypted with the new share key. * orig_folder or account->share may be null, indicating the * transition to or from a regular site and a shared folder. */ int lastpass_share_move(const struct session *session, struct account *account, struct share *orig_folder) { _cleanup_free_ char *url = NULL; _cleanup_free_ char *reply = NULL; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; if (!account->share && !orig_folder) return 0; bytes_to_hex((unsigned char *) account->url, &url, strlen(account->url)); http_post_add_params(¶ms, "token", session->token, "cmd", "uploadaccounts", "aid0", account->id, "name0", account->name_encrypted, "grouping0", account->group_encrypted, "url0", url, "username0", account->username_encrypted, "password0", account->password_encrypted, "pwprotect0", account->pwprotect ? "on" : "off", "extra0", account->note_encrypted, "todelete", account->id, NULL); if (account->share) { http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); } if (orig_folder) { http_post_add_params(¶ms, "origsharedfolderid", orig_folder->id, NULL); } reply = http_post_lastpass_param_set("lastpass/api.php", session, NULL, ¶ms); free(params.argv); if (!reply) return -EINVAL; return xml_api_err(reply); } int lastpass_share_get_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *ret_limit) { _cleanup_free_ char *reply = NULL; size_t len; reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "limit", "1", "uid", user->uid, "xmlr", "1", NULL); xml_parse_share_get_limits(reply, ret_limit); return 0; } int lastpass_share_set_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *limit) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *aid_buf = NULL; char numaids_str[30] = {0}; struct share_limit_aid *aid; int numaids = 0; size_t alloc_len = 0; size_t len; list_for_each_entry(aid, &limit->aid_list, list) { alloc_len += strlen(aid->aid) + 1 /* comma or null */; numaids++; } aid_buf = xcalloc(alloc_len, 1); list_for_each_entry(aid, &limit->aid_list, list) { strlcat(aid_buf, aid->aid, alloc_len); strlcat(aid_buf, ",", alloc_len); } aid_buf[alloc_len-1] = '\0'; snprintf(numaids_str, sizeof(numaids_str), "%d", numaids); reply = http_post_lastpass("share.php", session, &len, "token", session->token, "id", share->id, "limit", "1", "edit", "1", "uid", user->uid, "numaids", numaids_str, "hidebydefault", bool_str(limit->whitelist), "aids", aid_buf, "xmlr", "1", NULL); return 0; } lastpass-cli-1.0.0/blob.h0000644000175000017500000001054112743671271015435 0ustar troyhebetroyhebe#ifndef BLOB_H #define BLOB_H #include "kdf.h" #include "session.h" #include "list.h" #include "notes.h" #include #include struct share_user { char *uid; char *username; char *realname; char *cgid; bool read_only; bool is_group; /* if set uid, username store gid, groupname */ bool hide_passwords; bool admin; bool outside_enterprise; bool accepted; struct public_key sharing_key; struct list_head list; }; struct share_limit_aid { char *aid; struct list_head list; }; struct share_limit { bool whitelist; struct list_head aid_list; }; struct share { char *id; char *name; unsigned char key[KDF_HASH_LEN]; bool readonly; char *chunk; size_t chunk_len; struct list_head list; }; struct field { char *type; char *name; char *value, *value_encrypted; bool checked; struct list_head list; }; struct account { char *id; char *name, *name_encrypted; char *group, *group_encrypted; char *fullname; char *url; char *username, *username_encrypted; char *password, *password_encrypted; char *note, *note_encrypted; char *last_touch, *last_modified_gmt; bool pwprotect; bool fav; bool is_app; struct list_head field_head; struct share *share; struct list_head list; struct list_head match_list; }; struct app { struct account account; char *appname; char *extra, *extra_encrypted; char *wintitle; char *wininfo; char *exeversion; char *warnversion; char *exehash; }; struct blob { unsigned long long version; bool local_version; /* TODO: extract other data eventually... */ struct list_head account_head; struct list_head share_head; }; /* state used during master password change */ struct pwchange_info { char *reencrypt_id; char *token; char *privkey_encrypted; char *new_privkey_encrypted; char *new_privkey_hash; char *new_key_hash; struct list_head fields; struct list_head su_keys; }; /* replacement items for password change blob updates */ struct pwchange_field { char *old_ctext; char *new_ctext; bool optional; struct list_head list; }; /* Super-user keys used for enterprise password recovery. */ struct pwchange_su_key { char *uid; /* uid for super user */ struct public_key sharing_key; /* pubkey for this user */ char *new_enc_key; /* user AES key, enc w/ SU's RSA key */ struct list_head list; }; enum blobsync { BLOB_SYNC_AUTO, BLOB_SYNC_YES, BLOB_SYNC_NO }; struct blob *blob_parse(const unsigned char *blob, size_t len, const unsigned char key[KDF_HASH_LEN], const struct private_key *private_key); void blob_free(struct blob *blob); size_t blob_write(const struct blob *blob, const unsigned char key[KDF_HASH_LEN], char **out); 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 app *account_to_app(const struct account *account); struct app *new_app(); struct account *new_account(); void account_free(struct account *account); void account_set_username(struct account *account, char *username, unsigned const char key[KDF_HASH_LEN]); void account_set_password(struct account *account, char *password, unsigned const char key[KDF_HASH_LEN]); void account_set_group(struct account *account, char *group, unsigned const char key[KDF_HASH_LEN]); void account_set_name(struct account *account, char *name, unsigned const char key[KDF_HASH_LEN]); void account_set_fullname(struct account *account, char *fullname, unsigned const char key[KDF_HASH_LEN]); void account_set_url(struct account *account, char *url, unsigned const char key[KDF_HASH_LEN]); void account_set_note(struct account *account, char *note, unsigned const char key[KDF_HASH_LEN]); void account_set_appname(struct account *account, char *appname, unsigned const char key[KDF_HASH_LEN]); void account_assign_share(struct blob *blob, struct account *account, unsigned const char key[KDF_HASH_LEN]); void account_reencrypt(struct account *account, const unsigned char key[KDF_HASH_LEN]); bool account_is_group(struct account *account); void field_set_value(struct account *account, struct field *field, char *value, unsigned const char key[KDF_HASH_LEN]); struct account *notes_expand(struct account *acc); struct account *notes_collapse(struct account *acc); void share_free(struct share *share); struct share *find_unique_share(struct blob *blob, const char *name); #endif lastpass-cli-1.0.0/config.c0000644000175000017500000002621012743671271015757 0ustar troyhebetroyhebe/* * configuration file handling * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "config.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include /* * Map well-known pathnames to their configuration type. */ struct pathname_type_tuple { char *name; enum config_type type; }; struct pathname_type_tuple pathname_type_lookup[] = { { "env", CONFIG_CONFIG }, { "blob", CONFIG_DATA }, { "iterations", CONFIG_DATA }, { "username", CONFIG_DATA }, { "verify", CONFIG_DATA }, { "plaintext_key", CONFIG_DATA }, { "trusted_id", CONFIG_DATA }, { "session_uid", CONFIG_DATA }, { "session_sessionid", CONFIG_DATA }, { "session_token", CONFIG_DATA }, { "session_privatekey", CONFIG_DATA }, { "session_server", CONFIG_DATA }, { "lpass.log", CONFIG_DATA }, { "agent.sock", CONFIG_RUNTIME }, { "uploader.pid", CONFIG_RUNTIME }, }; char *config_type_to_xdg[] = { [CONFIG_DATA] = "XDG_DATA_HOME", [CONFIG_CONFIG] = "XDG_CONFIG_HOME", [CONFIG_RUNTIME] = "XDG_RUNTIME_DIR", }; static char *get_xdg_dir(const char *xdg_var) { char *home; char *retstr = NULL; if (getenv(xdg_var)) return xstrdup(getenv(xdg_var)); /* * $XDG var not set in environment; decide whether * to use backups locations based on existence of * $XDG_RUNTIME_DIR. */ if (!getenv("XDG_RUNTIME_DIR")) return NULL; home = getenv("HOME"); if (!home) return NULL; if (!strcmp(xdg_var, "XDG_DATA_HOME")) xasprintf(&retstr, "%s/.local/share", home); else if (!strcmp(xdg_var, "XDG_CONFIG_HOME")) xasprintf(&retstr, "%s/.config", home); return retstr; } /* * Get the path to a config file given its name and the type of file. * * lpass looks for files in the following directories: * * First, if $LPASS_HOME is set, everything goes there. * * After that, if it is a persistent, user-specific data file, * it goes in $XDG_DATA_HOME/lpass. * * If a configuration item, it goes in $XDG_CONFIG_HOME. * * If a purely runtime item (socket, pidfile, etc) it goes in * $XDG_RUNTIME_HOME. * * If none of the $XDG environment variables are set, fall-back * to ~/.lpass. */ static char *config_path_for_type(enum config_type type, const char *name) { char *home, *path, *xdg_env; _cleanup_free_ char *config = NULL; _cleanup_free_ char *xdg_dir = NULL; struct stat sbuf; int ret; xdg_env = config_type_to_xdg[type]; home = getenv("LPASS_HOME"); if (home) config = xstrdup(home); else if ((xdg_dir = get_xdg_dir(xdg_env))) { xasprintf(&config, "%s/lpass", xdg_dir); } else { home = getenv("HOME"); if (!home) die("HOME is not set"); xasprintf(&config, "%s/.lpass", home); } ret = stat(config, &sbuf); if ((ret == -1 && errno == ENOENT) || !S_ISDIR(sbuf.st_mode)) { unlink(config); if (mkdir(config, 0700) < 0) die_errno("mkdir(%s)", config); } else if (ret == -1) die_errno("stat(%s)", config); xasprintf(&path, "%s/%s", config, name); return path; } enum config_type config_path_type(const char *name) { unsigned int i; /* aliases are config files */ if (!strncmp(name, "alias", 5)) { return CONFIG_CONFIG; } /* lock files are runtime */ if (strlen(name) >= 5 && !strcmp(name-5, ".lock")) { return CONFIG_RUNTIME; } /* categorized this configuration file by name? */ for (i=0; i < ARRAY_SIZE(pathname_type_lookup); i++) { if (!strcmp(name, pathname_type_lookup[i].name)) { return pathname_type_lookup[i].type; } } /* everything else is config_data */ return CONFIG_DATA; } char *config_path(const char *name) { return config_path_for_type(config_path_type(name), name); } FILE *config_fopen(const char *name, const char *mode) { _cleanup_free_ char *path = config_path(name); return fopen(path, mode); } void config_touch(const char *name) { _cleanup_free_ char *path = NULL; path = config_path(name); if (utime(path, NULL) < 0) die_errno("utime"); } bool config_exists(const char *name) { _cleanup_free_ char *path = NULL; struct stat sbuf; path = config_path(name); return stat(path, &sbuf) != -1; } time_t config_mtime(const char *name) { _cleanup_free_ char *path = NULL; struct stat sbuf; path = config_path(name); if (stat(path, &sbuf) < 0) return 0; return sbuf.st_mtime; } bool config_unlink(const char *name) { _cleanup_free_ char *path = config_path(name); return unlink(path) == 0; } void config_write_string(const char *name, const char *string) { config_write_buffer(name, string, strlen(string)); } void config_write_buffer(const char *name, const char *buffer, size_t len) { _cleanup_free_ char *tempname = NULL; _cleanup_free_ char *finalpath = config_path(name); int tempfd; FILE *tempfile = NULL; xasprintf(&tempname, "%s.XXXXXX", finalpath); tempfd = mkstemp(tempname); if (tempfd < 0) die_errno("mkstemp(%s)", tempname); tempfile = fdopen(tempfd, "w"); if (!tempfile) goto error; if (fwrite(buffer, 1, len, tempfile) != len) goto error; fclose(tempfile); tempfile = NULL; if (rename(tempname, finalpath) < 0) goto error; return; error: tempfd = errno; if (tempfile) fclose(tempfile); unlink(tempname); errno = tempfd; die_errno("config-%s", name); } char *config_read_string(const char *name) { _cleanup_free_ char *buffer = NULL; size_t len = config_read_buffer(name, (unsigned char **) &buffer); if (!buffer) return NULL; return xstrndup(buffer, len); } size_t config_read_buffer(const char *name, unsigned char **out) { _cleanup_fclose_ FILE *file = NULL; unsigned char *buffer; size_t len, read; file = config_fopen(name, "r"); if (!file) { *out = NULL; return 0; } for (len = 0, buffer = xmalloc(8192); ; buffer = xrealloc(buffer, len + 8192)) { read = fread(buffer + len, 1, 8192, file); len += read; if (read != 8192) { if (ferror(file)) die_errno("fread(config-%s)", name); break; } } *out = buffer; return len; } /* * ciphertext = IV | aes-256-cbc(plaintext, key) * authenticated-ciphertext = HMAC-SHA256(ciphertext, key) | ciphertext * * These two functions work with `authenticated-ciphertext`. */ static size_t encrypt_buffer(const char *buffer, size_t in_len, unsigned const char key[KDF_HASH_LEN], char **out) { EVP_CIPHER_CTX ctx; char *ciphertext; unsigned char iv[AES_BLOCK_SIZE]; int out_len; unsigned int hmac_len; size_t len; if (!RAND_bytes(iv, AES_BLOCK_SIZE)) die("Could not generate random bytes for CBC IV."); 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-1.0.0/cipher.h0000644000175000017500000000260712743671271015775 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_bytes(const unsigned char *plaintext, size_t in_len, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len); int cipher_rsa_encrypt(const char *plaintext, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len); char *cipher_aes_decrypt(const unsigned char *ciphertext, size_t len, const unsigned char key[KDF_HASH_LEN]); char *cipher_aes_decrypt_base64(const char *ciphertext, const unsigned char key[KDF_HASH_LEN]); size_t cipher_aes_encrypt(const char *plaintext, const unsigned char key[KDF_HASH_LEN], unsigned char **ciphertext); char *cipher_base64(const unsigned char *bytes, size_t len); size_t cipher_unbase64(const char *ciphertext, unsigned char **b64data); char *encrypt_and_base64(const char *str, unsigned const char key[KDF_HASH_LEN]); void cipher_decrypt_private_key(const char *key_hex, unsigned const char key[KDF_HASH_LEN], struct private_key *out_key); char *cipher_encrypt_private_key(struct private_key *private_key, unsigned const char key[KDF_HASH_LEN]); char *cipher_sha256_hex(unsigned char *bytes, size_t len); char *cipher_sha256_b64(unsigned char *bytes, size_t len); #endif lastpass-cli-1.0.0/kdf.h0000644000175000017500000000061112743671271015260 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-1.0.0/agent.h0000644000175000017500000000055412743671271015620 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_ask(unsigned char key[KDF_HASH_LEN]); bool agent_load_key(unsigned char key[KDF_HASH_LEN]); #endif lastpass-cli-1.0.0/log.c0000644000175000017500000000512412743671271015274 0ustar troyhebetroyhebe/* * logging functions * * Copyright (C) 2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "util.h" #include "log.h" #include "config.h" #include #include #define TIME_FMT "%lld.%06lld" #define TIME_ARGS(tv) ((long long)(tv)->tv_sec), ((long long)(tv)->tv_usec) int lpass_log_level() { char *log_level_str; int level; log_level_str = getenv("LPASS_LOG_LEVEL"); if (!log_level_str) return LOG_NONE; level = strtoul(log_level_str, NULL, 10); return (enum log_level) level; } void lpass_log(enum log_level level, char *fmt, ...) { struct timeval tv; struct timezone tz; va_list ap; _cleanup_fclose_ FILE *fp = NULL; int req_level = lpass_log_level(); if (req_level < level) return; fp = lpass_log_open(); if (!fp) return; gettimeofday(&tv, &tz); fprintf(fp, "<%d> [" TIME_FMT "] ", level, TIME_ARGS(&tv)); va_start(ap, fmt); vfprintf(fp, fmt, ap); va_end(ap); fflush(fp); } FILE *lpass_log_open() { _cleanup_free_ char *upload_log_path = NULL; if (lpass_log_level() < 0) return NULL; upload_log_path = config_path("lpass.log"); return fopen(upload_log_path, "a"); } lastpass-cli-1.0.0/clipboard.c0000644000175000017500000000646412743671271016462 0ustar troyhebetroyhebe/* * system copy/paste routines * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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-1.0.0/xml.h0000644000175000017500000000134412743671271015320 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); int xml_parse_pwchange(const char *buf, struct pwchange_info *info); int xml_api_err(const char *buf); int xml_parse_share_getpubkeys(const char *buf, struct list_head *user_list); int xml_parse_share_get_limits(const char *buf, struct share_limit *limit); #endif lastpass-cli-1.0.0/LICENSE.OpenSSL0000644000175000017500000001547112743671271016644 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-1.0.0/cipher.c0000644000175000017500000003161212743671271015766 0ustar troyhebetroyhebe/* * encryption and decryption routines * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cipher.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #define LP_PKEY_PREFIX "LastPassPrivateKey<" #define LP_PKEY_SUFFIX ">LastPassPrivateKey" char *cipher_rsa_decrypt(const unsigned char *ciphertext, size_t len, const struct private_key *private_key) { PKCS8_PRIV_KEY_INFO *p8inf = NULL; EVP_PKEY *pkey = NULL; RSA *rsa = NULL; BIO *memory = NULL; char *ret = NULL; if (!len) return NULL; memory = BIO_new(BIO_s_mem()); if (BIO_write(memory, private_key->key, private_key->len) < 0) goto out; p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(memory, NULL); if (!p8inf) goto out; pkey = EVP_PKCS82PKEY(p8inf); if (!pkey) goto out; 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_bytes(const unsigned char *plaintext, size_t in_len, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len) { EVP_PKEY *pubkey = NULL; RSA *rsa = NULL; BIO *memory = NULL; int ret; if (*out_len < public_key->len) { ret = -EINVAL; goto out; } memory = BIO_new(BIO_s_mem()); ret = BIO_write(memory, public_key->key, public_key->len); if (ret < 0) goto out; ret = -EIO; pubkey = d2i_PUBKEY_bio(memory, NULL); if (!pubkey) goto out; rsa = EVP_PKEY_get1_RSA(pubkey); if (!rsa) goto out; ret = RSA_public_encrypt(in_len, plaintext, out_crypttext, rsa, RSA_PKCS1_OAEP_PADDING); if (ret < 0) goto out; *out_len = ret; ret = 0; out: EVP_PKEY_free(pubkey); RSA_free(rsa); BIO_free_all(memory); return ret; } int cipher_rsa_encrypt(const char *plaintext, const struct public_key *public_key, unsigned char *out_crypttext, size_t *out_len) { return cipher_rsa_encrypt_bytes((unsigned char *) plaintext, strlen(plaintext), public_key, out_crypttext, out_len); } char *cipher_aes_decrypt(const unsigned char *ciphertext, size_t len, const unsigned char key[KDF_HASH_LEN]) { EVP_CIPHER_CTX ctx; char *plaintext; int out_len; if (!len) return NULL; 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; } static size_t cipher_aes_encrypt_bytes(const unsigned char *bytes, size_t len, const unsigned char key[KDF_HASH_LEN], const unsigned char *iv, unsigned char **out) { EVP_CIPHER_CTX ctx; int out_len; size_t ret_len = 0; unsigned char *ctext; ctext = *out; if (!ctext) ctext = xcalloc(len + AES_BLOCK_SIZE * 2 + 1, 1); EVP_CIPHER_CTX_init(&ctx); if (!EVP_EncryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, key, iv)) goto error; if (!EVP_EncryptUpdate(&ctx, ctext, &out_len, bytes, len)) goto error; ret_len += out_len; if (!EVP_EncryptFinal_ex(&ctx, ctext + ret_len, &out_len)) goto error; ret_len += out_len; EVP_CIPHER_CTX_cleanup(&ctx); *out = ctext; return ret_len; error: EVP_CIPHER_CTX_cleanup(&ctx); if (!*out) free(ctext); die("Failed to encrypt data."); } size_t cipher_aes_encrypt(const char *plaintext, const unsigned char key[KDF_HASH_LEN], unsigned char **out) { unsigned char *ciphertext; unsigned char *tmp; unsigned char iv[AES_BLOCK_SIZE]; int in_len; size_t len; if (!RAND_bytes(iv, AES_BLOCK_SIZE)) die("Could not generate random bytes for CBC IV."); in_len = strlen(plaintext); ciphertext = xcalloc(in_len + AES_BLOCK_SIZE * 2 + 1, 1); ciphertext[0] = '!'; len = 1; memcpy(ciphertext + len, iv, AES_BLOCK_SIZE); len += AES_BLOCK_SIZE; tmp = ciphertext + len; len += cipher_aes_encrypt_bytes((unsigned char *)plaintext, in_len, key, iv, &tmp); *out = ciphertext; return len; } static char *base64(const unsigned char *bytes, size_t len) { BIO *memory, *b64; BUF_MEM *buffer; char *output; b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); memory = BIO_new(BIO_s_mem()); if (!b64 || !memory) goto error; b64 = BIO_push(b64, memory); if (!b64) goto error; if (BIO_write(b64, bytes, len) < 0 || BIO_flush(b64) < 0) goto error; BIO_get_mem_ptr(b64, &buffer); output = xmalloc(buffer->length + 1); memcpy(output, buffer->data, buffer->length); output[buffer->length] = '\0'; BIO_free_all(b64); return output; error: die("Could not base64 the given bytes."); } char *cipher_base64(const unsigned char *bytes, size_t len) { _cleanup_free_ char *iv = NULL; _cleanup_free_ char *data = NULL; char *output; if (len >= 33 && bytes[0] == '!' && len % 16 == 1) { iv = base64(bytes + 1, 16); data = base64(bytes + 17, len - 17); xasprintf(&output, "!%s|%s", iv, data); return output; } return base64(bytes, len); } 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."); } size_t cipher_unbase64(const char *ciphertext, unsigned char **b64data) { _cleanup_free_ char *copy = NULL; _cleanup_free_ unsigned char *iv = NULL; _cleanup_free_ unsigned char *data = NULL; unsigned char *unbase64_ciphertext = NULL; char *pipe; size_t iv_len, data_len, len; if (!strlen(ciphertext)) return 0; if (ciphertext[0] != '!') return unbase64(ciphertext, b64data); copy = xstrdup(&ciphertext[1]); pipe = strchr(copy, '|'); if (!pipe) return 0; *pipe = '\0'; iv_len = unbase64(copy, &iv); data_len = unbase64(pipe + 1, &data); len = iv_len + data_len + 1 /* '!' */; unbase64_ciphertext = xcalloc(len, 1); unbase64_ciphertext[0] = '!'; memcpy(&unbase64_ciphertext[1], iv, iv_len); memcpy(&unbase64_ciphertext[1 + iv_len], data, data_len); *b64data = unbase64_ciphertext; return len; } char *cipher_aes_decrypt_base64(const char *ciphertext, const unsigned char key[KDF_HASH_LEN]) { _cleanup_free_ unsigned char *unbase64_ciphertext = NULL; size_t len; len = cipher_unbase64(ciphertext, &unbase64_ciphertext); if (!len) return NULL; return cipher_aes_decrypt(unbase64_ciphertext, len, key); } char *encrypt_and_base64(const char *str, unsigned const char key[KDF_HASH_LEN]) { unsigned char *intermediate = NULL; char *base64 = NULL; size_t len; base64 = xstrdup(str); if (!*base64) return base64; len = cipher_aes_encrypt(base64, key, &intermediate); free(base64); base64 = cipher_base64(intermediate, len); free(intermediate); return base64; } /* * Decrypt the LastPass sharing RSA private key. The key has start_str * and end_str prepended / appended before encryption, and the result * is encrypted with the AES key. * * On success, the resulting key is stored in out_key and mlock()ed. * If there is a non-fatal error (or no key), the resulting structure * will have len = 0. */ void cipher_decrypt_private_key(const char *key_hex, unsigned const char key[KDF_HASH_LEN], struct private_key *out_key) { size_t len; _cleanup_free_ unsigned char *encrypted_key = NULL; _cleanup_free_ char *decrypted_key = NULL; unsigned char *encrypted_key_start; char *start, *end; unsigned char *dec_key = NULL; int ret; #define start_str LP_PKEY_PREFIX #define end_str LP_PKEY_SUFFIX memset(out_key, 0, sizeof(*out_key)); len = strlen(key_hex); if (!len) return; if (key_hex[0] == '!') { /* v2 format */ decrypted_key = cipher_aes_decrypt_base64( key_hex, key); } else { if (len % 2 != 0) die("Key hex in wrong format."); len /= 2; /* v1 format */ len += 16 /* IV */ + 1 /* bang symbol */; encrypted_key = xcalloc(len + 1, 1); encrypted_key[0] = '!'; memcpy(&encrypted_key[1], key, 16); encrypted_key_start = &encrypted_key[17]; hex_to_bytes(key_hex, &encrypted_key_start); decrypted_key = cipher_aes_decrypt(encrypted_key, len, key); } if (!decrypted_key) { warn("Could not decrypt private key."); return; } start = strstr(decrypted_key, start_str); end = strstr(decrypted_key, end_str); if (!start || !end || end <= start) { warn("Could not decode decrypted private key."); return; } start += strlen(start_str); *end = '\0'; ret = hex_to_bytes(start, &dec_key); if (ret) die("Invalid private key after decryption and decoding."); out_key->key = dec_key; out_key->len = strlen(start) / 2; mlock(out_key->key, out_key->len); #undef start_str #undef end_str } /* * Encrypt RSA sharing key. Encrypted key is returned as a hex-encoded string. */ char *cipher_encrypt_private_key(struct private_key *private_key, unsigned const char key[KDF_HASH_LEN]) { unsigned char *key_ptext; unsigned char *ctext = NULL; char *key_hex_dst; char *ctext_hex = NULL; size_t len, ctext_len, hex_len; if (!private_key->len) return xstrdup(""); hex_len = private_key->len * 2; len = strlen(LP_PKEY_PREFIX) + hex_len + strlen(LP_PKEY_SUFFIX); key_ptext = xcalloc(len + 1, 1); memcpy(key_ptext, LP_PKEY_PREFIX, strlen(LP_PKEY_PREFIX)); key_hex_dst = (char *) key_ptext + strlen(LP_PKEY_PREFIX); bytes_to_hex(private_key->key, &key_hex_dst, private_key->len); memcpy(key_ptext + strlen(LP_PKEY_PREFIX) + hex_len, LP_PKEY_SUFFIX, strlen(LP_PKEY_SUFFIX)); ctext_len = cipher_aes_encrypt_bytes(key_ptext, len, key, key, &ctext); bytes_to_hex(ctext, &ctext_hex, ctext_len); free(ctext); return ctext_hex; } /* * Get hex-encoded sha256() of a buffer. */ char *cipher_sha256_hex(unsigned char *bytes, size_t len) { char *tmp = NULL; SHA256_CTX sha256; unsigned char hash[SHA256_DIGEST_LENGTH]; if (!SHA256_Init(&sha256)) goto die; if (!SHA256_Update(&sha256, bytes, len)) goto die; if (!SHA256_Final(hash, &sha256)) goto die; bytes_to_hex(hash, &tmp, sizeof(hash)); return tmp; die: die("SHA-256 hash failed"); } char *cipher_sha256_b64(unsigned char *bytes, size_t len) { _cleanup_free_ unsigned char *hash_raw = NULL; _cleanup_free_ char *hash_hex = NULL; hash_hex = cipher_sha256_hex(bytes, len); hex_to_bytes(hash_hex, &hash_raw); return base64(hash_raw, strlen(hash_hex) / 2); } lastpass-cli-1.0.0/log.h0000644000175000017500000000117512743671271015303 0ustar troyhebetroyhebe#ifndef __LOG_H #define __LOG_H /* * Loglevels for ~/.lpass/lpass.log. By default, nothing is logged, but * setting LPASS_LOG_LEVEL to a positive value will turn on logging. * * NOTE: debug and verbose logs can include sensitive information such as * session IDs in the clear. Do NOT post logs in public without * scrubbing them first! */ enum log_level { LOG_NONE = -1, LOG_ERROR = 3, LOG_WARNING = 4, LOG_INFO = 6, LOG_DEBUG = 7, LOG_VERBOSE = 8, /* _everything_ including CURL verbose logs */ }; int lpass_log_level(); void lpass_log(enum log_level level, char *fmt, ...); FILE *lpass_log_open(); #endif lastpass-cli-1.0.0/version.h0000644000175000017500000000015112743671271016200 0ustar troyhebetroyhebe#define LASTPASS_CLI_VERSION "1.0.0" #define LASTPASS_CLI_USERAGENT "LastPass-CLI/" LASTPASS_CLI_VERSION lastpass-cli-1.0.0/endpoints.h0000644000175000017500000000456712743671271016535 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_move(const struct session *session, struct account *account, struct share *orig_folder); int lastpass_share_create(const struct session *session, const char *sharename); int lastpass_share_delete(const struct session *session, struct share *share); int lastpass_share_get_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *ret_limit); int lastpass_share_set_limits(const struct session *session, struct share *share, struct share_user *user, struct share_limit *limit); int lastpass_pwchange_start(const struct session *session, const char *username, const char hash[KDF_HEX_LEN], struct pwchange_info *pwchange_info); int lastpass_pwchange_complete(const struct session *session, const char *username, const char *enc_username, const char new_hash[KDF_HEX_LEN], int new_iterations, struct pwchange_info *pwchange_info); #endif lastpass-cli-1.0.0/CONTRIBUTING0000644000175000017500000000431712743671271016204 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-1.0.0/cmd-login.c0000644000175000017500000001025612743671271016366 0ustar troyhebetroyhebe/* * command for logging into the service * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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-1.0.0/cmd-rm.c0000644000175000017500000000601112743671271015666 0ustar troyhebetroyhebe/* * command for removing vault entries * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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-1.0.0/.gitignore0000644000175000017500000000007112743671271016333 0ustar troyhebetroyhebe*.o *.d *.swp lpass lpass.1 lpass.exe certificate.h tags lastpass-cli-1.0.0/agent.c0000644000175000017500000002132112743671271015606 0ustar troyhebetroyhebe/* * agent for caching decryption key * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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); } 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-1.0.0/pbkdf2.c0000644000175000017500000000531212743671271015662 0ustar troyhebetroyhebe/* * Copyright (c) 2014-2016 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-1.0.0/cmd.h0000644000175000017500000000753112743671271015267 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), }; enum edit_choice { EDIT_NONE, EDIT_USERNAME, EDIT_PASSWORD, EDIT_URL, EDIT_FIELD, EDIT_NAME, EDIT_NOTES, EDIT_ANY }; void init_all(enum blobsync sync, unsigned char key[KDF_HASH_LEN], struct session **session, struct blob **blob); enum blobsync parse_sync_string(const char *str); struct account *find_unique_account(struct blob *blob, const char *name); void find_matching_accounts(struct list_head *accounts, const char *name, struct list_head *ret_list); void find_matching_regex(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list); void find_matching_substr(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list); enum color_mode parse_color_mode_string(const char *colormode); bool parse_bool_arg_string(const char *extra); enum note_type parse_note_type_string(const char *extra); int edit_account(struct session *session, struct blob *blob, enum blobsync sync, struct account *editable, enum edit_choice choice, const char *field, bool non_interactive, unsigned char key[KDF_HASH_LEN]); int edit_new_account(struct session *session, struct blob *blob, enum blobsync sync, const char *name, enum edit_choice choice, const char *field, bool non_interactive, bool is_app, enum note_type note_type, unsigned char key[KDF_HASH_LEN]); #define color_usage "[--color=auto|never|always]" int cmd_login(int argc, char **argv); #define cmd_login_usage "login [--trust] [--plaintext-key [--force, -f]] " color_usage " USERNAME" int cmd_logout(int argc, char **argv); #define cmd_logout_usage "logout [--force, -f] " color_usage int cmd_passwd(int argc, char **argv); #define cmd_passwd_usage "passwd" int cmd_show(int argc, char **argv); #define cmd_show_usage "show [--sync=auto|now|no] [--clip, -c] [--expand-multi, -x] [--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] [-m] [-u] " color_usage " [GROUP]" int cmd_add(int argc, char **argv); #define cmd_add_usage "add [--sync=auto|now|no] [--non-interactive] " color_usage " {--username|--password|--url|--notes|--field=FIELD|--note-type=NOTETYPE} NAME" int cmd_edit(int argc, char **argv); #define cmd_edit_usage "edit [--sync=auto|now|no] [--non-interactive] " color_usage " {--name|--username|--password|--url|--notes|--field=FIELD} {NAME|UNIQUEID}" int cmd_generate(int argc, char **argv); #define cmd_generate_usage "generate [--sync=auto|now|no] [--clip, -c] [--username=USERNAME] [--url=URL] [--no-symbols] {NAME|UNIQUEID} LENGTH" int cmd_duplicate(int argc, char **argv); #define cmd_duplicate_usage "duplicate [--sync=auto|now|no] " color_usage " {UNIQUENAME|UNIQUEID}" int cmd_rm(int argc, char **argv); #define cmd_rm_usage "rm [--sync=auto|now|no] " color_usage " {UNIQUENAME|UNIQUEID}" int cmd_status(int argc, char **argv); #define cmd_status_usage "status [--quiet, -q] " color_usage int cmd_sync(int argc, char **argv); #define cmd_sync_usage "sync [--background, -b] " color_usage int cmd_export(int argc, char **argv); #define cmd_export_usage "export [--sync=auto|now|no] " color_usage int cmd_share(int argc, char **argv); #define cmd_share_usage "share subcommand sharename ..." #endif int cmd_mv(int argc, char **argv); #define cmd_mv_usage "mv " color_usage " {UNIQUENAME|UNIQUEID} GROUP" lastpass-cli-1.0.0/terminal.c0000644000175000017500000000543112743671271016327 0ustar troyhebetroyhebe/* * terminal printing routines * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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-1.0.0/Makefile0000644000175000017500000000455412743671271016015 0ustar troyhebetroyhebePREFIX ?= /usr DESTDIR ?= BINDIR ?= $(PREFIX)/bin LIBDIR ?= $(PREFIX)/lib MANDIR ?= $(PREFIX)/share/man COMPDIR ?= $(shell pkg-config --variable=completionsdir bash-completion 2>/dev/null || echo $(PREFIX)/share/bash-completion/completions) 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) HAS_XCODE := $(shell sh -c 'xcodebuild -version 2>/dev/null && echo 1') CFLAGS += -Wno-deprecated-declarations -I/usr/local/opt/openssl/include LDFLAGS += -L/usr/local/opt/openssl/lib endif ifeq ($(HAS_XCODE),1) SDKROOT ?= $(shell xcodebuild -version -sdk macosx | sed -n 's/^Path: \(.*\)/\1/p') CFLAGS += -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 $< 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" @install -v -d "$(DESTDIR)$(COMPDIR)" && install -m 0644 -v contrib/lpass_bash_completion "$(DESTDIR)$(COMPDIR)/lpass" uninstall: @rm -vrf "$(DESTDIR)$(MANDIR)/man1/lpass.1" "$(DESTDIR)$(BINDIR)/lpass" "$(DESTDIR)$(COMPDIR)/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-1.0.0/blob.c0000644000175000017500000007450412743671271015441 0ustar troyhebetroyhebe/* * encrypted vault parsing * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "blob.h" #include "config.h" #include "endpoints.h" #include "cipher.h" #include "util.h" #include "upload-queue.h" #include "version.h" #include #include #include #include #include #include #include #if defined(__APPLE__) && defined(__MACH__) #include #define htobe32(x) OSSwapHostToBigInt32(x) #define be32toh(x) OSSwapBigToHostInt32(x) #else # if (defined(__unix__) || defined(unix)) && !defined(USG) # include # endif # if defined(BSD) # include # else # include # endif #endif struct app *account_to_app(const struct account *account) { return container_of(account, struct app, account); } void share_free(struct share *share) { if (!share) return; free(share->name); free(share->id); free(share->chunk); free(share); } void field_free(struct field *field) { if (!field) return; free(field->name); free(field->value); free(field->value_encrypted); free(field->type); free(field); } void account_free_contents(struct account *account) { struct field *field, *tmp; free(account->id); free(account->name); free(account->group); free(account->fullname); free(account->url); free(account->username); free(account->password); free(account->note); free(account->name_encrypted); free(account->group_encrypted); free(account->username_encrypted); free(account->password_encrypted); free(account->note_encrypted); list_for_each_entry_safe(field, tmp, &account->field_head, list) { field_free(field); } } void app_free(struct app *app) { account_free_contents(&app->account); free(app->appname); free(app->extra); free(app->extra_encrypted); free(app->wintitle); free(app->wininfo); free(app->exeversion); free(app->warnversion); free(app->exehash); } bool account_is_group(struct account *account) { return !strcmp(account->url, "http://group"); } struct app *new_app() { struct app *app = new0(struct app, 1); struct account *account = &app->account; app->appname = xstrdup(""); app->extra = xstrdup(""); app->extra_encrypted = xstrdup(""); INIT_LIST_HEAD(&account->field_head); account->is_app = true; return app; } 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) { if (!account) return; if (account->is_app) { app_free(account_to_app(account)); return; } account_free_contents(account); free(account); } void blob_free(struct blob *blob) { if (!blob) return; struct account *account, *tmp; struct share *share, *tmp_share; list_for_each_entry_safe(account, tmp, &blob->account_head, list) account_free(account); list_for_each_entry_safe(share, tmp_share, &blob->share_head, list) share_free(share); free(blob); } struct blob_pos { const unsigned char *data; size_t len; }; struct chunk { char name[4 + 1]; const unsigned char *data; size_t len; }; struct item { const unsigned char *data; size_t len; }; static bool read_chunk(struct blob_pos *blob, struct chunk *chunk) { if (blob->len < 4) return false; chunk->name[0] = blob->data[0]; chunk->name[1] = blob->data[1]; chunk->name[2] = blob->data[2]; chunk->name[3] = blob->data[3]; chunk->name[4] = '\0'; blob->len -= 4; blob->data += 4; if (blob->len < sizeof(uint32_t)) return false; chunk->len = be32toh(*((uint32_t *)blob->data)); blob->len -= sizeof(uint32_t); blob->data += sizeof(uint32_t); if (chunk->len > blob->len) return false; chunk->data = blob->data; blob->data += chunk->len; blob->len -= chunk->len; return true; } static bool read_item(struct chunk *chunk, struct item *item) { if (chunk->len < sizeof(uint32_t)) return false; item->len = be32toh(*((uint32_t *)chunk->data)); chunk->len -= sizeof(uint32_t); chunk->data += sizeof(uint32_t); if (item->len > chunk->len) return false; item->data = chunk->data; chunk->data += item->len; chunk->len -= item->len; return true; } static char *read_hex_string(struct chunk *chunk) { struct item item; int result; char *str = NULL; if (!read_item(chunk, &item)) return NULL; if (item.len == 0) return xstrdup(""); result = hex_to_bytes((char *) item.data, (unsigned char **) &str); if (result) { free(str); return NULL; } return str; } static char *read_plain_string(struct chunk *chunk) { struct item item; if (!read_item(chunk, &item)) return NULL; if (item.len == 0) return xstrdup(""); return xstrndup((char *) item.data, item.len); } static char *read_crypt_string(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN], char **stored_base64) { struct item item; char *ptext; if (!read_item(chunk, &item)) return NULL; if (stored_base64) *stored_base64 = cipher_base64(item.data, item.len); if (item.len == 0) return xstrdup(""); ptext = cipher_aes_decrypt(item.data, item.len, key); if (!ptext) /* don't fail whole blob if this item cannot be decrypted */ return xstrdup(""); return ptext; } static int read_boolean(struct chunk *chunk) { struct item item; if (!read_item(chunk, &item)) return -1; if (item.len != 1) return 0; return item.data[0] == '1'; } #define entry_plain_at(base, var) do { \ char *__entry_val__ = read_plain_string(chunk); \ if (!__entry_val__) \ goto error; \ base->var = __entry_val__; \ } while (0) #define entry_plain(var) entry_plain_at(parsed, var) #define entry_hex_at(base, var) do { \ char *__entry_val__ = read_hex_string(chunk); \ if (!__entry_val__) \ goto error; \ base->var = __entry_val__; \ } while (0) #define entry_hex(var) entry_hex_at(parsed, var) #define entry_boolean(var) do { \ int __entry_val__ = read_boolean(chunk); \ if (__entry_val__ < 0) \ goto error; \ parsed->var = __entry_val__; \ } while (0) #define entry_crypt_at(base, var) do { \ char *__entry_val__ = read_crypt_string(chunk, key, &base->var##_encrypted); \ if (!__entry_val__) \ goto error; \ base->var = __entry_val__; \ } while (0) #define entry_crypt(var) entry_crypt_at(parsed, var) #define skip(placeholder) do { \ struct item skip_item; \ if (!read_item(chunk, &skip_item)) \ goto error; \ } while (0) static struct account *account_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct account *parsed = new_account(); entry_plain(id); entry_crypt(name); entry_crypt(group); entry_hex(url); entry_crypt(note); entry_boolean(fav); skip(sharedfromaid); entry_crypt(username); entry_crypt(password); entry_boolean(pwprotect); skip(genpw); skip(sn); entry_plain(last_touch); skip(autologin); skip(never_autofill); skip(realm_data); skip(fiid); skip(custom_js); skip(submit_id); skip(captcha_id); skip(urid); skip(basic_auth); skip(method); skip(action); skip(groupid); skip(deleted); 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'; /* use name as 'fullname' only if there's no assigned group */ if (strlen(parsed->group) && (strlen(parsed->name) || account_is_group(parsed))) xasprintf(&parsed->fullname, "%s/%s", parsed->group, parsed->name); else parsed->fullname = xstrdup(parsed->name); return parsed; error: account_free(parsed); return NULL; } static struct field *field_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct field *parsed = new0(struct field, 1); entry_plain(name); entry_plain(type); if (!strcmp(parsed->type, "email") || !strcmp(parsed->type, "tel") || !strcmp(parsed->type, "text") || !strcmp(parsed->type, "password") || !strcmp(parsed->type, "textarea")) entry_crypt(value); else entry_plain(value); entry_boolean(checked); return parsed; error: field_free(parsed); return NULL; } static struct field *app_field_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct field *parsed = new0(struct field, 1); entry_plain(name); entry_crypt(value); entry_plain(type); return parsed; error: field_free(parsed); return NULL; } static struct share *share_parse(struct chunk *chunk, const struct private_key *private_key) { struct share *parsed = new0(struct share, 1); struct item item; _cleanup_free_ unsigned char *ciphertext = NULL; _cleanup_free_ char *hex_key = NULL; _cleanup_free_ unsigned char *key = NULL; _cleanup_free_ char *base64_name = NULL; size_t len; if (!private_key) goto error; if (chunk->len) { parsed->chunk_len = chunk->len; parsed->chunk = xmalloc(chunk->len); memcpy(parsed->chunk, chunk->data, chunk->len); } entry_plain(id); if (!read_item(chunk, &item) || item.len == 0 || item.len % 2 != 0) goto error; hex_to_bytes((char *) item.data, &ciphertext); hex_key = cipher_rsa_decrypt(ciphertext, item.len / 2, private_key); if (!hex_key) goto error; len = strlen(hex_key); if (len % 2 != 0) goto error; len /= 2; if (len != KDF_HASH_LEN) goto error; hex_to_bytes(hex_key, &key); mlock(parsed->key, KDF_HASH_LEN); memcpy(parsed->key, key, KDF_HASH_LEN); base64_name = read_plain_string(chunk); parsed->name = cipher_aes_decrypt_base64(base64_name, parsed->key); if (!parsed->name) goto error; entry_boolean(readonly); return parsed; error: share_free(parsed); return NULL; } static struct app *app_parse(struct chunk *chunk, const unsigned char key[KDF_HASH_LEN]) { struct app *app = new_app(); struct account *parsed = &app->account; entry_plain(id); entry_hex_at(app, appname); entry_crypt_at(app, extra); entry_crypt(name); entry_crypt(group); entry_plain(last_touch); skip(fiid); entry_boolean(pwprotect); entry_boolean(fav); entry_plain_at(app, wintitle); entry_plain_at(app, wininfo); entry_plain_at(app, exeversion); skip(autologin); entry_plain_at(app, warnversion); entry_plain_at(app, exehash); parsed->username = xstrdup(""); parsed->password = xstrdup(""); parsed->note = xstrdup(""); parsed->url = xstrdup(""); if (strlen(parsed->group) && (strlen(parsed->name) || account_is_group(parsed))) xasprintf(&parsed->fullname, "%s/%s", parsed->group, parsed->name); else parsed->fullname = xstrdup(parsed->name); return app; error: app_free(app); return NULL; } #undef entry_plain #undef entry_plain_at #undef entry_hex #undef entry_boolean #undef entry_crypt #undef entry_crypt_at #undef skip struct blob *blob_parse(const unsigned char *blob, size_t len, const unsigned char key[KDF_HASH_LEN], const struct private_key *private_key) { struct blob_pos blob_pos = { .data = blob, .len = len }; struct chunk chunk; struct account *account = NULL; struct field *field; struct share *share, *last_share = NULL; struct app *app = NULL; struct blob *parsed; _cleanup_free_ char *versionstr = NULL; parsed = new0(struct blob, 1); parsed->local_version = false; INIT_LIST_HEAD(&parsed->account_head); INIT_LIST_HEAD(&parsed->share_head); while (read_chunk(&blob_pos, &chunk)) { if (!strcmp(chunk.name, "LPAV")) { versionstr = xstrndup((char *) chunk.data, chunk.len); parsed->version = strtoull(versionstr, NULL, 10); } else if (!strcmp(chunk.name, "ACCT")) { account = account_parse(&chunk, last_share ? last_share->key : key); if (!account) goto error; if (last_share) { account->share = last_share; char *tmp = account->fullname; xasprintf(&account->fullname, "%s/%s", last_share->name, tmp); free(tmp); } list_add(&account->list, &parsed->account_head); } else if (!strcmp(chunk.name, "ACFL") || !strcmp(chunk.name, "ACOF")) { if (!account) goto error; field = field_parse(&chunk, last_share ? last_share->key : key); if (!field) goto error; list_add_tail(&field->list, &account->field_head); } else if (!strcmp(chunk.name, "LOCL")) { parsed->local_version = true; } else if (!strcmp(chunk.name, "SHAR")) { share = share_parse(&chunk, private_key); last_share = share; if (share) list_add_tail(&share->list, &parsed->share_head); } else if (!strcmp(chunk.name, "AACT")) { app = app_parse(&chunk, last_share ? last_share->key : key); if (app) list_add_tail(&app->account.list, &parsed->account_head); } else if (!strcmp(chunk.name, "AACF")) { if (!app) goto error; field = app_field_parse(&chunk, last_share ? last_share->key : key); if (!field) goto error; list_add_tail(&field->list, &app->account.field_head); } } if (!versionstr) 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 *enc_str) { _cleanup_free_ unsigned char *encrypted = NULL; size_t len; /* * enc_str is base64-encoded, but we write out raw bytes in * the saved blob, so un-base64. */ len = cipher_unbase64(enc_str, &encrypted); write_item(buffer, (char *) encrypted, len); } static void write_boolean(struct buffer *buffer, bool yes) { write_plain_string(buffer, yes ? "1" : "0"); } static void write_chunk(struct buffer *dstbuffer, struct buffer *srcbuffer, char *tag) { if (strlen(tag) != 4) return; buffer_append(dstbuffer, tag, 4); write_item(dstbuffer, srcbuffer->bytes, srcbuffer->len); } static void write_app_chunk(struct buffer *buffer, struct account *account) { struct buffer accbuf, fieldbuf; struct field *field; struct app *app = account_to_app(account); memset(&accbuf, 0, sizeof(accbuf)); write_plain_string(&accbuf, account->id); write_hex_string(&accbuf, app->appname); write_crypt_string(&accbuf, app->extra_encrypted); write_crypt_string(&accbuf, account->name_encrypted); write_crypt_string(&accbuf, account->group_encrypted); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_boolean(&accbuf, account->pwprotect); write_boolean(&accbuf, account->fav); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_chunk(buffer, &accbuf, "AACT"); free(accbuf.bytes); list_for_each_entry(field, &account->field_head, list) { memset(&fieldbuf, 0, sizeof(fieldbuf)); write_plain_string(&fieldbuf, field->name); if (!strcmp(field->type, "email") || !strcmp(field->type, "tel") || !strcmp(field->type, "text") || !strcmp(field->type, "password") || !strcmp(field->type, "textarea")) write_crypt_string(&fieldbuf, field->value_encrypted); else write_plain_string(&fieldbuf, field->value); write_plain_string(&fieldbuf, field->type); write_chunk(buffer, &fieldbuf, "AACF"); free(fieldbuf.bytes); } } static void write_account_chunk(struct buffer *buffer, struct account *account) { struct buffer accbuf, fieldbuf; struct field *field; if (account->is_app) { write_app_chunk(buffer, account); return; } memset(&accbuf, 0, sizeof(accbuf)); write_plain_string(&accbuf, account->id); write_crypt_string(&accbuf, account->name_encrypted); write_crypt_string(&accbuf, account->group_encrypted); write_hex_string(&accbuf, account->url); write_crypt_string(&accbuf, account->note_encrypted); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_crypt_string(&accbuf, account->username_encrypted); write_crypt_string(&accbuf, account->password_encrypted); write_boolean(&accbuf, account->pwprotect); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_plain_string(&accbuf, "skipped"); write_chunk(buffer, &accbuf, "ACCT"); free(accbuf.bytes); list_for_each_entry(field, &account->field_head, list) { memset(&fieldbuf, 0, sizeof(fieldbuf)); write_plain_string(&fieldbuf, field->name); write_plain_string(&fieldbuf, field->type); if (!strcmp(field->type, "email") || !strcmp(field->type, "tel") || !strcmp(field->type, "text") || !strcmp(field->type, "password") || !strcmp(field->type, "textarea")) write_crypt_string(&fieldbuf, field->value_encrypted); else write_plain_string(&fieldbuf, field->value); write_boolean(&fieldbuf, field->checked); write_chunk(buffer, &fieldbuf, "ACFL"); free(fieldbuf.bytes); } } static void write_share_chunk(struct buffer *buffer, struct share *share) { struct buffer sharebuf = { .bytes = share->chunk, .len = share->chunk_len, .max = share->chunk_len }; write_chunk(buffer, &sharebuf, "SHAR"); } size_t blob_write(const struct blob *blob, const unsigned char key[KDF_HASH_LEN], char **out) { struct buffer buffer; struct share *last_share = NULL; struct account *account; UNUSED(key); memset(&buffer, 0, sizeof(buffer)); _cleanup_free_ char *version = xultostr(blob->version); buffer_append(&buffer, "LPAV", 4); write_plain_string(&buffer, version); buffer_append(&buffer, "LOCL", 4); write_plain_string(&buffer, LASTPASS_CLI_VERSION); list_for_each_entry(account, &blob->account_head, list) { if (!account->share) write_account_chunk(&buffer, account); } 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); } *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 { \ if (!obj->field || !field || strcmp(obj->field, field)) { \ set_field(obj, field); \ free(obj->field##_encrypted); \ obj->field##_encrypted = encrypt_and_base64(field, account->share ? account->share->key : key); \ } \ } while (0) #define reencrypt_field(obj, field) do { \ free(obj->field##_encrypted); \ obj->field##_encrypted = encrypt_and_base64(obj->field, account->share ? account->share->key : key); \ } while (0) void account_set_username(struct account *account, char *username, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, username); } void account_set_password(struct account *account, char *password, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, password); } void account_set_group(struct account *account, char *group, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, group); } void account_set_name(struct account *account, char *name, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, name); } void account_set_note(struct account *account, char *note, unsigned const char key[KDF_HASH_LEN]) { set_encrypted_field(account, note); } void account_set_url(struct account *account, char *url, unsigned const char key[KDF_HASH_LEN]) { UNUSED(key); set_field(account, url); } void account_set_appname(struct account *account, char *appname, unsigned const char key[KDF_HASH_LEN]) { UNUSED(key); struct app *app; if (!account->is_app) return; app = account_to_app(account); set_field(app, appname); } void field_set_value(struct account *account, struct field *field, char *value, unsigned const char key[KDF_HASH_LEN]) { if (!strcmp(field->type, "email") || !strcmp(field->type, "tel") || !strcmp(field->type, "text") || !strcmp(field->type, "password") || !strcmp(field->type, "textarea")) set_encrypted_field(field, value); else set_field(field, value); } static bool is_shared_folder_name(const char *fullname) { return !strncmp(fullname, "Shared-", 7) && strchr(fullname, '/'); } void account_reencrypt(struct account *account, const unsigned char key[KDF_HASH_LEN]) { struct field *field; reencrypt_field(account, name); reencrypt_field(account, group); reencrypt_field(account, username); reencrypt_field(account, password); reencrypt_field(account, note); list_for_each_entry(field, &account->field_head, list) { reencrypt_field(field, value); } } /* * Set just group and name, assuming we've stripped off any leading * shared folder from fullname. */ static void account_set_group_name(struct account *account, const char *groupname, unsigned const char key[KDF_HASH_LEN]) { char *slash = strrchr(groupname, '/'); if (!slash) { account_set_name(account, xstrdup(groupname), key); account_set_group(account, xstrdup(""), key); } else { account_set_name(account, xstrdup(slash + 1), key); account_set_group(account, xstrndup(groupname, slash - groupname), key); } } void account_set_fullname(struct account *account, char *fullname, unsigned const char key[KDF_HASH_LEN]) { char *groupname = fullname; /* skip Shared-XXX/ for shared folders */ if (is_shared_folder_name(fullname)) { char *tmp = strchr(fullname, '/'); if (tmp) groupname = tmp + 1; } account_set_group_name(account, groupname, key); free(account->fullname); account->fullname = fullname; } struct share *find_unique_share(struct blob *blob, const char *name) { struct share *share; list_for_each_entry(share, &blob->share_head, list) { if (!strcasecmp(share->name, name)) { return share; } } return NULL; } /* * Assign an account to the proper shared folder, if any. * * If the share changed from whatever it was previously, the account * fields are reencrypted with either the share key or the blob key. * * This function may exit if the name represents a shared folder but * same folder is not available. */ void account_assign_share(struct blob *blob, struct account *account, unsigned const char key[KDF_HASH_LEN]) { struct share *share, *old_share; _cleanup_free_ char *shared_name = NULL; char *name = account->fullname; old_share = account->share; /* strip off shared groupname */ char *slash = strchr(name, '/'); if (!slash) { account->share = NULL; goto reencrypt; } shared_name = xstrndup(name, slash - name); /* find a share matching group name */ share = find_unique_share(blob, shared_name); if (!share && is_shared_folder_name(name)) { /* don't allow normal folders named like SFs */ die("Unable to find shared folder for %s in blob\n", name); } account->share = share; /* update group name to not include new share, if needed */ if (share) account_set_group_name(account, slash + 1, key); reencrypt: if (old_share != account->share) account_reencrypt(account, key); } 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-1.0.0/list.h0000644000175000017500000003034212743671271015473 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; } #ifndef __clang_analyzer__ #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) #else #define LIST_POISON1 NULL #define LIST_POISON2 NULL #endif /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(const struct list_head *head) { return head->next == head; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty() on entry does not return true after this, the entry is * in an undefined state. */ static inline void __list_del_entry(struct list_head *entry) { __list_del(entry->prev, entry->next); } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } /** * list_entry - get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_first_entry - get the first element from a list * @ptr: the list head to take the element from. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. * * Note, that list is expected to be not empty. */ #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) /** * list_last_entry - get the last element from a list * @ptr: the list head to take the element from. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. * * Note, that list is expected to be not empty. */ #define list_last_entry(ptr, type, member) \ list_entry((ptr)->prev, type, member) /** * list_first_entry_or_null - get the first element from a list * @ptr: the list head to take the element from. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. * * Note that if the list is empty, it returns NULL. */ #define list_first_entry_or_null(ptr, type, member) \ (!list_empty(ptr) ? list_first_entry(ptr, type, member) : NULL) /** * list_next_entry - get the next element in list * @pos: the type * to cursor * @member: the name of the list_struct within the struct. */ #define list_next_entry(pos, member) \ list_entry((pos)->member.next, typeof(*(pos)), member) /** * list_prev_entry - get the prev element in list * @pos: the type * to cursor * @member: the name of the list_struct within the struct. */ #define list_prev_entry(pos, member) \ list_entry((pos)->member.prev, typeof(*(pos)), member) /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) /** * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_prev_safe(pos, n, head) \ for (pos = (head)->prev, n = pos->prev; \ pos != (head); \ pos = n, n = pos->prev) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member); \ &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_last_entry(head, typeof(*pos), member); \ &pos->member != (head); \ pos = list_prev_entry(pos, member)) /** * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() * @pos: the type * to use as a start point * @head: the head of the list * @member: the name of the list_struct within the struct. * * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). */ #define list_prepare_entry(pos, head, member) \ ((pos) ? : list_entry(head, typeof(*pos), member)) /** * list_for_each_entry_continue - continue iteration over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Continue to iterate over list of given type, continuing after * the current position. */ #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_next_entry(pos, member); \ &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_continue_reverse - iterate backwards from the given point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Start to iterate over list of given type backwards, continuing after * the current position. */ #define list_for_each_entry_continue_reverse(pos, head, member) \ for (pos = list_prev_entry(pos, member); \ &pos->member != (head); \ pos = list_prev_entry(pos, member)) /** * list_for_each_entry_from - iterate over list of given type from the current point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing from current position. */ #define list_for_each_entry_from(pos, head, member) \ for (; &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member), \ n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_continue - continue list iteration safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing after current point, * safe against removal of list entry. */ #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_next_entry(pos, member), \ n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_from - iterate over list from current point safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type from current point, safe against * removal of list entry. */ #define list_for_each_entry_safe_from(pos, n, head, member) \ for (n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate backwards over list of given type, safe against removal * of list entry. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_last_entry(head, typeof(*pos), member), \ n = list_prev_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_prev_entry(n, member)) /** * list_safe_reset_next - reset a stale list_for_each_entry_safe loop * @pos: the loop cursor used in the list_for_each_entry_safe loop * @n: temporary storage used in list_for_each_entry_safe * @member: the name of the list_struct within the struct. * * list_safe_reset_next is not safe to use in general if the list may be * modified concurrently (eg. the lock is dropped in the loop body). An * exception to this is if the cursor element (pos) is pinned in the list, * and list_safe_reset_next is called after re-taking the lock and before * completing the current iteration of the loop body. */ #define list_safe_reset_next(pos, n, member) \ n = list_next_entry(pos, member) #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #ifndef __clang_analyzer__ #define container_of(ptr, type, member) __extension__({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #else #define container_of(ptr, type, member) __extension__({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - (char *)offsetof(type,member) );}) #endif #endif lastpass-cli-1.0.0/terminal.h0000644000175000017500000000322112743671271016327 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-1.0.0/xml.c0000644000175000017500000004020112743671271015306 0ustar troyhebetroyhebe/* * xml parsing routines * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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 = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) goto out; root = xmlDocGetRootElement(doc); if (root && !xmlStrcmp(root->name, BAD_CAST "response")) { for (root = root->children; root; root = root->next) { if (!xmlStrcmp(root->name, BAD_CAST "ok")) break; } } if (root && !xmlStrcmp(root->name, BAD_CAST "ok")) { session = session_new(); for (xmlAttrPtr attr = root->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "uid")) session->uid = (char *)xmlNodeListGetString(doc, attr->children, 1); if (!xmlStrcmp(attr->name, BAD_CAST "sessionid")) session->sessionid = (char *)xmlNodeListGetString(doc, attr->children, 1); if (!xmlStrcmp(attr->name, BAD_CAST "token")) session->token = (char *)xmlNodeListGetString(doc, attr->children, 1); if (!xmlStrcmp(attr->name, BAD_CAST "privatekeyenc")) { _cleanup_free_ char *private_key = (char *)xmlNodeListGetString(doc, attr->children, 1); session_set_private_key(session, key, private_key); } } } out: if (doc) xmlFreeDoc(doc); if (!session_is_valid(session)) { session_free(session); return NULL; } return session; } unsigned long long xml_login_check(const char *buf, struct session *session) { _cleanup_free_ char *versionstr = NULL; unsigned long long version = 0; xmlDoc *doc = NULL; xmlNode *root, *child = NULL; doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) goto out; root = xmlDocGetRootElement(doc); if (root && !xmlStrcmp(root->name, BAD_CAST "response")) { for (child = root->children; child; child = child->next) { if (!xmlStrcmp(child->name, BAD_CAST "ok")) break; } } if (child) { for (xmlAttrPtr attr = child->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "uid")) { free(session->uid); session->uid = (char *)xmlNodeListGetString(doc, attr->children, 1); } else if (!xmlStrcmp(attr->name, BAD_CAST "sessionid")) { free(session->sessionid); session->sessionid = (char *)xmlNodeListGetString(doc, attr->children, 1); } else if (!xmlStrcmp(attr->name, BAD_CAST "token")) { free(session->token); session->token = (char *)xmlNodeListGetString(doc, attr->children, 1); } else if (!xmlStrcmp(attr->name, BAD_CAST "accts_version")) { versionstr = (char *)xmlNodeListGetString(doc, attr->children, 1); version = strtoull(versionstr, NULL, 10); } } } out: if (doc) xmlFreeDoc(doc); return version; } char *xml_error_cause(const char *buf, const char *what) { char *result = NULL; xmlDoc *doc = NULL; xmlNode *root; doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) goto out; root = xmlDocGetRootElement(doc); if (root && !xmlStrcmp(root->name, BAD_CAST "response")) { for (xmlNode *child = root->children; child; child = child->next) { if (!xmlStrcmp(child->name, BAD_CAST "error")) { for (xmlAttrPtr attr = child->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST what)) { result = (char *)xmlNodeListGetString(doc, attr->children, 1); goto out; } } break; } } } out: if (doc) xmlFreeDoc(doc); if (!result) result = xstrdup("unknown"); return result; } /* * Check if node has the tag "name", and interpret as a string * if so. * * Return true and update the string pointed to by ptr if the node * matches name. */ static bool xml_parse_str(xmlDoc *doc, xmlNode *parent, const char *name, char **ptr) { if (xmlStrcmp(parent->name, BAD_CAST name)) return false; *ptr = (char *) xmlNodeListGetString(doc, parent->xmlChildrenNode, 1); if (!*ptr) return false; return true; } /* * Check if node has the tag "name", and interpret as an int if so. * * Return true and update the int pointed to by ptr if the node * matches name. */ static bool xml_parse_int(xmlDoc *doc, xmlNode *parent, const char *name, int *ptr) { if (xmlStrcmp(parent->name, BAD_CAST name)) return false; _cleanup_free_ char *str = (char *) xmlNodeListGetString(doc, parent->xmlChildrenNode, 1); if (!str) return false; *ptr = atoi(str); return true; } /* * Check if node is for the boolean "name", and interpret as a boolean * if so. * * Return true and update the boolean pointed to by ptr if the node * matches name. */ static bool xml_parse_bool(xmlDoc *doc, xmlNode *parent, const char *name, bool *ptr) { int intval; if (!xml_parse_int(doc, parent, name, &intval)) return false; *ptr = intval; return true; } static void xml_parse_share_permissions(xmlDoc *doc, xmlNode *item, struct share_user *user) { bool tmp; for (xmlNode *child = item->children; child; child = child->next) { if (xml_parse_bool(doc, child, "canadminister", &user->admin)) continue; if (xml_parse_bool(doc, child, "readonly", &user->read_only)) continue; if (xml_parse_bool(doc, child, "give", &tmp)) { user->hide_passwords = !tmp; continue; } } } static void xml_parse_share_user(xmlDoc *doc, xmlNode *item, struct share_user *user) { char *tmp; /* process a user item */ for (xmlNode *child = item->children; child; child = child->next) { if (xml_parse_str(doc, child, "realname", &user->realname)) continue; if (xml_parse_str(doc, child, "username", &user->username)) continue; if (xml_parse_str(doc, child, "uid", &user->uid)) continue; if (xml_parse_bool(doc, child, "group", &user->is_group)) continue; if (xml_parse_bool(doc, child, "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); } } static int xml_parse_share_key_entry(xmlDoc *doc, xmlNode *root, struct share_user *user, int idx) { char *tmp; _cleanup_free_ char *pubkey = NULL; _cleanup_free_ char *username = NULL; _cleanup_free_ char *uid = NULL; _cleanup_free_ char *cgid = NULL; xasprintf(&pubkey, "pubkey%d", idx); xasprintf(&username, "username%d", idx); xasprintf(&uid, "uid%d", idx); xasprintf(&cgid, "cgid%d", idx); memset(user, 0, sizeof(*user)); for (xmlNode *item = root->children; item; item = item->next) { if (xml_parse_str(doc, item, pubkey, &tmp)) { int ret = hex_to_bytes(tmp, &user->sharing_key.key); if (ret == 0) user->sharing_key.len = strlen(tmp) / 2; free(tmp); continue; } if (xml_parse_str(doc, item, username, &user->username)) continue; if (xml_parse_str(doc, item, uid, &user->uid)) continue; if (xml_parse_str(doc, item, cgid, &user->cgid)) continue; } if (!user->uid) { free(user->cgid); free(user->username); free(user->sharing_key.key); return -ENOENT; } return 0; } int xml_parse_share_getinfo(const char *buf, struct list_head *users) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) return -EINVAL; /* * XML fields are as follows: * xmlresponse * users * item * realname * uid * group * username * permissions * readonly * canadminister * give * outsideenterprise * accepted * item... */ xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "xmlresponse") || !root->children || xmlStrcmp(root->children->name, BAD_CAST "users")) { ret = -EINVAL; goto free_doc; } xmlNode *usernode = root->children; for (xmlNode *item = usernode->children; item; item = item->next) { if (xmlStrcmp(item->name, BAD_CAST "item")) continue; struct share_user *new_user = xcalloc(1, sizeof(*new_user)); xml_parse_share_user(doc, item, new_user); list_add_tail(&new_user->list, users); } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } int xml_parse_share_getpubkeys(const char *buf, struct list_head *user_list) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) return -EINVAL; /* * XML fields are as follows: * xmlresponse * success * pubkey0 * uid0 * username0 * cgid0 (if group) */ xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "xmlresponse") || !root->children) { ret = -EINVAL; goto free_doc; } for (int count = 0; ; count++) { struct share_user *user = new0(struct share_user, 1); ret = xml_parse_share_key_entry(doc, root, user, count); if (ret) { free(user); break; } list_add(&user->list, user_list); } if (list_empty(user_list)) ret = -ENOENT; else ret = 0; free_doc: xmlFreeDoc(doc); return ret; } static int xml_parse_su_key_entry(xmlDoc *doc, xmlNode *parent, struct pwchange_su_key *su_key, int idx) { char *tmp; _cleanup_free_ char *pubkey = NULL; _cleanup_free_ char *uid = NULL; xasprintf(&pubkey, "sukey%d", idx); xasprintf(&uid, "suuid%d", idx); memset(su_key, 0, sizeof(*su_key)); for (xmlAttrPtr attr = parent->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST pubkey)) { tmp = (char *) xmlNodeListGetString(doc, attr->children, 1); int ret = hex_to_bytes(tmp, &su_key->sharing_key.key); if (ret == 0) su_key->sharing_key.len = strlen(tmp) / 2; free(tmp); continue; } if (!xmlStrcmp(attr->name, BAD_CAST uid)) { tmp = (char *) xmlNodeListGetString(doc, attr->children, 1); su_key->uid = tmp; continue; } } if (!su_key->sharing_key.len || !su_key->uid) { free(su_key->uid); free(su_key->sharing_key.key); return -ENOENT; } return 0; } static int xml_parse_pwchange_su_keys(xmlDoc *doc, xmlNode *parent, struct pwchange_info *info) { for (int count = 0; ; count++) { struct pwchange_su_key *su_key = new0(struct pwchange_su_key,1); int ret = xml_parse_su_key_entry(doc, parent, su_key, count); if (ret) { free(su_key); break; } list_add(&su_key->list, &info->su_keys); } return 0; } static int xml_parse_pwchange_data(char *data, struct pwchange_info *info) { char *token, *end; struct pwchange_field *field; /* * read the first two lines without strtok: in case there are * empty lines we don't want to skip them. */ #define next_line(x) { \ end = strchr(data, '\n'); \ if (!end) \ return -ENOENT; \ *end++ = 0; \ info->x = xstrdup(data); \ data = end; \ } next_line(reencrypt_id); next_line(privkey_encrypted); #undef next_line for (token = strtok(data, "\n"); token; token = strtok(NULL, "\n")) { if (!strncmp(token, "endmarker", 9)) break; field = new0(struct pwchange_field, 1); char *delim = strchr(token, '\t'); if (delim) { *delim = 0; field->optional = *(delim + 1) == '0'; } field->old_ctext = xstrdup(token); list_add_tail(&field->list, &info->fields); } return 0; } int xml_api_err(const char *buf) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "lastpass") || !root->children) { ret = -EINVAL; goto free_doc; } for (xmlAttrPtr attr = root->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "rc")) { _cleanup_free_ char *val = (char *) xmlNodeListGetString(doc, attr->children, 1); if (strcmp(val, "OK") != 0) { ret = -EPERM; goto free_doc; } } } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } int xml_parse_pwchange(const char *buf, struct pwchange_info *info) { int ret; xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); INIT_LIST_HEAD(&info->fields); INIT_LIST_HEAD(&info->su_keys); xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "lastpass") || !root->children) { ret = -EINVAL; goto free_doc; } for (xmlAttrPtr attr = root->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "rc")) { _cleanup_free_ char *val = (char *) xmlNodeListGetString(doc, attr->children, 1); if (strcmp(val, "OK") != 0) { ret = -EPERM; goto free_doc; } } } for (xmlNode *item = root->children; item; item = item->next) { if (xmlStrcmp(item->name, BAD_CAST "data")) continue; for (xmlAttrPtr attr = item->properties; attr; attr = attr->next) { if (!xmlStrcmp(attr->name, BAD_CAST "xml")) { _cleanup_free_ char *data = (char *) xmlNodeListGetString(doc, attr->children, 1); ret = xml_parse_pwchange_data(data, info); if (ret) goto free_doc; } if (!xmlStrcmp(attr->name, BAD_CAST "token")) info->token = (char *)xmlNodeListGetString(doc, attr->children, 1); } xml_parse_pwchange_su_keys(doc, item, info); } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } int xml_parse_share_getpubkey(const char *buf, struct share_user *user) { struct list_head users; struct share_user *share_user, *tmp; int ret; INIT_LIST_HEAD(&users); ret = xml_parse_share_getpubkeys(buf, &users); if (ret) return ret; if (list_empty(&users)) return -ENOENT; share_user = list_first_entry(&users, struct share_user, list); *user = *share_user; list_for_each_entry_safe(share_user, tmp, &users, list) free(share_user); return 0; } static void xml_parse_share_limit_aids(xmlDoc *doc, xmlNode *parent, struct list_head *list) { for (xmlNode *item = parent->children; item; item = item->next) { if (xmlStrncmp(item->name, BAD_CAST "aid", 3)) continue; struct share_limit_aid *aid = new0(struct share_limit_aid, 1); aid->aid = (char *) xmlNodeListGetString(doc, item->xmlChildrenNode, 1); list_add_tail(&aid->list, list); } } int xml_parse_share_get_limits(const char *buf, struct share_limit *limit) { int ret; memset(limit, 0, sizeof(*limit)); INIT_LIST_HEAD(&limit->aid_list); xmlDoc *doc = xmlReadMemory(buf, strlen(buf), NULL, NULL, 0); if (!doc) return -EINVAL; xmlNode *root = xmlDocGetRootElement(doc); if (!root || xmlStrcmp(root->name, BAD_CAST "xmlresponse") || !root->children) { ret = -EINVAL; goto free_doc; } for (xmlNode *item = root->children; item; item = item->next) { if (xml_parse_bool(doc, item, "hidebydefault", &limit->whitelist)) continue; if (!xmlStrcmp(item->name, BAD_CAST "aids")) { xml_parse_share_limit_aids(doc, item, &limit->aid_list); } } ret = 0; free_doc: xmlFreeDoc(doc); return ret; } lastpass-cli-1.0.0/contrib/0000755000175000017500000000000012743671271016005 5ustar troyhebetroyhebelastpass-cli-1.0.0/contrib/completions-lpass.fish0000644000175000017500000000760712743671271022346 0ustar troyhebetroyhebe# fish-shell completion for lastpass-cli # # for single-user installation, copy this file to # ~/.config/fish/completions/lpass.fish function __lpass_subcommands lpass --help \ | grep lpass \ | grep -v -- '--help' \ | sed -n 's/^ //p' \ | cut -f 2 -d" " end function __lpass_entries set -l id_re '\[id: ([0-9][0-9]*)\]$' set -l id_re_escaped '\[id: \([0-9][0-9]*\)\]$' for entry in (lpass ls --sync auto) if not begin; echo $entry | grep -e '. '$id_re_escaped > /dev/null; end; # Entry has no name set -l out (string replace -r '.* '$id_re '$1' $entry) echo $out else # Strip '(none)/' from output since it's printed by `lpass ls`, but # not accepted by `lpass show` set -l out (string replace -r '(\(none\)/)?(.*) '$id_re '$2' $entry) echo $out end end end function __lpass_needs_command set cmd (commandline -opc) if test (count $cmd) -eq 1 return 0 end return 1 end function __lpass_has_command if __lpass_needs_command return 1 else return 0 end end function __lpass_using_command set cmd (commandline -opc) if test (count $cmd) -gt 1 for arg in $argv if test "$arg" = "$cmd[2]" return 0 end end end return 1 end complete -f -c lpass -l help -d "Print usage" complete -f -c lpass -n '__lpass_needs_command' -a login -d 'Login to LastPass' complete -f -c lpass -n '__lpass_needs_command' -a show -d 'Show entry details' complete -f -c lpass -n '__lpass_needs_command' -a generate -d 'Generate password' complete -f -c lpass -n '__lpass_needs_command' -a add -d 'Add entry' complete -f -c lpass -n '__lpass_needs_command' -a edit -d 'Edit entry' complete -f -c lpass -n '__lpass_needs_command' -a sync -d 'Synchronize local cache with server' # {UNIQUENAME|UNIQUEID} complete -f -c lpass \ -n '__lpass_using_command show mv edit generate duplicate rm' \ -a '(__lpass_entries)' # --background complete -f -c lpass \ -n '__lpass_using_command sync' \ -s b -l background \ -d 'Synchronize in background' # --sync=SYNC complete -f -c lpass \ -n '__lpass_using_command show ls add edit generate dubplicate rm export' \ -r -l sync \ -d 'Synchronize local cache with server: auto | now | no' # --color=COLOR complete -f -c lpass \ -n '__lpass_using_command login logout show ls mv add edit duplicate rm sync export' \ -r -l color \ -d 'Color: auto | never | always' # --non-interactive complete -f -c lpass \ -n '__lpass_using_command add edit' \ -l non-interactive \ -d 'Use stardard input instead of $EDITOR' # --clip complete -f -c lpass -n '__lpass_using_command show generate' \ -s c -l clip \ -d 'Copy output to clipboard' # --expand-multi complete -f -c lpass -n '__lpass_using_command show' \ -s x -l expand-multi \ -d 'Expand multi' # --all complete -f -c lpass -n '__lpass_using_command show' \ -l all \ -d 'All fields' # --url complete -f -c lpass -n '__lpass_using_command show add edit' \ -l url \ -d 'URL' # --field=FIELD complete -f -c lpass -n '__lpass_using_command show add edit' \ -r -l field \ -d 'Custom field' # --name complete -f -c lpass -n '__lpass_using_command edit' \ -l name \ -d 'Name' # --notes complete -f -c lpass -n '__lpass_using_command show add edit' \ -l notes \ -d 'Notes' # --username complete -f -c lpass -n '__lpass_using_command show add edit' \ -l username \ -d 'Username' # --password complete -f -c lpass -n '__lpass_using_command show add edit' \ -l password \ -d 'Password' # --url=URL complete -f -c lpass -n '__lpass_using_command generate' \ -r -l url \ -d 'URL' # --username=USERNAME complete -f -c lpass -n '__lpass_using_command generate' \ -r -l username \ -d 'Username' lastpass-cli-1.0.0/contrib/specfile/0000755000175000017500000000000012743671271017577 5ustar troyhebetroyhebelastpass-cli-1.0.0/contrib/specfile/lastpass-cli.spec0000644000175000017500000000151312743671271023052 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-1.0.0/contrib/lpass_bash_completion0000644000175000017500000000676012743671271022311 0ustar troyhebetroyhebe__lpass_complete_name() { local cur=$1 local matches # matches on full path matches=$(lpass ls | egrep "^$cur" | awk '{print $1}') # matches on leaves matches+=$(lpass ls | egrep "/$cur" | sed -e "s/ \[id.*//g" | \ awk -F '/' '{print $NF}') local IFS=$'\n' COMPREPLY=($(compgen -W "$matches" "$cur")) if [[ ! -z $COMPREPLY ]]; then COMPREPLY=($(printf "%q\n" "${COMPREPLY[@]}")) fi } __lpass_complete_group() { local cur=$1 local matches matches=$(lpass ls | egrep "^$cur.*/" | awk -F '/' '{print $1}') local IFS=$'\n' COMPREPLY=($(compgen -W "$matches" "$cur")) if [[ ! -z $COMPREPLY ]]; then COMPREPLY=($(printf "%q\n" "${COMPREPLY[@]}")) fi } __lpass_complete_opt() { local cmd=$1 local cur=$2 opts="" case "$cmd" in login) opts="--trust --plaintext-key --force --color" ;; logout) opts="--force --color" ;; show) opts="--sync --clip --expand-multi --all --username --password --url --notes --field --id --name --basic-regexp --fixed-strings --color" ;; ls) opts="--sync --long --color" ;; mv|duplicate|rm|export) opts="--sync --color" ;; edit) opts="--sync --non-interactive --name --username --password --url --notes --field --color" ;; generate) opts="--sync --clip --username --url --no-symbols --color" ;; share) opts="--read_only --hidden --admin" esac COMPREPLY=($(compgen -W "$opts" -- $cur)) } _lpass() { local cur="${COMP_WORDS[COMP_CWORD]}" local cmd="${COMP_WORDS[1]}" local subcmd="${COMP_WORDS[2]}" local optind=1 for i in `seq 2 $COMP_CWORD`; do if [[ ${COMP_WORDS[COMP_CWORD]} != "-*" ]]; then optind=i break fi done local all_cmds=" login logout passwd show ls mv add edit generate duplicate rm sync export share " local share_cmds=" userls useradd usermod userdel create rm " # include aliases (although we can't really do much with them) for a in ~/.lpass/alias.*; do all_cmds="$all_cmds ${a#*alias.}" done # subcommands if [[ $COMP_CWORD -eq 1 ]]; then COMPREPLY=($(compgen -W "$all_cmds" $cur)) return # share subcommands elif [[ $COMP_CWORD -eq 2 && $cmd == "share" ]]; then COMPREPLY=($(compgen -W "$share_cmds" $cur)) return fi COMPREPLY=() case "$cur" in -*) __lpass_complete_opt $cmd $cur return ;; esac case "$cmd" in show|rm|edit|duplicate|generate) __lpass_complete_name $cur ;; mv) if [[ $COMP_CWORD -eq $optind ]]; then __lpass_complete_name $cur else __lpass_complete_group $cur fi ;; ls|add) __lpass_complete_group $cur ;; share) case "$subcmd" in userls|useradd|usermod|userdel|rm) if [[ $cur != "Shared-*" ]]; then cur="Shared-$cur" fi __lpass_complete_group $cur ;; create) ;; esac ;; *) ;; esac } complete -o default -F _lpass lpass lastpass-cli-1.0.0/contrib/examples/0000755000175000017500000000000012743671271017623 5ustar troyhebetroyhebelastpass-cli-1.0.0/contrib/examples/change-ssh-password.sh0000755000175000017500000000434312743671271024046 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-1.0.0/contrib/examples/lpass-sudo.sh0000755000175000017500000000027412743671271022257 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-1.0.0/contrib/examples/change-mysql-password.sh0000755000175000017500000000213412743671271024412 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-1.0.0/contrib/examples/msmtprc0000644000175000017500000000042512743671271021234 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-1.0.0/contrib/examples/git-credential-lastpass0000755000175000017500000000226312743671271024277 0ustar troyhebetroyhebe#!/bin/bash # A credential helper for git to retrieve usernames and passwords from lastpass. # For general usage, see https://git-scm.com/docs/gitcredentials. # Here's a quick version: # 1. Put this somewhere in your path. # 2. git config --global credential.helper lastpass declare -A params if [ "x$1" == "x-l" ]; then shift lpassuser=$1 shift fi if [ "x$1" == "xget" ]; then read line while [ -n "$line" ]; do key=${line%%=*} value=${line#*=} params[$key]=$value read line done if [ "x${params['protocol']}" != "xhttps" ]; then exit fi if [ -z "${params["host"]}" ]; then exit fi lpass ls > /dev/null 2>&1 if [ $? -ne 0 ]; then if [ -z "$lpassuser" ]; then read -p "Lastpass username: " lpassuser < /dev/tty > /dev/tty fi if [ -z "$lpassuser" ]; then exit fi lpass login $lpassuser > /dev/null if [ $? -ne 0 ]; then echo "Failed to login to lastpass" > /dev/stderr exit fi fi user=`lpass show --username ${params["host"]}` pass=`lpass show --password ${params["host"]}` if [ "x$user" == "x" ] || [ "x$pass" == "x" ]; then echo "Couldn't find host in lastpass DB." > /dev/stderr exit fi echo username=$user echo password=$pass fi lastpass-cli-1.0.0/contrib/examples/lpass-sudo-askpass.sh0000755000175000017500000000042112743671271023714 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-1.0.0/notes.c0000644000175000017500000001435012743671271015644 0ustar troyhebetroyhebe/* * routines for classifying secure notes * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include #include "notes.h" #include "util.h" /* Templates for shared note types */ struct note_template note_templates[] = { [ NOTE_TYPE_AMEX ] = { .name = "American Express", .shortname = "amex", .fields = { NULL }}, [ NOTE_TYPE_BANK ] = { .name = "Bank Account", .shortname = "bank", .fields = { "Bank Name", "Account Type", "Routing Number", "Account Number", "SWIFT Code", "IBAN Number", "Pin", "Branch Address", "Branch Phone", NULL }}, [ NOTE_TYPE_CREDIT ] = { .name = "Credit Card", .shortname = "credit-card", .fields = { "Name on Card", "Type", "Number", "Security Code", "Start Date", "Expiration Date", NULL }}, [ NOTE_TYPE_DATABASE ] = { .name = "Database", .shortname = "database", .fields = { "Type", "Hostname", "Port", "Database", "Username", "Password", "SID", "Alias", NULL }}, [ NOTE_TYPE_DRIVERS_LICENSE ] = { .name = "Driver's License", .shortname = "drivers-license", .fields = { "Number", "Expiration Date", "License Class", "Name", "Address", "City / Town", "State", "ZIP / Postal Code", "Country", "Date of Birth", "Sex", "Height", NULL }}, [ NOTE_TYPE_EMAIL ] = { .name = "Email Account", .shortname = "email", .fields = { "Username", "Password", "Server", "Port", "Type", "SMTP Server", "SMTP Port", NULL }}, [ NOTE_TYPE_HEALTH_INSURANCE ] = { .name = "Health Insurance", .shortname = "health-insurance", .fields = { "Company", "Company Phone", "Policy Type", "Policy Number", "Group ID", "Member Name", "Member ID", "Physician Name", "Physician Phone", "Physician Address", "Co-pay", NULL }}, [ NOTE_TYPE_IM ] = { .name = "Instant Messenger", .shortname = "im", .fields = { "Type", "Username", "Password", "Server", "Port", NULL }}, [ NOTE_TYPE_INSURANCE ] = { .name = "Insurance", .shortname = "insurance", .fields = { "Company", "Policy Type", "Policy Number", "Expiration", "Agent Name", "Agent Phone", "URL", NULL }}, [ NOTE_TYPE_MASTERCARD ] = { .name = "Mastercard", .shortname = "mastercard", .fields = { NULL }}, [ NOTE_TYPE_MEMBERSHIP ] = { .name = "Membership", .shortname = "membership", .fields = { "Organization", "Membership Number", "Member Name", "Start Date", "Expiration Date", "Website", "Telephone", "Password", NULL }}, [ NOTE_TYPE_PASSPORT ] = { .name = "Passport", .shortname = "passport", .fields = { "Type", "Name", "Country", "Number", "Sex", "Nationality", "Date of Birth", "Issued Date", "Expiration Date", NULL }}, [ NOTE_TYPE_SERVER ] = { .name = "Server", .shortname = "server", .fields = { "Hostname", "Username", "Password", NULL }}, [ NOTE_TYPE_SSN ] = { .name = "Social Security", .shortname = "ssn", .fields = { "Name", "Number", NULL }}, [ NOTE_TYPE_SOFTWARE_LICENSE ] = { .name = "Software License", .shortname = "software-license", .fields = { "License Key", "Licensee", "Version", "Publisher", "Support Email", "Website", "Price", "Purchase Date", "Order Number", "Number of Licenses", "Order Total", NULL }}, [ NOTE_TYPE_SSH_KEY ] = { .name = "SSH Key", .shortname = "ssh-key", .fields = { "Bit Strength", "Format", "Passphrase", "Private Key", "Public Key", "Hostname", "Date", NULL }}, [ NOTE_TYPE_VISA ] = { .name = "VISA", .shortname = "visa", .fields = { NULL }}, [ NOTE_TYPE_WIFI ] = { .name = "Wi-Fi Password", .shortname = "wifi", .fields = { "SSID", "Password", "Connection Type", "Connection Mode", "Authentication", "Encryption", "Use 802.1X", "FIPS Mode", "Key Type", "Protected", "Key Index", NULL }}, }; const char *notes_get_name(enum note_type note_type) { if (note_type <= NOTE_TYPE_NONE || note_type >= NUM_NOTE_TYPES) return ""; return note_templates[note_type].name; } enum note_type notes_get_type_by_shortname(const char *type_str) { BUILD_BUG_ON(ARRAY_SIZE(note_templates) != NUM_NOTE_TYPES); size_t i; for (i = 0; i < NUM_NOTE_TYPES; i++) { if (!strcasecmp(type_str, note_templates[i].shortname)) return i; } return NOTE_TYPE_NONE; } enum note_type notes_get_type_by_name(const char *type_str) { size_t i; for (i = 0; i < NUM_NOTE_TYPES; i++) { if (!strcasecmp(type_str, note_templates[i].name)) return i; } return NOTE_TYPE_NONE; } char *note_type_usage() { int i; char *start = "--note-type=TYPE\n\nValid values for TYPE:\n"; size_t alloc_len = strlen(start) + 1; char *usage_str; for (i = 0; i < NUM_NOTE_TYPES; i++) alloc_len += strlen(note_templates[i].shortname) + 2; usage_str = xcalloc(1, alloc_len); strlcat(usage_str, start, alloc_len); for (i = 0; i < NUM_NOTE_TYPES; i++) { strlcat(usage_str, "\t", alloc_len); strlcat(usage_str, note_templates[i].shortname, alloc_len); if (i != NUM_NOTE_TYPES - 1) strlcat(usage_str, "\n", alloc_len); } return usage_str; } lastpass-cli-1.0.0/util.h0000644000175000017500000000677312743671271015510 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)) #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) void warn(const char *err, ...) _printf_(1, 2); void warn_errno(const char *err, ...) _printf_(1, 2); _noreturn_ void die(const char *err, ...) _printf_(1, 2); _noreturn_ void die_errno(const char *err, ...) _printf_(1, 2); _noreturn_ void die_usage(const char *usage); bool ask_yes_no(bool default_yes, const char *prompt, ...); void *xmalloc(size_t size); void *xcalloc(size_t nmemb, size_t size); void *xrealloc(void *ptr, size_t size); void *reallocarray(void *ptr, size_t nmemb, size_t size); void *xreallocarray(void *ptr, size_t nmemb, size_t size); void *xstrdup(const char *str); void *xstrndup(const char *str, size_t maxlen); int xasprintf(char **strp, const char *fmt, ...) _printf_(2, 3); int xvasprintf(char **strp, const char *fmt, va_list ap); #ifdef __GLIBC__ size_t strlcpy(char *dst, const char *src, size_t dstsize); size_t strlcat(char *dst, const char *src, size_t dstsize); #else char *strchrnul(const char *s, int c); #endif void strlower(char *str); void strupper(char *str); char *xstrlower(const char *str); char *xstrupper(const char *str); char *xultostr(unsigned long num); void xstrappend(char **str, const char *suffix); void xstrappendf(char **str, const char *suffixfmt, ...) _printf_(2, 3); void xstrprepend(char **str, const char *suffix); void xstrprependf(char **str, const char *suffixfmt, ...) _printf_(2, 3); bool starts_with(const char *str, const char *start); bool ends_with(const char *str, const char *end); char *trim(char *str); void bytes_to_hex(const unsigned char *bytes, char **hex, size_t len); int hex_to_bytes(const char *hex, unsigned char **bytes); void secure_clear(void *ptr, size_t len); void secure_clear_str(char *str); void *secure_resize(void *ptr, size_t oldlen, size_t newlen); /* [min, max) */ unsigned long range_rand(unsigned long min, unsigned long max); void get_random_bytes(unsigned char *buf, size_t len); const char *bool_str(bool val); #endif lastpass-cli-1.0.0/edit.c0000644000175000017500000003445512743671271015451 0ustar troyhebetroyhebe/* * common routines for editing / adding accounts * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "endpoints.h" #include "blob.h" #include "agent.h" #include #include #include #include #include "util.h" #if defined(__linux__) || defined(__CYGWIN__) static char *shared_memory_dir(void) { return xstrdup("/dev/shm"); } #elif defined(__APPLE__) && defined(__MACH__) static const char *shared_memory_dir_mount = "dir=\"$(mktemp -d \"${TMPDIR:-/tmp}/lpass.XXXXXXXXXXXXX\")\"\n" "dev=\"$(hdid -drivekey system-image=yes -nomount 'ram://32768' | cut -d ' ' -f 1)\"\n" "[[ -z $dev ]] && exit 1\n" "newfs_hfs -M 700 \"$dev\" >/dev/null 2>&1 || exit 1\n" "mount -t hfs -o noatime -o nobrowse \"$dev\" \"$dir\" || exit 1\n" "echo \"$dev\"\necho \"$dir\"\n"; static const char *shared_memory_dir_unmount = "umount \"$SECURE_TMPDIR\"\n" "diskutil quiet eject \"$RAMDISK_DEV\"\n" "rm -rf \"$SECURE_TMPDIR\"\n"; static void shared_memory_dir_eject(void) { system(shared_memory_dir_unmount); } static char *shared_memory_dir(void) { char *stored = getenv("SECURE_TMPDIR"); if (stored) return xstrdup(stored); _cleanup_free_ char *dev = NULL; char *dir = NULL; size_t len; FILE *script = popen(shared_memory_dir_mount, "r"); if (!script) return NULL; len = 0; if (getline(&dev, &len, script) <= 0) { pclose(script); return NULL; } trim(dev); len = 0; if (getline(&dir, &len, script) <= 0) { pclose(script); return NULL; } trim(dir); setenv("SECURE_TMPDIR", dir, true); setenv("RAMDISK_DEV", dev, true); atexit(shared_memory_dir_eject); return dir; } #else static char *shared_memory_dir(void) { char *tmpdir = getenv("SECURE_TMPDIR"); if (!tmpdir) { if (!(tmpdir = getenv("TMPDIR"))) tmpdir = "/tmp"; fprintf(stderr, "Warning: Using %s as secure temporary directory.\n" "Recommend using tmpfs and encrypted swap.\n" "Set SECURE_TMPDIR environment variable to override.\n", tmpdir); sleep(5); } return xstrdup(tmpdir); } #endif _noreturn_ static inline void die_unlink_errno(const char *str, const char *file, const char *dir) { int saved = errno; if (file) unlink(file); if (dir) rmdir(dir); errno = saved; die_errno("%s", str); } static void assign_account_value(struct account *account, const char *label, char *value, unsigned char key[KDF_HASH_LEN]) { struct field *editable_field = NULL; #define assign_if(title, field) do { \ if (!strcmp(label, title)) { \ account_set_##field(account, value, key); \ return; \ } \ } while (0) value = xstrdup(trim(value)); assign_if("Name", fullname); assign_if("URL", url); assign_if("Username", username); assign_if("Password", password); assign_if("Application", appname); /* if we got here maybe it's a secure note field */ list_for_each_entry(editable_field, &account->field_head, list) { if (!strcmp(label, editable_field->name)) { field_set_value(account, editable_field, value, key); return; } } /* Some other name: value pair -- treat like a new field */ editable_field = new0(struct field, 1); editable_field->name = xstrdup(label); editable_field->type = xstrdup("password"); field_set_value(account, editable_field, value, key); list_add_tail(&editable_field->list, &account->field_head); #undef assign_if } static int read_file_buf(FILE *fp, char **value_out, size_t *len_out) { size_t len; size_t read; char *value; *len_out = 0; *value_out = NULL; for (len = 0, value = xmalloc(8192 + 1); ; value = xrealloc(value, len + 8192 + 1)) { read = fread(value + len, 1, 8192, fp); len += read; if (read != 8192) { if (ferror(fp)) return -EIO; break; } } value[len] = '\0'; *value_out = value; *len_out = len; return 0; } /* * Read a file representing all of the data in an account. * We generate this file when editing an account, and parse it back * after a user has edited it. Each line, with the exception of the * final "notes" label, is parsed from the end of the label to the * first newline. In the case of notes, the rest of the file is considered * part of the note. * * Name: text0 * URL: text1 * [...] * Notes: * notes text here * */ static void parse_account_file(FILE *input, struct account *account, unsigned char key[KDF_HASH_LEN]) { _cleanup_free_ char *line = NULL; ssize_t read; size_t len = 0; char *label, *delim, *value; bool parsing_notes = false; int ret; /* parse label: [value] */ while ((read = getline(&line, &len, input)) != -1) { delim = strchr(line, ':'); if (!delim) continue; *delim = 0; value = delim + 1; label = line; if (!strcmp(label, "Notes")) { parsing_notes = true; break; } assign_account_value(account, label, value, key); } if (!parsing_notes) return; /* everything else goes into notes section */ value = NULL; len = 0; ret = read_file_buf(input, &value, &len); if (ret) return; account_set_note(account, value, key); } static struct field *add_default_field(struct account *account, const char *field_name, unsigned char key[KDF_HASH_LEN]) { struct field *editable_field = NULL; bool found = false; list_for_each_entry(editable_field, &account->field_head, list) { if (!strcmp(editable_field->name, field_name)) { found = true; break; } } if (found) return editable_field; editable_field = new0(struct field, 1); editable_field->type = xstrdup("text"); editable_field->name = xstrdup(field_name); field_set_value(account, editable_field, xstrdup(""), key); list_add_tail(&editable_field->list, &account->field_head); return editable_field; } static void add_default_fields(struct account *account, enum note_type note_type, unsigned char key[KDF_HASH_LEN]) { int i; struct note_template *tmpl; if (note_type <= NOTE_TYPE_NONE || note_type >= NUM_NOTE_TYPES) return; /* * Add a new, empty field for any label in the template which * does not already exist in the account. */ tmpl = ¬e_templates[note_type]; for (i=0; tmpl->fields[i]; i++) add_default_field(account, tmpl->fields[i], key); } enum note_type get_note_type(struct account *account) { struct field *editable_field; list_for_each_entry(editable_field, &account->field_head, list) { if (!strcmp(editable_field->name, "NoteType")) { return notes_get_type_by_name(editable_field->value); } } return NOTE_TYPE_NONE; } static int write_account_file(FILE *fp, struct account *account, unsigned char key[KDF_HASH_LEN]) { struct field *editable_field = NULL; enum note_type note_type; #define write_field(title, field) do { \ if (fprintf(fp, "%s: %s\n", title, field) < 0) \ return -errno; \ } while (0) write_field("Name", account->fullname); note_type = get_note_type(account); if (account->is_app) { struct app *app = account_to_app(account); write_field("Application", app->appname); } else if (note_type != NOTE_TYPE_NONE) { add_default_fields(account, note_type, key); } else { write_field("URL", account->url); write_field("Username", account->username); write_field("Password", account->password); } list_for_each_entry(editable_field, &account->field_head, list) { write_field(editable_field->name, editable_field->value); } if (fprintf(fp, "Notes: # Add notes below this line.\n%s", account->note) < 0) return -errno; return 0; #undef write_field } int edit_account(struct session *session, struct blob *blob, enum blobsync sync, struct account *editable, enum edit_choice choice, const char *field, bool non_interactive, unsigned char key[KDF_HASH_LEN]) { size_t len; struct account *notes_expansion, *notes_collapsed = NULL; struct field *editable_field = NULL; _cleanup_free_ char *tmppath = NULL; _cleanup_free_ char *tmpdir = NULL; _cleanup_free_ char *editcmd = NULL; int tmpfd; FILE *tmpfile; char *value; int ret; struct share *old_share = editable->share; notes_expansion = notes_expand(editable); if (notes_expansion) { notes_collapsed = editable; editable = notes_expansion; } else if (choice == EDIT_FIELD) die("Editing fields of entries that are not secure notes is currently not supported."); switch(choice) { case EDIT_USERNAME: value = editable->username; break; case EDIT_PASSWORD: value = editable->password; break; case EDIT_URL: value = editable->url; break; case EDIT_NAME: value = editable->fullname; break; case EDIT_FIELD: editable_field = add_default_field(editable, field, key); value = editable_field->value; break; case EDIT_NOTES: value = editable->note; break; default: value = NULL; } if (!non_interactive) { if (editable->pwprotect) { unsigned char pwprotect_key[KDF_HASH_LEN]; if (!agent_load_key(pwprotect_key)) die("Could not authenticate for protected entry."); if (memcmp(pwprotect_key, key, KDF_HASH_LEN)) die("Current key is not on-disk key."); } if (strcmp(editable->id, "0")) lastpass_log_access(sync, session, key, editable); tmpdir = shared_memory_dir(); xstrappend(&tmpdir, "/lpass.XXXXXX"); if (!mkdtemp(tmpdir)) die_errno("mkdtemp"); xasprintf(&tmppath, "%s/lpass.XXXXXX", tmpdir); tmpfd = mkstemp(tmppath); if (tmpfd < 0) die_unlink_errno("mkstemp", tmppath, tmpdir); tmpfile = fdopen(tmpfd, "w"); if (!tmpfile) die_unlink_errno("fdopen", tmppath, tmpdir); if (choice == EDIT_ANY) { if (write_account_file(tmpfile, editable, key)) die_unlink_errno("fprintf", tmppath, tmpdir); } else { if (fprintf(tmpfile, "%s\n", value) < 0) die_unlink_errno("fprintf", tmppath, tmpdir); } fclose(tmpfile); xasprintf(&editcmd, "${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 == EDIT_NOTES) { ret = read_file_buf(tmpfile, &value, &len); if (ret) die_unlink_errno("fread(tmpfile)", tmppath, tmpdir); } else if (choice == EDIT_ANY) { parse_account_file(tmpfile, editable, key); value = NULL; } else { value = NULL; len = 0; if (getline(&value, &len, tmpfile) < 0) die_unlink_errno("getline", tmppath, tmpdir); } fclose(tmpfile); if (value) { len = strlen(value); if (len && value[len - 1] == '\n') value[len - 1] = '\0'; } if (tmppath) { unlink(tmppath); rmdir(tmpdir); } if (choice == EDIT_USERNAME) account_set_username(editable, value, key); else if (choice == EDIT_PASSWORD) account_set_password(editable, value, key); else if (choice == EDIT_URL) account_set_url(editable, value, key); else if (choice == EDIT_NAME) account_set_fullname(editable, value, key); else if (choice == EDIT_NOTES) account_set_note(editable, value, key); else if (choice == EDIT_FIELD) { if (!value || !strlen(value)) { list_del(&editable_field->list); field_free(editable_field); } else field_set_value(editable, editable_field, value, key); } if (notes_expansion && notes_collapsed) { editable = notes_collapsed; notes_collapsed = notes_collapse(notes_expansion); account_free(notes_expansion); account_set_note(editable, xstrdup(notes_collapsed->note), key); if (choice == EDIT_NAME) account_set_fullname(editable, xstrdup(notes_collapsed->fullname), key); account_free(notes_collapsed); } account_assign_share(blob, editable, key); if (old_share != editable->share) { die("Use lpass mv to move items to/from shared folders"); } lastpass_update_account(sync, key, session, editable, blob); blob_save(blob, key); session_free(session); blob_free(blob); return 0; } int edit_new_account(struct session *session, struct blob *blob, enum blobsync sync, const char *name, enum edit_choice choice, const char *field, bool non_interactive, bool is_app, enum note_type note_type, unsigned char key[KDF_HASH_LEN]) { struct app *app; struct account *account; if (note_type != NOTE_TYPE_NONE && choice != EDIT_NOTES && choice != EDIT_ANY) { die("Note type may only be used with secure notes"); } if (is_app) { app = new_app(); account = &app->account; } else { account = new_account(); } account->id = xstrdup("0"); account_set_password(account, xstrdup(""), key); account_set_fullname(account, xstrdup(name), key); account_set_username(account, xstrdup(""), key); account_set_note(account, xstrdup(""), key); if (choice == EDIT_NOTES || note_type != NOTE_TYPE_NONE) { account->url = xstrdup("http://sn"); } else { account->url = xstrdup(""); } account_assign_share(blob, account, key); list_add(&account->list, &blob->account_head); if (note_type != NOTE_TYPE_NONE) { char *note_type_str = NULL; xasprintf(¬e_type_str, "NoteType:%s\n", notes_get_name(note_type)); account_set_note(account, note_type_str, key); } return edit_account(session, blob, sync, account, choice, field, non_interactive, key); } lastpass-cli-1.0.0/config.h0000644000175000017500000000217012743671271015763 0ustar troyhebetroyhebe#ifndef CONFIG_H #define CONFIG_H #include "kdf.h" #include #include #include enum config_type { CONFIG_DATA, CONFIG_CONFIG, CONFIG_RUNTIME, }; char *config_path(const char *name); FILE *config_fopen(const char *name, const char *mode); bool config_exists(const char *name); bool config_unlink(const char *name); time_t config_mtime(const char *name); void config_touch(const char *name); void config_write_string(const char *name, const char *string); void config_write_buffer(const char *name, const char *buffer, size_t len); char *config_read_string(const char *name); size_t config_read_buffer(const char *name, unsigned char **buffer); void config_write_encrypted_string(const char *name, const char *string, unsigned const char key[KDF_HASH_LEN]); void config_write_encrypted_buffer(const char *name, const char *buffer, size_t len, unsigned const char key[KDF_HASH_LEN]); char *config_read_encrypted_string(const char *name, unsigned const char key[KDF_HASH_LEN]); size_t config_read_encrypted_buffer(const char *name, unsigned char **buffer, unsigned const char key[KDF_HASH_LEN]); #endif lastpass-cli-1.0.0/upload-queue.c0000644000175000017500000002500512743671271017121 0ustar troyhebetroyhebe/* * queue for changes uploaded to LastPass * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "upload-queue.h" #include "session.h" #include "http.h" #include "util.h" #include "config.h" #include "kdf.h" #include "log.h" #include "process.h" #include "password.h" #include "endpoints.h" #include #include #include #include #include #include #include #include #include #include #include #include /* keep around failed updates for a couple of weeks */ #define FAIL_MAX_AGE 86400 * 14 static void make_upload_dir(const char *path) { _cleanup_free_ char *base_path = NULL; struct stat sbuf; int ret; base_path = config_path(path); ret = stat(base_path, &sbuf); if ((ret == -1 && errno == ENOENT) || !S_ISDIR(sbuf.st_mode)) { unlink(base_path); if (mkdir(base_path, 0700) < 0) die_errno("mkdir(%s)", base_path); } else if (ret == -1) die_errno("stat(%s)", base_path); } static void upload_queue_write_entry(const char *entry, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *name = NULL; unsigned long serial; make_upload_dir("upload-queue"); for (serial = 0; serial < ULONG_MAX; ++serial) { free(name); xasprintf(&name, "upload-queue/%lu%04lu", time(NULL), serial); if (!config_exists(name)) break; } if (serial == ULONG_MAX) die("No more upload queue entry slots available."); config_write_encrypted_string(name, entry, key); } static void upload_queue_cleanup_failures() { _cleanup_free_ char *base_path = config_path("upload-fail"); DIR *dir = opendir(base_path); struct dirent *entry; char *p; struct stat sbuf; int ret; if (!dir) return; while ((entry = readdir(dir))) { _cleanup_free_ char *fn = NULL; if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN) continue; for (p = entry->d_name; *p; ++p) { if (!isdigit(*p)) break; } if (*p) continue; xasprintf(&fn, "%s/%s", base_path, entry->d_name); ret = stat(fn, &sbuf); if (ret) continue; if ((time(NULL) - sbuf.st_mtime) > FAIL_MAX_AGE) { unlink(fn); } } closedir(dir); } static void upload_queue_drop(const char *name) { _cleanup_free_ char *newname = NULL; _cleanup_free_ char *old_full = NULL; _cleanup_free_ char *new_full = NULL; char *basename; int ret; lpass_log(LOG_DEBUG, "UQ: dropping %s\n", name); make_upload_dir("upload-fail"); basename = strrchr(name, '/'); if (!basename) { unlink(name); return; } basename += 1; xasprintf(&newname, "upload-fail/%s", basename); old_full = config_path(name); new_full = config_path(newname); ret = rename(old_full, new_full); lpass_log(LOG_DEBUG, "UQ: rename returned %d (errno=%d)\n", ret, errno); upload_queue_cleanup_failures(); } static char *upload_queue_next_entry(unsigned const char key[KDF_HASH_LEN], char **name, char **lock) { unsigned long long smallest = ULLONG_MAX, current; _cleanup_free_ char *smallest_name = NULL; _cleanup_free_ char *base_path = config_path("upload-queue"); _cleanup_free_ char *pidstr = NULL; pid_t pid; char *result, *p; DIR *dir = opendir(base_path); struct dirent *entry; if (!dir) return NULL; while ((entry = readdir(dir))) { if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN) continue; for (p = entry->d_name; *p; ++p) { if (!isdigit(*p)) break; } if (*p) continue; current = strtoull(entry->d_name, NULL, 10); if (!current) continue; if (current < smallest) { smallest = current; free(smallest_name); smallest_name = xstrdup(entry->d_name); } } closedir(dir); if (smallest == ULLONG_MAX) return NULL; xasprintf(name, "upload-queue/%s", smallest_name); xasprintf(lock, "%s.lock", *name); while (config_exists(*lock)) { free(pidstr); pidstr = config_read_encrypted_string(*lock, key); if (!pidstr) { config_unlink(*lock); break; } pid = strtoul(pidstr, NULL, 10); if (!pid) { config_unlink(*lock); break; } if (process_is_same_executable(pid)) sleep(1); else { config_unlink(*lock); break; } } free(pidstr); pidstr = xultostr(getpid()); config_write_encrypted_string(*lock, pidstr, key); result = config_read_encrypted_string(*name, key); if (!result) { /* could not decrypt: drop this file */ lpass_log(LOG_DEBUG, "UQ: unable to decrypt job %s\n", *name); upload_queue_drop(*name); config_unlink(*lock); return NULL; } return result; } static void upload_queue_cleanup(int signal) { UNUSED(signal); config_unlink("uploader.pid"); _exit(EXIT_SUCCESS); } static void upload_queue_upload_all(const struct session *session, unsigned const char key[KDF_HASH_LEN]) { char *entry, *next_entry, *result; int size; char **argv = NULL; char **argv_ptr; char *name, *lock, *p; bool do_break; bool should_fetch_new_blob_after = false; int curl_ret; long http_code; bool http_failed_all; int backoff; while ((entry = upload_queue_next_entry(key, &name, &lock))) { lpass_log(LOG_DEBUG, "UQ: processing job %s\n", name); size = 0; for (p = entry; *p; ++p) { if (*p == '\n') ++size; } if (p > entry && p[-1] != '\n') ++size; if (size < 1) { config_unlink(name); config_unlink(lock); goto end; } argv_ptr = argv = xcalloc(size + 1, sizeof(char **)); for (do_break = false, p = entry, next_entry = entry; ; ++p) { if (!*p) do_break = true; if (*p == '\n' || !*p) { *p = '\0'; *(argv_ptr++) = pinentry_unescape(next_entry); next_entry = p + 1; if (do_break) break; } } argv[size] = NULL; http_failed_all = true; backoff = 1; for (int i = 0; i < 5; ++i) { if (i) { lpass_log(LOG_DEBUG, "UQ: attempt %d, sleeping %d seconds\n", i+1, backoff); sleep(backoff); backoff *= 8; } lpass_log(LOG_DEBUG, "UQ: posting to %s\n", argv[0]); result = http_post_lastpass_v_noexit(session->server, argv[0], session, NULL, &argv[1], &curl_ret, &http_code); http_failed_all &= (curl_ret == HTTP_ERROR_CODE || curl_ret == HTTP_ERROR_CONNECT); lpass_log(LOG_DEBUG, "UQ: result %d (http_code=%ld)\n", curl_ret, http_code); if (result && strlen(result)) should_fetch_new_blob_after = true; free(result); if (result) break; } if (!result) { lpass_log(LOG_DEBUG, "UQ: failed, http_failed_all: %d\n", http_failed_all); /* server failed response 5 times, remove it */ if (http_failed_all) upload_queue_drop(name); config_unlink(lock); } else { lpass_log(LOG_DEBUG, "UQ: succeeded\n"); config_unlink(name); config_unlink(lock); } for (argv_ptr = argv; *argv_ptr; ++argv_ptr) free(*argv_ptr); free(argv); end: free(name); free(lock); free(entry); } if (should_fetch_new_blob_after) blob_free(lastpass_get_blob(session, key)); } static void upload_queue_run(const struct session *session, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *pid = NULL; upload_queue_kill(); pid_t child = fork(); if (child < 0) die_errno("fork(agent)"); if (child == 0) { int null = open("/dev/null", 0); int upload_log = null; if (null >= 0) { dup2(null, 0); dup2(null, 1); dup2(null, 2); close(null); close(upload_log); } setsid(); IGNORE_RESULT(chdir("/")); process_set_name("lpass [upload queue]"); signal(SIGHUP, upload_queue_cleanup); signal(SIGINT, upload_queue_cleanup); signal(SIGQUIT, upload_queue_cleanup); signal(SIGTERM, upload_queue_cleanup); signal(SIGALRM, upload_queue_cleanup); setvbuf(stdout, NULL, _IOLBF, 0); if (http_init()) { lpass_log(LOG_ERROR, "UQ: unable to restart curl\n"); _exit(EXIT_FAILURE); } lpass_log(LOG_DEBUG, "UQ: starting queue run\n"); upload_queue_upload_all(session, key); lpass_log(LOG_DEBUG, "UQ: queue run complete\n"); upload_queue_cleanup(0); _exit(EXIT_SUCCESS); } pid = xultostr(child); config_write_string("uploader.pid", pid); } void upload_queue_kill(void) { _cleanup_free_ char *pidstr = NULL; pid_t pid; pidstr = config_read_string("uploader.pid"); if (!pidstr) return; pid = strtoul(pidstr, NULL, 10); if (!pid) return; kill(pid, SIGTERM); } bool upload_queue_is_running(void) { _cleanup_free_ char *pidstr = NULL; pid_t pid; pidstr = config_read_string("uploader.pid"); if (!pidstr) return false; pid = strtoul(pidstr, NULL, 10); if (!pid) return false; return process_is_same_executable(pid); } void upload_queue_enqueue(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const char *page, struct http_param_set *params) { _cleanup_free_ char *sum = xstrdup(page); char *next = NULL; char *escaped = NULL; char *param; char **argv = params->argv; while ((param = *argv++)) { escaped = pinentry_escape(param); xasprintf(&next, "%s\n%s", sum, escaped); free(escaped); free(sum); sum = next; } upload_queue_write_entry(sum, key); if (sync != BLOB_SYNC_NO) upload_queue_ensure_running(key, session); } void upload_queue_ensure_running(unsigned const char key[KDF_HASH_LEN], const struct session *session) { if (!upload_queue_is_running()) upload_queue_run(session, key); } lastpass-cli-1.0.0/cmd-status.c0000644000175000017500000000543012743671271016577 0ustar troyhebetroyhebe/* * command to get the status of the LastPass agent * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "agent.h" #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "upload-queue.h" #include #include #include #include int cmd_status(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; static struct option long_options[] = { {"quiet", no_argument, NULL, 'q'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; bool quiet = false; _cleanup_free_ char *username = NULL; while ((option = getopt_long(argc, argv, "q", long_options, &option_index)) != -1) { switch (option) { case 'q': quiet = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_status_usage); } } if (!agent_ask(key)) { if(!quiet) { terminal_printf(TERMINAL_FG_RED TERMINAL_BOLD "Not logged in" TERMINAL_RESET ".\n"); } return 1; } else { if(!quiet) { username = config_read_string("username"); terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "Logged in" TERMINAL_RESET " as " TERMINAL_UNDERLINE "%s" TERMINAL_RESET ".\n", username); } return 0; } } lastpass-cli-1.0.0/lpass.c0000644000175000017500000001165312743671271015641 0ustar troyhebetroyhebe/* * lpass - lastpass command line utility * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "process.h" #include "cmd.h" #include "string.h" #include "util.h" #include "http.h" #include "config.h" #include "terminal.h" #include "version.h" #include "log.h" #include #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(passwd), CMD(show), CMD(ls), CMD(mv), CMD(add), CMD(edit), CMD(generate), CMD(duplicate), CMD(rm), CMD(status), 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); if (http_init()) die("Unable to initialize curl"); load_saved_environment(); if (argc >= 2 && argv[1][0] != '-') return process_command(argc - 1, argv + 1); return global_options(argc, argv); } lastpass-cli-1.0.0/cmd.c0000644000175000017500000001644712743671271015270 0ustar troyhebetroyhebe/* * general utility functions used by multiple commands * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "agent.h" #include "blob.h" #include "session.h" #include "util.h" #include "process.h" #include #include #include enum blobsync parse_sync_string(const char *syncstr) { if (!syncstr || !strcasecmp(syncstr, "auto")) return BLOB_SYNC_AUTO; else if (!strcasecmp(syncstr, "now")) return BLOB_SYNC_YES; else if (!strcasecmp(syncstr, "no")) return BLOB_SYNC_NO; else die_usage("... --sync=auto|now|no"); } enum color_mode parse_color_mode_string(const char *colormode) { if (!colormode || strcmp(colormode, "auto") == 0) return COLOR_MODE_AUTO; else if (strcmp(colormode, "never") == 0) return COLOR_MODE_NEVER; else if (strcmp(colormode, "always") == 0) return COLOR_MODE_ALWAYS; else die_usage("... --color=auto|never|always"); } bool parse_bool_arg_string(const char *extra) { return !extra || strcmp(extra, "true") == 0; } enum note_type parse_note_type_string(const char *extra) { enum note_type result; result = notes_get_type_by_shortname(extra); if (result == NOTE_TYPE_NONE) { _cleanup_free_ char *params = NULL; _cleanup_free_ char *usage = NULL; params = note_type_usage(); xasprintf(&usage, "... %s", params); die_usage(usage); } return result; } void init_all(enum blobsync sync, unsigned char key[KDF_HASH_LEN], struct session **session, struct blob **blob) { if (!agent_get_decryption_key(key)) die("Could not find decryption key. Perhaps you need to login with `%s login`.", ARGV[0]); *session = 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; } /* * Search accounts with a given comparator. * * Any matched account is removed from the accounts list, and added to * ret_list. * * Note, the account list is iterated through match_list, so the caller * must first create a list of possible matches (from blob->account_head). * This is done instead of searching blob->account_head directly to enable * multiple searches of the potential match set. */ static void search_accounts(struct list_head *accounts, const void *needle, int (*cmp)(const char *haystack, const char *needle), int fields, struct list_head *ret_list) { struct account *account, *tmp; list_for_each_entry_safe(account, tmp, accounts, match_list) { if (((fields & ACCOUNT_ID) && cmp(account->id, needle) == 0) || ((fields & ACCOUNT_NAME) && cmp(account->name, needle) == 0) || ((fields & ACCOUNT_FULLNAME) && cmp(account->fullname, needle) == 0) || ((fields & ACCOUNT_URL) && cmp(account->url, needle) == 0) || ((fields & ACCOUNT_USERNAME) && cmp(account->username, needle) == 0)) { list_del(&account->match_list); list_add_tail(&account->match_list, ret_list); } } } /* * Search accounts on given fields, returning results into ret_list. * * @pattern - a basic regular expression * @fields - which fields to search on */ void find_matching_regex(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list) { regex_t regex; if (regcomp(®ex, pattern, REG_ICASE)) die("Invalid regex '%s'", pattern); search_accounts(accounts, ®ex, cmp_regex, fields, ret_list); regfree(®ex); } /* * Search accounts on name, username, and url fields, adding all matches * into ret_list. * * @pattern - a basic regular expression * @fields - which fields to search on */ void find_matching_substr(struct list_head *accounts, const char *pattern, int fields, struct list_head *ret_list) { search_accounts(accounts, pattern, cmp_substr, fields, ret_list); } /* * Search list of accounts for any and all accounts matching a given name. * Matching accounts are appended to ret_list which should be initialized * by the caller. * * In the case of an id match, we return only the matching id entry. */ void find_matching_accounts(struct list_head *accounts, const char *name, struct list_head *ret_list) { /* look for exact id match */ struct account *account; list_for_each_entry(account, accounts, match_list) { if (strcmp(name, "0") && !strcasecmp(account->id, name)) { list_del(&account->match_list); list_add_tail(&account->match_list, ret_list); /* if id match, stop processing */ return; } } /* search for fullname or name match */ search_accounts(accounts, name, strcmp, ACCOUNT_NAME | ACCOUNT_FULLNAME, ret_list); } struct account *find_unique_account(struct blob *blob, const char *name) { struct list_head matches; struct list_head potential_set; struct account *account, *last_account; INIT_LIST_HEAD(&matches); INIT_LIST_HEAD(&potential_set); list_for_each_entry(account, &blob->account_head, list) list_add(&account->match_list, &potential_set); find_matching_accounts(&potential_set, name, &matches); if (list_empty(&matches)) return NULL; account = list_first_entry(&matches, struct account, match_list); last_account = list_last_entry(&matches, struct account, match_list); if (account != last_account) die("Multiple matches found for '%s'. You must specify an ID instead of a name.", name); return account; } lastpass-cli-1.0.0/endpoints.c0000644000175000017500000002511012743671271016513 0ustar troyhebetroyhebe/* * https endpoints for LastPass services * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "endpoints.h" #include "http.h" #include "version.h" #include "xml.h" #include "config.h" #include "util.h" #include "upload-queue.h" #include #include #include unsigned int lastpass_iterations(const char *username) { _cleanup_free_ char *reply = NULL; _cleanup_free_ char *user_lower = NULL; user_lower = xstrlower(username); reply = http_post_lastpass("iterations.php", NULL, NULL, "email", user_lower, NULL); if (!reply) return 0; return strtoul(reply, NULL, 10); } void lastpass_logout(const struct session *session) { free(http_post_lastpass("logout.php", session, NULL, "method", "cli", "noredirect", "1", "token", session->token, NULL)); } struct blob *lastpass_get_blob(const struct session *session, const unsigned char key[KDF_HASH_LEN]) { size_t len; _cleanup_free_ char *blob = http_post_lastpass("getaccts.php", session, &len, "mobile", "1", "requestsrc", "cli", "hasplugin", LASTPASS_CLI_VERSION, NULL); if (!blob || !len) return NULL; config_write_encrypted_buffer("blob", blob, len, key); return blob_parse((unsigned char *) blob, len, key, &session->private_key); } void lastpass_remove_account(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const struct account *account, struct blob *blob) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; http_post_add_params(¶ms, "extjs", "1", "token", session->token, "delete", "1", "aid", account->id, NULL); if (account->share) http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); ++blob->version; upload_queue_enqueue(sync, key, session, "show_website.php", ¶ms); } static char *stringify_field(const struct field *field) { char *str, *name, *type, *value, *intermediate; CURL *curl; curl = curl_easy_init(); if (!curl) return xstrdup(""); name = curl_easy_escape(curl, field->name, 0); type = curl_easy_escape(curl, field->type, 0); if (field->value_encrypted) value = curl_easy_escape(curl, field->value_encrypted, 0); else if (!strcmp(field->type, "checkbox") || !strcmp(field->type, "radio")) { xasprintf(&intermediate, "%s-%c", field->value, field->checked ? '1' : '0'); value = curl_easy_escape(curl, intermediate, 0); free(intermediate); } else value = curl_easy_escape(curl, field->value, 0); xasprintf(&str, "0\t%s\t%s\t%s\n", name, value, type); curl_free(name); curl_free(type); curl_free(value); curl_easy_cleanup(curl); return str; } static char *stringify_fields(const struct list_head *field_head) { char *field_str, *fields = NULL; struct field *field; list_for_each_entry(field, field_head, list) { field_str = stringify_field(field); xstrappend(&fields, field_str); free(field_str); } if (fields) xstrappend(&fields, "0\taction\t\taction\n0\tmethod\t\tmethod\n"); else fields = xstrdup(""); field_str = NULL; bytes_to_hex((unsigned char *) fields, &field_str, strlen(fields)); free(fields); return field_str; } static void add_app_fields(const struct account *account, struct http_param_set *params) { int index = 0; struct field *field; list_for_each_entry(field, &account->field_head, list) { char *id_name, *type_name, *value_name; xasprintf(&id_name, "fieldid%d", index); xasprintf(&type_name, "fieldtype%d", index); xasprintf(&value_name, "fieldvalue%d", index); http_post_add_params(params, id_name, field->name, type_name, field->type, value_name, field->value_encrypted, NULL); index++; } } void lastpass_update_account(enum blobsync sync, unsigned const char key[KDF_HASH_LEN], const struct session *session, const struct account *account, struct blob *blob) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; _cleanup_free_ char *url = NULL; _cleanup_free_ char *fields = NULL; bytes_to_hex((unsigned char *) account->url, &url, strlen(account->url)); fields = stringify_fields(&account->field_head); ++blob->version; http_post_add_params(¶ms, "extjs", "1", "token", session->token, "name", account->name_encrypted, "grouping", account->group_encrypted, "pwprotect", account->pwprotect ? "on" : "off", NULL); if (account->share) { http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); } if (account->is_app) { struct app *app = account_to_app(account); http_post_add_params(¶ms, "ajax", "1", "cmd", "updatelpaa", "appname", app->appname, NULL); add_app_fields(account, ¶ms); if (strcmp(account->id, "0")) http_post_add_params(¶ms, "appaid", account->id, NULL); upload_queue_enqueue(sync, key, session, "addapp.php", ¶ms); } else { http_post_add_params(¶ms, "aid", account->id, "url", url, "username", account->username_encrypted, "password", account->password_encrypted, "extra", account->note_encrypted, NULL); upload_queue_enqueue(sync, key, session, "show_website.php", ¶ms); } } unsigned long long lastpass_get_blob_version(struct session *session, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *reply = NULL; unsigned long long version; reply = http_post_lastpass("login_check.php", session, NULL, "method", "cli", NULL); if (!reply) return 0; version = xml_login_check(reply, session); if (version) session_save(session, key); return version; } void lastpass_log_access(enum blobsync sync, const struct session *session, unsigned const char key[KDF_HASH_LEN], const struct account *account) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; if (!strcmp(account->id, "0")) return; http_post_add_params(¶ms, "id", account->id, "method", "cli", NULL); if (account->share) http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); upload_queue_enqueue(sync, key, session, "loglogin.php", ¶ms); } int lastpass_pwchange_start(const struct session *session, const char *username, const char hash[KDF_HEX_LEN], struct pwchange_info *info) { _cleanup_free_ char *reply = NULL; reply = http_post_lastpass("lastpass/api.php", session, NULL, "cmd", "getacctschangepw", "username", username, "hash", hash, "changepw", "1", "changepw2", "1", "includersaprivatekeyenc", "1", "changeun", "", "resetrsakeys", "0", "includeendmarker", "1", NULL); if (!reply) return -ENOENT; return xml_parse_pwchange(reply, info); } int lastpass_pwchange_complete(const struct session *session, const char *username, const char *enc_username, const char new_hash[KDF_HEX_LEN], int new_iterations, struct pwchange_info *info) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; struct pwchange_field *field; struct pwchange_su_key *su_key; _cleanup_free_ char *iterations_str = xultostr(new_iterations); _cleanup_free_ char *sukeycnt_str = NULL; _cleanup_free_ char *reencrypt_string = NULL; _cleanup_free_ char *reply = NULL; size_t len; int su_key_ind; char suuid_str[30] = {0}; char sukey_str[30] = {0}; unsigned int i; /* build reencrypt string from change pw info */ len = strlen(info->reencrypt_id) + 1; list_for_each_entry(field, &info->fields, list) { len += strlen(field->old_ctext) + strlen(field->new_ctext) + 1 /* ':' */ + 1 /* '\n' */; } reencrypt_string = xcalloc(len + 1, 1); strlcat(reencrypt_string, info->reencrypt_id, len); strlcat(reencrypt_string, "\n", len); list_for_each_entry(field, &info->fields, list) { strlcat(reencrypt_string, field->old_ctext, len); strlcat(reencrypt_string, ":", len); strlcat(reencrypt_string, field->new_ctext, len); strlcat(reencrypt_string, "\n", len); } http_post_add_params(¶ms, "cmd", "updatepassword", "pwupdate", "1", "email", username, "token", info->token, "reencrypt", reencrypt_string, "newprivatekeyenc", info->new_privkey_encrypted, "newuserkeyhexhash", info->new_key_hash, "newprivatekeyenchexhash", info->new_privkey_hash, "newpasswordhash", new_hash, "key_iterations", iterations_str, "encrypted_username", enc_username, "origusername", username, NULL); su_key_ind = 0; list_for_each_entry(su_key, &info->su_keys, list) { snprintf(suuid_str, sizeof(suuid_str), "suuid%d", su_key_ind); snprintf(sukey_str, sizeof(sukey_str), "sukey%d", su_key_ind); http_post_add_params(¶ms, xstrdup(suuid_str), su_key->uid, xstrdup(sukey_str), su_key->new_enc_key, NULL); su_key_ind++; } sukeycnt_str = xultostr(su_key_ind); http_post_add_params(¶ms, xstrdup("sukeycnt"), sukeycnt_str, NULL); reply = http_post_lastpass_param_set("lastpass/api.php", session, NULL, ¶ms); for (i=0; i < params.n_alloced && params.argv[i]; i++) { if (starts_with(params.argv[i], "sukey") || starts_with(params.argv[i], "suuid")) { free(params.argv[i]); } } if (!reply) return -EINVAL; if (!strstr(reply, "pwchangeok")) return -EINVAL; return 0; } lastpass-cli-1.0.0/notes.h0000644000175000017500000000160312743671271015646 0ustar troyhebetroyhebe#ifndef NOTE_TYPES #define NOTE_TYPES enum note_type { NOTE_TYPE_NONE = -1, NOTE_TYPE_AMEX, NOTE_TYPE_BANK, NOTE_TYPE_CREDIT, NOTE_TYPE_DATABASE, NOTE_TYPE_DRIVERS_LICENSE, NOTE_TYPE_EMAIL, NOTE_TYPE_HEALTH_INSURANCE, NOTE_TYPE_IM, NOTE_TYPE_INSURANCE, NOTE_TYPE_MASTERCARD, NOTE_TYPE_MEMBERSHIP, NOTE_TYPE_PASSPORT, NOTE_TYPE_SERVER, NOTE_TYPE_SOFTWARE_LICENSE, NOTE_TYPE_SSH_KEY, NOTE_TYPE_SSN, NOTE_TYPE_VISA, NOTE_TYPE_WIFI, NUM_NOTE_TYPES, /* keep last */ }; #define MAX_FIELD_CT 12 struct note_template { const char *shortname; const char *name; const char *fields[MAX_FIELD_CT + 1]; }; extern struct note_template note_templates[]; const char *notes_get_name(enum note_type note_type); enum note_type notes_get_type_by_shortname(const char *shortname); enum note_type notes_get_type_by_name(const char *type_str); char *note_type_usage(); #endif /* NOTE_TYPES */ lastpass-cli-1.0.0/http.h0000644000175000017500000000163712743671271015504 0ustar troyhebetroyhebe#ifndef HTTP_H #define HTTP_H #include #include #include #include "session.h" struct http_param_set { char **argv; size_t n_alloced; }; #define LASTPASS_SERVER "lastpass.com" #define HTTP_ERROR_CODE CURLE_HTTP_RETURNED_ERROR #define HTTP_ERROR_CONNECT CURLE_SSL_CONNECT_ERROR int http_init(); void http_post_add_params(struct http_param_set *params, ...); char *http_post_lastpass(const char *page, const struct session *session, size_t *len, ...); char *http_post_lastpass_v(const char *server, const char *page, const struct session *session, size_t *len, char **argv); char *http_post_lastpass_param_set(const char *page, const struct session *session, size_t *len, struct http_param_set *params); char *http_post_lastpass_v_noexit(const char *server, const char *page, const struct session *session, size_t *final_len, char **argv, int *curl_ret, long *http_code); #endif lastpass-cli-1.0.0/lpass.1.txt0000644000175000017500000002357512743671271016403 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 *passwd* lpass *show* [--sync=auto|now|no] [--clip, -c] [--expand-multi, -x] [--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 *mv* [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID} GROUP lpass *add* [--sync=auto|now|no] [--non-interactive] {--name|--username, -u|--password, -p|--url|--notes|--field=FIELD|--note-type=NOTETYPE} [--color=auto|never|always] {NAME|UNIQUEID} lpass *edit* [--sync=auto|now|no] [--non-interactive] {--name|--username, -u|--password, -p|--url|--notes|--field=FIELD} [--color=auto|never|always] {NAME|UNIQUEID} lpass *generate* [--sync=auto|now|no] [--clip, -c] [--username=USERNAME] [--url=URL] [--no-symbols] [--color=auto|never|always] {NAME|UNIQUEID} LENGTH lpass *duplicate* [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID} lpass *rm* [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID} lpass *status* [--quiet, -q] [--color=auto|never|always] lpass *sync* [--background, -b] [--color=auto|never|always] lpass *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 lpass *share* *limit* [--deny|--allow] [--add|--rm|--clear] SHARE USERNAME [sites] Synchronization ~~~~~~~~~~~~~~~ The '--sync' options control when the current operation involves a synchronization with the server. If 'now' is set, and the command makes a change, the change is synchronized before the command exits. If 'now' is set, and the command displays a value, the local cache is synchronized before the value is shown. If 'now' is set, and the command is otherwise successful, but synchronization fails, the command will return an error. If 'auto' is set, and the command makes a change, the change is synchronized to the server in the background. If 'auto' is set, and the command displays a value, the local cache is synchronized before the value is shown only if the local cache is more than 5 seconds (or 'LPASS_AUTO_SYNC_TIME' seconds, if set) old. If 'no' is set, the command will not interact with the server, unless there is a current upload queue being processed. Any local changes that are not synchronized with the server will exist in a queue of timestamped requests which will be synchronized on the next occurring synchronization. The 'sync' command forces a synchronization of the local cache with the LastPass servers, and does not exit until the local cache is synchronized or until an error occurs. Alternatively, if '--background' is specified, the synchronization occurs in a daemonized process. Agent ~~~~~ An agent process will be spawned in the background on a first successful command, and all subsequent commands will use the agent for decryption, instead of asking a user for a password. The agent will quit after one hour, unless the 'LPASS_AGENT_TIMEOUT' environment variable is set to an alternative number of seconds in which to quit, or 0 to never quit. If the environment variable 'LPASS_AGENT_DISABLE' is set to 1, the agent will not be used. Password Entry ~~~~~~~~~~~~~~ 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 local cache and configuration folder, then attempt to authenticate itself with the LastPass servers, using the provided command line credentials or by interactively prompting (in the case of multifactor or an unprovided password). The '--trust' option will cause subsequent logins to not require multifactor authentication. If the '--plaintext-key' option is specified, the decryption key will be saved to the hard disk in plaintext. Please note that use of this option is discouraged except in limited situations, as it greatly decreases the security of data. The 'logout' subcommand will remove the local cache and stored encryption keys. It will prompt the user to confirm, unless '--force' is specified. The 'passwd' subcommand may be used to change your LastPass password: it will prompt for the old and new password and then re-encrypt all records with the newly derived key. Viewing ~~~~~~~ The 'show' subcommand will display a password or selected field. 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. The 'share limit' command may be used to manipulate account access lists on the share for a specific user. Running with no arguments will display the current access levels for a user. The '--add', '--rm', and '--clear' options may be used to add to, remove from, or reset the list. Passing '--allow' or '--deny' will make the list a whitelist or blacklist, respectively. Clipboard ~~~~~~~~~ Commands that take a '-c' or '--clip' option will copy the output to the clipboard, using *xclip*(1) or *xsel*(1) on X11-based systems, *pbcopy*(1) on OSX, or *putclip* on Cygwin. The command to be used can be overridden by specifying the `LPASS_CLIPBOARD_COMMAND` environment variable. Color Output ~~~~~~~~~~~~ The '--color' option controls colored output to the terminal. By default, commands will use '--color=auto', in which color output is used unless the output is not a tty (for example, when passed to a pipe or file). If 'always' is used, colors are produced regardless of the output detection. If 'never' is used, no color escape sequences are emitted. Configuration ~~~~~~~~~~~~~ 'lpass' stores configuration in the following locations, in descending order of precedence: * The directory '$LPASS_HOME', if set * '$XDG_CONFIG_HOME/lpass', '$XDG_DATA_HOME/lpass', and '$XDG_RUNTIME_DIR/lpass' (or equivalent defaults), if at least '$XDG_RUNTIME_DIR' is set * '$HOME/.lpass' All configuration may be specified via environment variables. Alternatively, a set of environment variable overrides may be specified in '$LPASS_HOME/env' in the form of: [verse] VARIABLE1=VALUE1 VARIABLE2=VALUE2 ... 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' EXAMPLES -------- ---- $ lpass login user@example.com $ lpass generate work/email 20 G #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-1.0.0/session.c0000644000175000017500000000772212743671271016204 0ustar troyhebetroyhebe/* * session handling routines * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "xml.h" #include "config.h" #include "util.h" #include "cipher.h" #include "agent.h" #include "upload-queue.h" #include #include struct session *session_new(void) { return new0(struct session, 1); } void session_free(struct session *session) { if (!session) return; free(session->uid); free(session->sessionid); free(session->token); free(session->private_key.key); free(session->server); free(session); } bool session_is_valid(struct session *session) { return session && session->uid && session->sessionid && session->token; } void session_set_private_key(struct session *session, unsigned const char key[KDF_HASH_LEN], const char *key_hex) { cipher_decrypt_private_key(key_hex, key, &session->private_key); } void session_save(struct session *session, unsigned const char key[KDF_HASH_LEN]) { config_write_encrypted_string("session_uid", session->uid, key); config_write_encrypted_string("session_sessionid", session->sessionid, key); config_write_encrypted_string("session_token", session->token, key); config_write_encrypted_buffer("session_privatekey", (char *)session->private_key.key, session->private_key.len, key); /* * existing sessions may not have a server yet; they will fall back * to lastpass.com. */ if (session->server) config_write_string("session_server", session->server); } struct session *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->server = config_read_string("session_server"); session->private_key.len = config_read_encrypted_buffer("session_privatekey", &session->private_key.key, key); mlock(session->private_key.key, session->private_key.len); if (session_is_valid(session)) return session; else { session_free(session); return NULL; } } void session_kill() { 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("session_server"); config_unlink("plaintext_key"); agent_kill(); upload_queue_kill(); } lastpass-cli-1.0.0/cmd-sync.c0000644000175000017500000000520512743671271016230 0ustar troyhebetroyhebe/* * command to synchronize with LastPass servers * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "kdf.h" #include "upload-queue.h" #include #include #include #include int cmd_sync(int argc, char **argv) { unsigned char key[KDF_HASH_LEN]; struct session *session = NULL; struct blob *blob = NULL; static struct option long_options[] = { {"background", no_argument, NULL, 'b'}, {"color", required_argument, NULL, 'C'}, {0, 0, 0, 0} }; int option; int option_index; bool background = false; while ((option = getopt_long(argc, argv, "b", long_options, &option_index)) != -1) { switch (option) { case 'b': background = true; break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case '?': default: die_usage(cmd_sync_usage); } } init_all(0, key, &session, NULL); upload_queue_ensure_running(key, session); if (!background) { while (upload_queue_is_running()) usleep(1000000 / 3); } session_free(session); blob_free(blob); return 0; } lastpass-cli-1.0.0/pins.h0000644000175000017500000000130712743671271015470 0ustar troyhebetroyhebe#ifndef PINS_H #define PINS_H const char *PK_PINS[] = { /* current lastpass.com primary (Thawte) */ "HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=", /* current lastpass.eu primary (AddTrust) */ "lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=", /* future lastpass root CA (GlobalSign R2) */ "iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0=", /* future lastpass.com primary (leaf) */ "0hkr5YW/WE6Nq5hNTcApxpuaiwlwy5HUFiOt3Qd9VBc=", /* future lastpass.com backup (leaf) */ "8CzY4qWQKZjFDwHXTOIpsVfWkiVnrhQOJEM4Q2b2Ar4=", /* future lastpass.eu primary (leaf) */ "SQAWwwYXoceSd8VNbiyxspGXEjFndkklEO2XzLMts10=", /* future lastpass.eu backup (leaf) */ "qr2VCNpUi0PK80PfRyF7lFBIEU1Gzz931k03hrD+xGQ=", }; #endif lastpass-cli-1.0.0/cmd-show.c0000644000175000017500000002311312743671271016232 0ustar troyhebetroyhebe/* * command to show the contents of a vault entry * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "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 /* * If a secure note field contains ascii armor, its newlines will * have been replaced by spaces when saving. Undo this for * display purposes. The replacement (if applicable) is done * in-place. */ static char *fix_ascii_armor(char *armor_str) { char *end_header, *start_trailer, *ptr; /* need at least 4 "-----" strings */ if (strlen(armor_str) < 20) return armor_str; /* look for -----BEGIN [xxx]----- and -----END [xxx]----- strings */ if (strncmp(armor_str, "-----BEGIN", 10)) return armor_str; end_header = strstr(armor_str + 10, "----- "); if (!end_header) return armor_str; start_trailer = strstr(end_header, "-----END"); if (!start_trailer) return armor_str; if (strncmp(armor_str + strlen(armor_str) - 5, "-----", 5)) return armor_str; /* ok, probably ascii armor, go ahead and munge it as such */ ptr = end_header; while ((ptr = strchr(ptr, ' ')) != NULL) { if (ptr >= start_trailer) break; *ptr = '\n'; } return armor_str; } static char *pretty_field_value(struct field *field) { char *value; if (!strcmp(field->type, "checkbox")) value = xstrdup(field->checked ? "Checked" : "Unchecked"); else if (!strcmp(field->type, "radio")) xasprintf(&value, "%s, %s", field->value, field->checked ? "Checked" : "Unchecked"); else value = fix_ascii_armor(xstrdup(field->value)); return value; } static void print_header(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'}, {"expand-multi", no_argument, NULL, 'x'}, {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, *account; struct app *app; enum blobsync sync = BLOB_SYNC_AUTO; bool clip = false; bool expand_multi = false; struct list_head matches, potential_set; enum search_type search = SEARCH_EXACT_MATCH; int fields = ACCOUNT_NAME | ACCOUNT_ID | ACCOUNT_FULLNAME; while ((option = getopt_long(argc, argv, "cupFGx", 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 'x': expand_multi = true; break; case '?': default: die_usage(cmd_show_usage); } } if (argc - optind < 1) die_usage(cmd_show_usage); if (argc - optind > 1) { /* * if multiple search criteria supplied, go ahead * and expand all matches */ expand_multi = true; } init_all(sync, key, &session, &blob); INIT_LIST_HEAD(&matches); INIT_LIST_HEAD(&potential_set); list_for_each_entry(account, &blob->account_head, list) list_add(&account->match_list, &potential_set); for (; optind < argc; optind++) { name = argv[optind]; switch (search) { case SEARCH_EXACT_MATCH: find_matching_accounts(&potential_set, name, &matches); break; case SEARCH_BASIC_REGEX: find_matching_regex(&potential_set, name, fields, &matches); break; case SEARCH_FIXED_SUBSTRING: find_matching_substr(&potential_set, name, fields, &matches); break; } } if (list_empty(&matches)) die("Could not find specified account(s)."); found = list_first_entry(&matches, struct account, match_list); last_found = list_last_entry(&matches, struct account, match_list); if (found != last_found && !expand_multi) { /* Multiple matches; dump the ids and exit */ terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "Multiple matches found.\n"); list_for_each_entry(found, &matches, match_list) print_header(found); exit(EXIT_SUCCESS); } /* reprompt if necessary for any matched item */ list_for_each_entry(found, &matches, match_list) { if (found->pwprotect) { unsigned char pwprotect_key[KDF_HASH_LEN]; if (!agent_load_key(pwprotect_key)) die("Could not authenticate for protected entry."); if (memcmp(pwprotect_key, key, KDF_HASH_LEN)) die("Current key is not on-disk key."); break; } } if (clip) clipboard_open(); list_for_each_entry(account, &matches, match_list) { found = account; lastpass_log_access(sync, session, key, found); notes_expansion = notes_expand(found); if (notes_expansion) found = notes_expansion; if (choice == FIELD) { bool has_field = false; list_for_each_entry(found_field, &found->field_head, list) { if (!strcmp(found_field->name, field)) { has_field = true; break; } } if (!has_field) die("Could not find specified field '%s'.", field); value = pretty_field_value(found_field); } else if (choice == USERNAME) value = xstrdup(found->username); else if (choice == PASSWORD) value = xstrdup(found->password); else if (choice == URL) value = xstrdup(found->url); else if (choice == ID) value = xstrdup(found->id); else if (choice == NAME) value = xstrdup(found->name); else if (choice == NOTES) value = xstrdup(found->note); 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); if (found->is_app) { app = account_to_app(found); if (strlen(app->appname)) terminal_printf(TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ": %s\n", "Application", app->appname); } 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-1.0.0/clipboard.h0000644000175000017500000000015012743671271016451 0ustar troyhebetroyhebe#ifndef CLIPBOARD_H #define CLIPBOARD_H void clipboard_open(void); void clipboard_close(void); #endif lastpass-cli-1.0.0/util.c0000644000175000017500000002741112743671271015473 0ustar troyhebetroyhebe/* * utility functions * * Copyright (C) 2014-2016 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. * * 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 (!size || !nmemb) { errno = ENOMEM; return NULL; } if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return NULL; } return realloc(optr, size * nmemb); } void *xreallocarray(void *ptr, size_t nmemb, size_t size) { void *ret = reallocarray(ptr, nmemb, size); if (likely(ret)) return ret; die_errno("reallocarray(%p, %zu, %zu)", ptr, nmemb, size); } void *xstrdup(const char *str) { void *ret = strdup(str); if (likely(ret)) return ret; die_errno("strdup(%p)", (void *) str); } void *xstrndup(const char *str, size_t maxlen) { void *ret = strndup(str, maxlen); if (likely(ret)) return ret; die_errno("strndup(%p, %zu)", (void *) str, maxlen); } int xasprintf(char **strp, const char *fmt, ...) { va_list params; int ret; va_start(params, fmt); ret = xvasprintf(strp, fmt, params); va_end(params); return ret; } int xvasprintf(char **strp, const char *fmt, va_list ap) { int ret; ret = vasprintf(strp, fmt, ap); if (ret == -1) die_errno("asprintf(%p, %s, ...)", (void *)strp, fmt); return ret; } #ifdef __GLIBC__ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = *s++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return (s - src - 1); /* count does not include NUL */ } size_t strlcat(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return (dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return (dlen + (s - src)); /* count does not include NUL */ } #else char *strchrnul(const char *s, int c) { char *p = strchr(s, c); if (!p) p = (char *)s + strlen(s); return p; } #endif void strlower(char *str) { for (; *str; ++str) *str = tolower(*str); } void strupper(char *str) { for (; *str; ++str) *str = toupper(*str); } char *xstrlower(const char *str) { char *copy = xstrdup(str); strlower(copy); return copy; } char *xstrupper(const char *str) { char *copy = xstrdup(str); strupper(copy); return copy; } char *xultostr(unsigned long num) { char *str; xasprintf(&str, "%ld", num); return str; } bool starts_with(const char *str, const char *start) { for (; ; ++str, ++start) { if (!*start) return true; else if (*str != *start) return false; } } bool ends_with(const char *str, const char *end) { int str_len = strlen(str); int end_len = strlen(end); if (str_len < end_len) return false; else return !strcmp(str + str_len - end_len, end); } char *trim(char *str) { int start, i; for (start = 0; isspace(str[start]) && str[start]; ++start); for (i = 0; str[i + start]; ++i) str[i] = str[i + start]; str[i] = '\0'; for (--i; i >= 0 && isspace(str[i]); --i) str[i] = '\0'; return str; } void xstrappend(char **str, const char *suffix) { if (!*str) { *str = xstrdup(suffix); return; } size_t len = strlen(*str) + strlen(suffix) + 1; *str = xrealloc(*str, len); strlcat(*str, suffix, len); } void xstrappendf(char **str, const char *suffixfmt, ...) { _cleanup_free_ char *fmt = NULL; va_list args; va_start(args, suffixfmt); xvasprintf(&fmt, suffixfmt, args); va_end(args); xstrappend(str, fmt); } void xstrprepend(char **str, const char *prefix) { if (!*str) { *str = xstrdup(prefix); return; } size_t len = strlen(*str) + strlen(prefix) + 1; char *new = xmalloc(len); strlcpy(new, prefix, len); strlcat(new, *str, len); free(*str); *str = new; } void xstrprependf(char **str, const char *suffixfmt, ...) { _cleanup_free_ char *fmt = NULL; va_list args; va_start(args, suffixfmt); xvasprintf(&fmt, suffixfmt, args); va_end(args); xstrprepend(str, fmt); } void secure_clear(void *ptr, size_t len) { if (!ptr) return; memset(ptr, 0, len); /* prevent GCC / LLVM from optimizing out memset */ asm volatile("" : : "r"(ptr) : "memory"); } void secure_clear_str(char *str) { if (!str) return; secure_clear(str, strlen(str)); } void *secure_resize(void *ptr, size_t oldlen, size_t newlen) { /* open-coded realloc, with a secure memset in the middle */ void *newptr = xmalloc(newlen); if (ptr) { memcpy(newptr, ptr, min(oldlen, newlen)); secure_clear(ptr, oldlen); free(ptr); } return newptr; } static char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; _unroll_ void bytes_to_hex(const unsigned char *bytes, char **hex, size_t len) { if (!*hex) *hex = xmalloc(len * 2 + 1); for (size_t i = 0; i < len; ++i) { (*hex)[i * 2] = hex_digits[(bytes[i] >> 4) & 0xF]; (*hex)[i * 2 + 1] = hex_digits[bytes[i] & 0xF]; } (*hex)[len * 2] = '\0'; } int hex_to_bytes(const char *hex, unsigned char **bytes) { size_t len = strlen(hex); if (len % 2 != 0) { if (!*bytes) *bytes = xcalloc(1, 1); **bytes = '\0'; return -EINVAL; } if (!*bytes) *bytes = xmalloc(len / 2 + 1); for (size_t i = 0; i < len / 2; ++i) { if (sscanf(&hex[i * 2], "%2hhx", (unsigned char *)(*bytes + i)) != 1) { fprintf(stderr, "%s\n", hex); **bytes = '\0'; return -EINVAL; } } (*bytes)[len / 2] = '\0'; return 0; } /* [min, max) */ unsigned long range_rand(unsigned long min, unsigned long max) { unsigned long base_random, range, remainder, bucket; if (!RAND_bytes((unsigned char *)&base_random, sizeof(base_random))) die("Could not generate random bytes."); if (ULONG_MAX == base_random) return range_rand(min, max); range = max - min; remainder = ULONG_MAX % range; bucket = ULONG_MAX / range; if (base_random < ULONG_MAX - remainder) return min + base_random / bucket; return range_rand(min, max); } void get_random_bytes(unsigned char *buf, size_t len) { if (!RAND_bytes(buf, len)) die("Could not generate random bytes."); } const char *bool_str(bool val) { return val ? "1" : "0"; } lastpass-cli-1.0.0/cmd-share.c0000644000175000017500000003211312743671271016354 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders grant you * additional permission to link or combine this program with the OpenSSL * library and distribute the resulting work. See the LICENSE.OpenSSL file * in this distribution for more details. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "cmd.h" #include "util.h" #include "config.h" #include "terminal.h" #include "agent.h" #include "kdf.h" #include "endpoints.h" #include "clipboard.h" #include "upload-queue.h" #include "process.h" #include #include #include #include #include struct share_args { struct session *session; struct blob *blob; enum blobsync sync; unsigned char key[KDF_HASH_LEN]; const char *sharename; struct share *share; bool read_only; bool set_read_only; bool admin; bool set_admin; bool hide_passwords; bool set_hide_passwords; bool specified_limit_type; bool whitelist; bool add; bool remove; bool clear; }; struct share_command { const char *name; const char *usage; int (*cmd)(struct share_command *cmd, int, char **, struct share_args *share); }; #define share_userls_usage "userls SHARE" #define share_useradd_usage "useradd [--read-only=[true|false] --hidden=[true|false] --admin=[true|false] SHARE USERNAME" #define share_usermod_usage "usermod [--read-only=[true|false] --hidden=[true|false] --admin=[true|false] SHARE USERNAME" #define share_userdel_usage "userdel SHARE USERNAME" #define share_create_usage "create SHARE" #define share_limit_usage "limit [--deny|--allow] [--add|--rm|--clear] SHARE USERNAME [sites]" #define share_rm_usage "rm SHARE" static char *checkmark(int x) { return (x) ? "x" : "_"; } static void die_share_usage(struct share_command *cmd) { die_usage(cmd->usage); } static int share_userls(struct share_command *cmd, int argc, char **argv, struct share_args *args) { UNUSED(argv); struct share_user *user; char name[40]; LIST_HEAD(users); bool has_groups = false; if (argc) die_share_usage(cmd); if (lastpass_share_getinfo(args->session, args->share->id, &users)) die("Unable to access user list for share %s\n", args->sharename); terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "%-40s %6s %6s %6s %6s %6s" TERMINAL_RESET "\n", "User", "RO", "Admin", "Hide", "OutEnt", "Accept"); list_for_each_entry(user, &users, list) { if (user->is_group) { has_groups = true; continue; } if (user->realname) { snprintf(name, sizeof(name), "%s <%s>", user->realname, user->username); } else { snprintf(name, sizeof(name), "%s", user->username); } terminal_printf("%-40s %6s %6s %6s %6s %6s" "\n", name, checkmark(user->read_only), checkmark(user->admin), checkmark(user->hide_passwords), checkmark(user->outside_enterprise), checkmark(user->accepted)); } if (!has_groups) return 0; terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "%-40s %6s %6s %6s %6s %6s" TERMINAL_RESET "\n", "Group", "RO", "Admin", "Hide", "OutEnt", "Accept"); list_for_each_entry(user, &users, list) { if (!user->is_group) continue; terminal_printf("%-40s %6s %6s %6s %6s %6s" "\n", user->username, checkmark(user->read_only), checkmark(user->admin), checkmark(user->hide_passwords), checkmark(user->outside_enterprise), checkmark(user->accepted)); } return 0; } static int share_useradd(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user new_user = { .read_only = args->read_only, .hide_passwords = args->hide_passwords, .admin = args->admin }; if (argc != 1) die_share_usage(cmd); new_user.username = argv[0]; lastpass_share_user_add(args->session, args->share, &new_user); return 0; } static struct share_user *get_user_from_share(struct session *session, struct share *share, const char *username) { struct share_user *tmp, *found = NULL; LIST_HEAD(users); if (lastpass_share_getinfo(session, share->id, &users)) die("Unable to access user list for share %s\n", share->name); list_for_each_entry(tmp, &users, list) { if (strcmp(tmp->username, username) == 0) { found = tmp; break; } } if (!found) die("Unable to find user %s in the user list\n", username); return found; } static int share_usermod(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user *user; if (argc != 1) die_share_usage(cmd); user = get_user_from_share(args->session, args->share, argv[0]); if (args->set_read_only) user->read_only = args->read_only; if (args->set_hide_passwords) user->hide_passwords = args->hide_passwords; if (args->set_admin) user->admin = args->admin; lastpass_share_user_mod(args->session, args->share, user); return 0; } static int share_userdel(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user *found; if (argc != 1) die_share_usage(cmd); found = get_user_from_share(args->session, args->share, argv[0]); lastpass_share_user_del(args->session, args->share->id, found); return 0; } static void print_share_limits(struct blob *blob, struct share *share, struct share_limit *limit) { struct account *account; struct share_limit_aid *aid; char sitename[80]; /* display current settings for this user */ terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "%-60s %7s %5s" TERMINAL_RESET "\n", "Site", "Unavail", "Avail"); list_for_each_entry(account, &blob->account_head, list) { if (account->share != share) continue; bool in_list = false; list_for_each_entry(aid, &limit->aid_list, list) { if (!strcmp(aid->aid, account->id)) { in_list = true; } } bool avail = (in_list && limit->whitelist) || (!in_list && !limit->whitelist); snprintf(sitename, sizeof(sitename), TERMINAL_BOLD "%-.30s" TERMINAL_NO_BOLD " [id: %s]", account->name, account->id); terminal_printf(TERMINAL_FG_GREEN "%-66s" TERMINAL_RESET " %8s %5s\n", sitename, checkmark(!avail), checkmark(avail)); } } static int share_limit(struct share_command *cmd, int argc, char **argv, struct share_args *args) { struct share_user *found; struct share_limit limit; struct account *account; struct share_limit_aid *aid, *tmp; struct blob *blob = args->blob; bool changed_list_type; int optind; struct list_head potential_set; struct list_head matches; if (argc < 1) die_share_usage(cmd); found = get_user_from_share(args->session, args->share, argv[0]); lastpass_share_get_limits(args->session, args->share, found, &limit); if (!args->specified_limit_type) args->whitelist = limit.whitelist; /* * prompt if we switch list type and there are entries already, in * order to avoid accidentally changing a blacklist to a whitelist */ changed_list_type = args->whitelist != limit.whitelist && !list_empty(&limit.aid_list); if (argc == 1 && !changed_list_type) { /* nothing to do, just print current limits */ print_share_limits(blob, args->share, &limit); return 0; } if (changed_list_type) { bool isok = ask_yes_no(false, "Supplied limit type (%s) doesn't match existing list (%s).\nContinue and switch?", args->whitelist ? "default deny" : "default allow", limit.whitelist ? "default deny" : "default allow"); if (!isok) die("Aborted."); } /* add to, or subtract from current list */ INIT_LIST_HEAD(&potential_set); INIT_LIST_HEAD(&matches); /* search only accts in this share */ list_for_each_entry(account, &blob->account_head, list) { if (account->share == args->share) list_add(&account->match_list, &potential_set); } for (optind = 1; optind < argc; optind++) { char *name = argv[optind]; find_matching_accounts(&potential_set, name, &matches); } if (args->clear) { list_for_each_entry_safe(aid, tmp, &limit.aid_list, list) { list_del(&aid->list); free(aid->aid); } } list_for_each_entry(account, &matches, match_list) { /* add account to share_limit */ bool in_list = false; list_for_each_entry(aid, &limit.aid_list, list) { if (!strcmp(aid->aid, account->id)) { in_list = true; break; } } if ((!in_list && args->add) || args->clear) { struct share_limit_aid *newaid = new0(struct share_limit_aid, 1); newaid->aid = account->id; list_add_tail(&newaid->list, &limit.aid_list); } else if (in_list && args->remove) { list_del(&aid->list); } } limit.whitelist = args->whitelist; lastpass_share_set_limits(args->session, args->share, found, &limit); print_share_limits(blob, args->share, &limit); return 0; } static int share_create(struct share_command *cmd, int argc, char **argv, struct share_args *args) { if (argc != 0) die_share_usage(cmd); UNUSED(argv); lastpass_share_create(args->session, args->sharename); return 0; } static int share_rm(struct share_command *cmd, int argc, char **argv, struct share_args *args) { if (argc != 0) die_share_usage(cmd); UNUSED(argv); lastpass_share_delete(args->session, args->share); return 0; } #define SHARE_CMD(name) { #name, "share " share_##name##_usage, share_##name } static struct share_command share_commands[] = { SHARE_CMD(userls), SHARE_CMD(useradd), SHARE_CMD(usermod), SHARE_CMD(userdel), SHARE_CMD(create), SHARE_CMD(rm), SHARE_CMD(limit), }; #undef SHARE_CMD /* Display more verbose usage if no subcmd is given or matched. */ static void share_help(void) { terminal_fprintf(stderr, "Usage: %s %s\n", ARGV[0], cmd_share_usage); for (size_t i = 0; i < ARRAY_SIZE(share_commands); ++i) printf(" %s %s\n", ARGV[0], share_commands[i].usage); exit(1); } int cmd_share(int argc, char **argv) { char *subcmd; static struct option long_options[] = { {"sync", required_argument, NULL, 'S'}, {"color", required_argument, NULL, 'C'}, {"read-only", required_argument, NULL, 'r'}, {"hidden", required_argument, NULL, 'H'}, {"admin", required_argument, NULL, 'a'}, {"deny", no_argument, NULL, 'd'}, {"allow", no_argument, NULL, 'w'}, {"add", no_argument, NULL, 'A'}, {"rm", no_argument, NULL, 'R'}, {"clear", no_argument, NULL, 'c'}, {0, 0, 0, 0} }; struct share_args args = { .sync = BLOB_SYNC_AUTO, .read_only = true, .hide_passwords = true, .add = true, }; bool invalid_params = false; struct share_command *command; /* * Parse out all option commands for all subcommands, and store * them in the share_args struct. * * All commands have at least subcmd and sharename non-option args. * Additional non-option commands are passed as argc/argv to the * sub-command. */ int option; int option_index; while ((option = getopt_long(argc, argv, "S:C:r:H:a:dwARc", long_options, &option_index)) != -1) { switch (option) { case 'S': args.sync = parse_sync_string(optarg); break; case 'C': terminal_set_color_mode( parse_color_mode_string(optarg)); break; case 'r': args.read_only = parse_bool_arg_string(optarg); args.set_read_only = true; break; case 'H': args.hide_passwords = parse_bool_arg_string(optarg); args.set_hide_passwords = true; break; case 'a': args.admin = parse_bool_arg_string(optarg); args.set_admin = true; break; case 'w': args.whitelist = true; args.specified_limit_type = true; break; case 'd': args.whitelist = false; args.specified_limit_type = true; break; case 'A': args.add = true; args.remove = args.clear = false; break; case 'R': args.remove = true; args.add = args.clear = false; break; case 'c': args.clear = true; args.add = args.remove = false; break; case '?': default: invalid_params = true; } } if (argc - optind < 1) share_help(); subcmd = argv[optind++]; command = NULL; for (unsigned int i=0; i < ARRAY_SIZE(share_commands); i++) { if (strcmp(subcmd, share_commands[i].name) == 0) { command = &share_commands[i]; break; } } if (!command) share_help(); if (argc - optind < 1 || invalid_params) die_share_usage(command); args.sharename = argv[optind++]; init_all(args.sync, args.key, &args.session, &args.blob); if (strcmp(subcmd, "create") != 0) { args.share = find_unique_share(args.blob, args.sharename); if (!args.share) die("Share %s not found.", args.sharename); } command->cmd(command, argc - optind, &argv[optind], &args); session_free(args.session); blob_free(args.blob); return 0; }