Exemple #1
0
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();
}
Exemple #2
0
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);
}
Exemple #3
0
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));
}
Exemple #4
0
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;
}
Exemple #5
0
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(&params, args);
	char *result = http_post_lastpass_param_set(page, session, final_len, &params);
	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;
}