/* * https endpoints for LastPass services * * Copyright (C) 2014-2018 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "endpoints.h" #include "http.h" #include "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, "method", "cli", "name", account->name_encrypted, "grouping", account->group_encrypted, "pwprotect", account->pwprotect ? "on" : "off", NULL); if (account->share) { http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); } if (account->is_app) { struct app *app = account_to_app(account); http_post_add_params(¶ms, "ajax", "1", "cmd", "updatelpaa", "appname", app->appname, NULL); add_app_fields(account, ¶ms); if (strcmp(account->id, "0")) http_post_add_params(¶ms, "appaid", account->id, NULL); upload_queue_enqueue(sync, key, session, "addapp.php", ¶ms); goto out_free_params; } http_post_add_params(¶ms, "aid", account->id, "url", url, "username", account->username_encrypted, "password", account->password_encrypted, "extra", account->note_encrypted, NULL); if (strlen(fields)) { http_post_add_params(¶ms, "save_all", "1", "data", fields, NULL); } upload_queue_enqueue(sync, key, session, "show_website.php", ¶ms); out_free_params: free(params.argv); } unsigned long long lastpass_get_blob_version(struct session *session, unsigned const char key[KDF_HASH_LEN]) { _cleanup_free_ char *reply = NULL; unsigned long long version; reply = http_post_lastpass("login_check.php", session, NULL, "method", "cli", NULL); if (!reply) return 0; version = xml_login_check(reply, session); if (version) session_save(session, key); return version; } void lastpass_log_access(enum blobsync sync, const struct session *session, unsigned const char key[KDF_HASH_LEN], const struct account *account) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; if (!strcmp(account->id, "0")) return; http_post_add_params(¶ms, "id", account->id, "method", "cli", NULL); if (account->share) http_post_add_params(¶ms, "sharedfolderid", account->share->id, NULL); upload_queue_enqueue(sync, key, session, "loglogin.php", ¶ms); free(params.argv); } int lastpass_pwchange_start(const struct session *session, const char *username, const char hash[KDF_HEX_LEN], struct pwchange_info *info) { _cleanup_free_ char *reply = NULL; reply = http_post_lastpass("lastpass/api.php", session, NULL, "cmd", "getacctschangepw", "username", username, "hash", hash, "changepw", "1", "changepw2", "1", "includersaprivatekeyenc", "1", "changeun", "", "resetrsakeys", "0", "includeendmarker", "1", NULL); if (!reply) return -ENOENT; return xml_parse_pwchange(reply, info); } int lastpass_pwchange_complete(const struct session *session, const char *username, const char *enc_username, const char new_hash[KDF_HEX_LEN], int new_iterations, struct pwchange_info *info) { struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; struct pwchange_field *field; struct pwchange_su_key *su_key; _cleanup_free_ char *iterations_str = xultostr(new_iterations); _cleanup_free_ char *sukeycnt_str = NULL; _cleanup_free_ char *reencrypt_string = NULL; _cleanup_free_ char *reply = NULL; size_t len; int su_key_ind; char suuid_str[30] = {0}; char sukey_str[30] = {0}; unsigned int i; /* build reencrypt string from change pw info */ len = strlen(info->reencrypt_id) + 1; list_for_each_entry(field, &info->fields, list) { len += strlen(field->old_ctext) + strlen(field->new_ctext) + 1 /* ':' */ + 1 /* '\n' */; } reencrypt_string = xcalloc(len + 1, 1); strlcat(reencrypt_string, info->reencrypt_id, len); strlcat(reencrypt_string, "\n", len); list_for_each_entry(field, &info->fields, list) { strlcat(reencrypt_string, field->old_ctext, len); strlcat(reencrypt_string, ":", len); strlcat(reencrypt_string, field->new_ctext, len); strlcat(reencrypt_string, "\n", len); } http_post_add_params(¶ms, "cmd", "updatepassword", "pwupdate", "1", "email", username, "token", info->token, "reencrypt", reencrypt_string, "newprivatekeyenc", info->new_privkey_encrypted, "newuserkeyhexhash", info->new_key_hash, "newprivatekeyenchexhash", info->new_privkey_hash, "newpasswordhash", new_hash, "key_iterations", iterations_str, "encrypted_username", enc_username, "origusername", username, NULL); su_key_ind = 0; list_for_each_entry(su_key, &info->su_keys, list) { snprintf(suuid_str, sizeof(suuid_str), "suuid%d", su_key_ind); snprintf(sukey_str, sizeof(sukey_str), "sukey%d", su_key_ind); http_post_add_params(¶ms, xstrdup(suuid_str), su_key->uid, xstrdup(sukey_str), su_key->new_enc_key, NULL); su_key_ind++; } sukeycnt_str = xultostr(su_key_ind); http_post_add_params(¶ms, xstrdup("sukeycnt"), sukeycnt_str, NULL); reply = http_post_lastpass_param_set("lastpass/api.php", session, NULL, ¶ms); for (i=0; i < params.n_alloced && params.argv[i]; i++) { if (starts_with(params.argv[i], "sukey") || starts_with(params.argv[i], "suuid")) { free(params.argv[i]); } } if (!reply) return -EINVAL; if (!strstr(reply, "pwchangeok")) return -EINVAL; return 0; } /* * Upload a set of accounts, used for import. */ int lastpass_upload(const struct session *session, struct list_head *accounts) { _cleanup_free_ char *reply = NULL; struct account *account; int index; unsigned int i; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; if (list_empty(accounts)) return 0; http_post_add_params(¶ms, "token", session->token, "cmd", "uploadaccounts", NULL); index = 0; list_for_each_entry(account, accounts, list) { char *name_param, *grouping_param; char *url_param, *username_param, *password_param; char *fav_param, *extra_param; char *url = NULL; bytes_to_hex((unsigned char *) account->url, &url, strlen(account->url)); xasprintf(&name_param, "name%d", index); xasprintf(&grouping_param, "grouping%d", index); xasprintf(&url_param, "url%d", index); xasprintf(&username_param, "username%d", index); xasprintf(&password_param, "password%d", index); xasprintf(&fav_param, "fav%d", index); xasprintf(&extra_param, "extra%d", index); http_post_add_params(¶ms, name_param, account->name_encrypted, grouping_param, account->group_encrypted, url_param, url, username_param, account->username_encrypted, password_param, account->password_encrypted, fav_param, account->fav ? "1" : "0", extra_param, account->note_encrypted, NULL); index++; } reply = http_post_lastpass_param_set("lastpass/api.php", session, NULL, ¶ms); for (i=0; i < params.n_alloced && params.argv[i]; i++) { if (starts_with(params.argv[i], "name") || starts_with(params.argv[i], "grouping") || starts_with(params.argv[i], "username") || starts_with(params.argv[i], "password") || starts_with(params.argv[i], "fav") || starts_with(params.argv[i], "extra")) { free(params.argv[i]); } else if (starts_with(params.argv[i], "url")) { free(params.argv[i]); if (i < params.n_alloced) { free(params.argv[i+1]); i++; } } } free(params.argv); if (!reply) return -EINVAL; return xml_api_err(reply); } /* * Get the attachment for a given attachment id. The crypttext is returned * and should be decrypted with account->attachkey. The pointer returned * in *result should be freed by the caller. */ int lastpass_load_attachment(const struct session *session, const char *shareid, struct attach *attach, char **result) { char *reply = NULL; char *p; *result = NULL; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; http_post_add_params(¶ms, "token", session->token, "getattach", attach->storagekey, NULL); if (shareid) { http_post_add_params(¶ms, "sharedfolderid", shareid, NULL); } reply = http_post_lastpass_param_set("getattach.php", session, NULL, ¶ms); free(params.argv); if (!reply) return -ENOENT; /* returned string is json-encoded base64 string; unescape it */ if (reply[0] == '"') memmove(reply, reply+1, strlen(reply)); if (reply[strlen(reply)-1] == '"') reply[strlen(reply)-1] = 0; p = reply; while (*p) { if (*p == '\\') { memmove(p, p + 1, strlen(p)); } else { p++; } } *result = reply; return 0; }