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 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) { _cleanup_free_ char *upload_log_path = NULL; int null = open("/dev/null", 0); int upload_log = null; if (lpass_log_level() >= 0) { upload_log_path = config_path("lpass.log"); upload_log = open(upload_log_path, O_WRONLY | O_CREAT | O_APPEND, 0600); } if (null >= 0) { dup2(null, 0); dup2(upload_log, 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); }
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 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; }
char *http_post_lastpass(const char *page, const struct session *session, size_t *final_len, ...) { va_list args; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; va_start(args, final_len); vhttp_post_add_params(¶ms, args); char *result = http_post_lastpass_param_set(page, session, final_len, ¶ms); free(params.argv); return result; } #ifndef TEST_BUILD char *http_post_lastpass_v_noexit(const char *server, const char *page, const struct session *session, size_t *final_len, char **argv, int *curl_ret, long *http_code) { _cleanup_free_ char *url = NULL; _cleanup_free_ char *postdata = NULL; _cleanup_free_ char *cookie = NULL; _cleanup_fclose_ FILE *logstream = NULL; char *param, *encoded_param; CURL *curl = NULL; char separator; size_t len, new_len; int ret; struct mem_chunk result; const char *login_server; /* if we have a session, use that server, otherwise use whatever was passed */ login_server = session ? session->server : server; /* if nothing passed, use lastpass */ if (!login_server) login_server = LASTPASS_SERVER; xasprintf(&url, "https://%s/%s", login_server, page); lpass_log(LOG_DEBUG, "Making request to %s\n", url); curl = curl_easy_init(); if (!curl) die("Could not init curl"); len = 0; for (separator = '=', param = *argv; param; separator = (separator == '=') ? '&' : '=', param = *(++argv)) { encoded_param = curl_easy_escape(curl, param, 0); if (!encoded_param) die("Could not escape %s with curl", param); new_len = strlen(encoded_param) + 1 /* separator */; postdata = xrealloc(postdata, len + new_len + 1 /* null */); snprintf(postdata + len, new_len + 1, "%s%c", encoded_param, separator); len += new_len; curl_free(encoded_param); } if (len && postdata) postdata[len - 1] = '\0'; memset(&result, 0, sizeof(result)); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_USERAGENT, LASTPASS_CLI_USERAGENT); /* TODO: Make this optional via either env vars and/or an option for * lpass -4 or lpass -6 */ curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); if (lpass_log_level() >= LOG_VERBOSE) { logstream = lpass_log_open(); if (logstream) { curl_easy_setopt(curl, CURLOPT_STDERR, logstream); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); } } #if defined(DO_NOT_ENABLE_ME_MITM_PROXY_FOR_DEBUGGING_ONLY) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_PROXY, "http://localhost:8080"); #else curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, pin_keys); #endif curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, check_interruption); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); if (postdata) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata); if (session) { xasprintf(&cookie, "PHPSESSID=%s", session->sessionid); curl_easy_setopt(curl, CURLOPT_COOKIE, cookie); } set_interrupt_detect(); ret = curl_easy_perform(curl); unset_interrupt_detect(); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code); curl_easy_cleanup(curl); *curl_ret = ret; if (ret != CURLE_OK) { result.len = 0; free(result.ptr); result.ptr = NULL; } else if (!result.ptr) result.ptr = xstrdup(""); if (final_len) *final_len = result.len; return result.ptr; }