/*
Copyright (C) 2010 Red Hat, Inc.
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.
*/
#include "https-utils.h"
#define MAX_FORMATS 16
#define MAX_RELEASES 32
#define MAX_DOTS_PER_LINE 80
#define MIN_EXPLOITABLE_RATING 4
enum
{
TASK_RETRACE,
TASK_DEBUG,
TASK_VMCORE,
};
static struct language lang;
struct retrace_settings
{
int running_tasks;
int max_running_tasks;
long long max_packed_size;
long long max_unpacked_size;
char *supported_formats[MAX_FORMATS];
char *supported_releases[MAX_RELEASES];
};
static const char *dump_dir_name = NULL;
static const char *coredump = NULL;
static const char *required_retrace[] = { FILENAME_COREDUMP,
FILENAME_EXECUTABLE,
FILENAME_PACKAGE,
FILENAME_OS_RELEASE,
NULL };
static const char *optional_retrace[] = { FILENAME_ROOTDIR,
FILENAME_OS_RELEASE_IN_ROOTDIR,
NULL };
static const char *required_vmcore[] = { FILENAME_VMCORE,
NULL };
static unsigned delay = 0;
static int task_type = TASK_RETRACE;
static bool http_show_headers;
static bool no_pkgcheck;
static struct https_cfg cfg =
{
.url = "retrace.fedoraproject.org",
.port = 443,
.ssl_allow_insecure = false,
};
static void alert_crash_too_large()
{
alert(_("Retrace server can not be used, because the crash "
"is too large. Try local retracing."));
}
/* Add an entry name to the args array if the entry name exists in a
* problem directory. The entry is added to argindex offset to the array,
* and the argindex is then increased.
*/
static void args_add_if_exists(const char *args[],
struct dump_dir *dd,
const char *name,
int *argindex)
{
if (dd_exist(dd, name))
{
args[*argindex] = name;
*argindex += 1;
}
}
/* Create an archive with files required for retrace server and return
* a file descriptor. Returns -1 if it fails.
*/
static int create_archive(bool unlink_temp)
{
struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
if (!dd)
return -1;
/* Open a temporary file. */
char *filename = xstrdup(LARGE_DATA_TMP_DIR"/abrt-retrace-client-archive-XXXXXX.tar.xz");
int tempfd = mkstemps(filename, /*suffixlen:*/7);
if (tempfd == -1)
perror_msg_and_die(_("Can't create temporary file in "LARGE_DATA_TMP_DIR));
if (unlink_temp)
xunlink(filename);
free(filename);
/* Run xz:
* - xz reads input from a pipe
* - xz writes output to the temporary file.
*/
const char *xz_args[4];
xz_args[0] = "xz";
xz_args[1] = "-2";
xz_args[2] = "-";
xz_args[3] = NULL;
int tar_xz_pipe[2];
xpipe(tar_xz_pipe);
fflush(NULL); /* paranoia */
pid_t xz_child = vfork();
if (xz_child == -1)
perror_msg_and_die("vfork");
if (xz_child == 0)
{
close(tar_xz_pipe[1]);
xmove_fd(tar_xz_pipe[0], STDIN_FILENO);
xmove_fd(tempfd, STDOUT_FILENO);
execvp(xz_args[0], (char * const*)xz_args);
perror_msg_and_die(_("Can't execute '%s'"), xz_args[0]);
}
close(tar_xz_pipe[0]);
/* Run tar, and set output to a pipe with xz waiting on the other
* end.
*/
const char *tar_args[10];
tar_args[0] = "tar";
tar_args[1] = "cO";
tar_args[2] = xasprintf("--directory=%s", dump_dir_name);
const char **required_files = task_type == TASK_VMCORE ? required_vmcore : required_retrace;
int index = 3;
while (required_files[index - 3])
args_add_if_exists(tar_args, dd, required_files[index - 3], &index);
if (task_type == TASK_RETRACE || task_type == TASK_DEBUG)
{
int i;
for (i = 0; optional_retrace[i]; ++i)
args_add_if_exists(tar_args, dd, optional_retrace[i], &index);
}
tar_args[index] = NULL;
dd_close(dd);
fflush(NULL); /* paranoia */
pid_t tar_child = vfork();
if (tar_child == -1)
perror_msg_and_die("vfork");
if (tar_child == 0)
{
xmove_fd(xopen("/dev/null", O_RDWR), STDIN_FILENO);
xmove_fd(tar_xz_pipe[1], STDOUT_FILENO);
execvp(tar_args[0], (char * const*)tar_args);
perror_msg_and_die(_("Can't execute '%s'"), tar_args[0]);
}
free((void*)tar_args[2]);
close(tar_xz_pipe[1]);
/* Wait for tar and xz to finish successfully */
int status;
log_notice("Waiting for tar...");
safe_waitpid(tar_child, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
/* Hopefully, by this time child emitted more meaningful
* error message. But just in case it didn't:
*/
error_msg_and_die(_("Can't create temporary file in "LARGE_DATA_TMP_DIR));
log_notice("Waiting for xz...");
safe_waitpid(xz_child, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
error_msg_and_die(_("Can't create temporary file in "LARGE_DATA_TMP_DIR));
log_notice("Done...");
xlseek(tempfd, 0, SEEK_SET);
return tempfd;
}
struct retrace_settings *get_settings()
{
struct retrace_settings *settings = xzalloc(sizeof(struct retrace_settings));
PRFileDesc *tcp_sock, *ssl_sock;
ssl_connect(&cfg, &tcp_sock, &ssl_sock);
struct strbuf *http_request = strbuf_new();
strbuf_append_strf(http_request,
"GET /settings HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n"
"\r\n", cfg.url);
PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
alert_connection_error(cfg.url);
error_msg_and_die(_("Failed to send HTTP header of length %d: NSS error %d"),
http_request->len, PR_GetError());
}
strbuf_free(http_request);
char *http_response = tcp_read_response(tcp_sock);
if (http_show_headers)
http_print_headers(stderr, http_response);
int response_code = http_get_response_code(http_response);
if (response_code != 200)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Unexpected HTTP response from server: %d\n%s"),
response_code, http_response);
}
char *headers_end = strstr(http_response, "\r\n\r\n");
char *c, *row, *value;
if (!headers_end)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing HTTP message body."));
}
row = headers_end + strlen("\r\n\r\n");
do
{
/* split rows */
c = strchr(row, '\n');
if (c)
*c = '\0';
/* split key and values */
value = strchr(row, ' ');
if (!value)
{
row = c + 1;
continue;
}
*value = '\0';
++value;
if (0 == strcasecmp("running_tasks", row))
settings->running_tasks = atoi(value);
else if (0 == strcasecmp("max_running_tasks", row))
settings->max_running_tasks = atoi(value);
else if (0 == strcasecmp("max_packed_size", row))
settings->max_packed_size = atoll(value) * 1024 * 1024;
else if (0 == strcasecmp("max_unpacked_size", row))
settings->max_unpacked_size = atoll(value) * 1024 * 1024;
else if (0 == strcasecmp("supported_formats", row))
{
char *space;
int i;
for (i = 0; i < MAX_FORMATS - 1 && (space = strchr(value, ' ')); ++i)
{
*space = '\0';
settings->supported_formats[i] = xstrdup(value);
value = space + 1;
}
/* last element */
settings->supported_formats[i] = xstrdup(value);
}
else if (0 == strcasecmp("supported_releases", row))
{
char *space;
int i;
for (i = 0; i < MAX_RELEASES - 1 && (space = strchr(value, ' ')); ++i)
{
*space = '\0';
settings->supported_releases[i] = xstrdup(value);
value = space + 1;
}
/* last element */
settings->supported_releases[i] = xstrdup(value);
}
/* the beginning of the next row */
row = c + 1;
} while (c);
free(http_response);
ssl_disconnect(ssl_sock);
return settings;
}
static void free_settings(struct retrace_settings *settings)
{
if (!settings)
return;
int i;
for (i = 0; i < MAX_FORMATS; ++i)
free(settings->supported_formats[i]);
for (i = 0; i < MAX_RELEASES; ++i)
free(settings->supported_releases[i]);
free(settings);
}
/* returns release identifier as dist-ver-arch */
/* or NULL if unknown */
static char *get_release_id(map_string_t *osinfo, const char *architecture)
{
char *arch = xstrdup(architecture);
if (strcmp("i686", arch) == 0 || strcmp("i586", arch) == 0)
{
free(arch);
arch = xstrdup("i386");
}
char *result = NULL;
char *release = NULL;
char *version = NULL;
parse_osinfo_for_rhts(osinfo, (char **)&release, (char **)&version);
if (release == NULL || version == NULL)
error_msg_and_die("Can't parse OS release name or version");
char *space = strchr(version, ' ');
if (space)
*space = '\0';
if (strcmp("Fedora", release) == 0)
{
/* Because of inconsistency between Fedora's os-release and retrace
* server.
*
* Adding the reporting fields to Fedora's os-release was a bit
* frustrating for all participants and fixing it on the retrace server
* side is neither feasible nor acceptable.
*
* Therefore, we have decided to add the following hack.
*/
if (strcmp("Rawhide", version) == 0)
{
/* Rawhide -> rawhide */
version[0] = 'r';
}
/* Fedora -> fedora */
release[0] = 'f';
}
else if (strcmp("Red Hat Enterprise Linux", release) == 0)
strcpy(release, "rhel");
result = xasprintf("%s-%s-%s", release, version, arch);
free(release);
free(version);
free(arch);
return result;
}
static int check_package(const char *nvr, const char *arch, map_string_t *osinfo, char **msg)
{
char *releaseid = get_release_id(osinfo, arch);
PRFileDesc *tcp_sock, *ssl_sock;
ssl_connect(&cfg, &tcp_sock, &ssl_sock);
struct strbuf *http_request = strbuf_new();
strbuf_append_strf(http_request,
"GET /checkpackage HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n"
"X-Package-NVR: %s\r\n"
"X-Package-Arch: %s\r\n"
"X-OS-Release: %s\r\n"
"%s"
"%s"
"\r\n",
cfg.url, nvr, arch, releaseid,
lang.accept_charset,
lang.accept_language
);
PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
alert_connection_error(cfg.url);
error_msg_and_die(_("Failed to send HTTP header of length %d: NSS error %d"),
http_request->len, PR_GetError());
}
strbuf_free(http_request);
char *http_response = tcp_read_response(tcp_sock);
ssl_disconnect(ssl_sock);
if (http_show_headers)
http_print_headers(stderr, http_response);
int response_code = http_get_response_code(http_response);
/* we are expecting either 302 or 404 */
if (response_code != 302 && response_code != 404)
{
char *http_body = http_get_body(http_response);
alert_server_error(cfg.url);
error_msg_and_die(_("Unexpected HTTP response from server: %d\n%s"),
response_code, http_body);
}
if (msg)
{
if (response_code == 404)
{
const char *os = get_map_string_item_or_empty(osinfo, OSINFO_PRETTY_NAME);
if (!os)
os = get_map_string_item_or_empty(osinfo, OSINFO_NAME);
*msg = xasprintf(_("Retrace server is unable to process package "
"'%s.%s'.\nIs it a part of official '%s' repositories?"),
nvr, arch, os);
}
else
*msg = NULL;
}
free(http_response);
free(releaseid);
return response_code == 302;
}
static int create(bool delete_temp_archive,
char **task_id,
char **task_password)
{
if (delay)
{
puts(_("Querying server settings"));
fflush(stdout);
}
struct retrace_settings *settings = get_settings();
if (settings->running_tasks >= settings->max_running_tasks)
{
alert(_("The server is fully occupied. Try again later."));
error_msg_and_die(_("The server denied your request."));
}
long long unpacked_size = 0;
struct stat file_stat;
/* get raw size */
if (coredump)
{
xstat(coredump, &file_stat);
unpacked_size = (long long)file_stat.st_size;
}
else if (dump_dir_name != NULL)
{
struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags*/ 0);
if (!dd)
xfunc_die(); /* dd_opendir already emitted error message */
if (dd_exist(dd, FILENAME_VMCORE))
task_type = TASK_VMCORE;
dd_close(dd);
char *path;
int i = 0;
const char **required_files = task_type == TASK_VMCORE ? required_vmcore : required_retrace;
while (required_files[i])
{
path = concat_path_file(dump_dir_name, required_files[i]);
xstat(path, &file_stat);
free(path);
if (!S_ISREG(file_stat.st_mode))
error_msg_and_die(_("'%s' must be a regular file in "
"order to use Retrace server."),
required_files[i]);
unpacked_size += (long long)file_stat.st_size;
++i;
}
if (task_type == TASK_RETRACE || task_type == TASK_DEBUG)
{
for (i = 0; optional_retrace[i]; ++i)
{
path = concat_path_file(dump_dir_name, optional_retrace[i]);
if (stat(path, &file_stat) != -1)
{
if (!S_ISREG(file_stat.st_mode))
error_msg_and_die(_("'%s' must be a regular file in "
"order to use Retrace server."),
required_files[i]);
unpacked_size += (long long)file_stat.st_size;
}
free(path);
}
}
}
if (unpacked_size > settings->max_unpacked_size)
{
alert_crash_too_large();
/* Leaking size and max_size in hope the memory will be released in
* error_msg_and_die() */
gchar *size = g_format_size_full(unpacked_size, G_FORMAT_SIZE_IEC_UNITS);
gchar *max_size = g_format_size_full(settings->max_unpacked_size, G_FORMAT_SIZE_IEC_UNITS);
error_msg_and_die(_("The size of your crash is %s, "
"but the retrace server only accepts "
"crashes smaller or equal to %s."),
size, max_size);
}
if (settings->supported_formats)
{
int i;
bool supported = false;
for (i = 0; i < MAX_FORMATS && settings->supported_formats[i]; ++i)
if (strcmp("application/x-xz-compressed-tar", settings->supported_formats[i]) == 0)
{
supported = true;
break;
}
if (!supported)
{
alert_server_error(cfg.url);
error_msg_and_die(_("The server does not support "
"xz-compressed tarballs."));
}
}
if (task_type != TASK_VMCORE && dump_dir_name)
{
struct dump_dir *dd = dd_opendir(dump_dir_name, DD_OPEN_READONLY);
if (!dd)
xfunc_die();
problem_data_t *pd = create_problem_data_from_dump_dir(dd);
dd_close(dd);
char *package = problem_data_get_content_or_NULL(pd, FILENAME_PACKAGE);
char *arch = problem_data_get_content_or_NULL(pd, FILENAME_ARCHITECTURE);
map_string_t *osinfo = new_map_string();
problem_data_get_osinfo(pd, osinfo);
/* not needed for TASK_VMCORE - the information is kept in the vmcore itself */
if (settings->supported_releases)
{
char *releaseid = get_release_id(osinfo, arch);
if (!releaseid)
error_msg_and_die("Unable to parse release.");
int i;
bool supported = false;
for (i = 0; i < MAX_RELEASES && settings->supported_releases[i]; ++i)
if (strcmp(releaseid, settings->supported_releases[i]) == 0)
{
supported = true;
break;
}
if (!supported)
{
char *msg = xasprintf(_("The release '%s' is not supported by the"
" Retrace server."), releaseid);
alert(msg);
free(msg);
error_msg_and_die(_("The server is not able to"
" handle your request."));
}
free(releaseid);
}
/* not relevant for vmcores - it may take a long time to get package from vmcore */
if (!no_pkgcheck)
{
char *msg;
int known = check_package(package, arch, osinfo, &msg);
if (msg)
{
alert(msg);
free(msg);
}
if (!known)
error_msg_and_die(_("Unknown package sent to Retrace server."));
}
free_map_string(osinfo);
problem_data_free(pd);
}
if (delay)
{
puts(_("Preparing an archive to upload"));
fflush(stdout);
}
int tempfd = create_archive(delete_temp_archive);
if (-1 == tempfd)
return 1;
/* Get the file size. */
fstat(tempfd, &file_stat);
gchar *human_size = g_format_size_full((long long)file_stat.st_size, G_FORMAT_SIZE_IEC_UNITS);
if ((long long)file_stat.st_size > settings->max_packed_size)
{
alert_crash_too_large();
/* Leaking human_size and max_size in hope the memory will be released in
* error_msg_and_die() */
gchar *max_size = g_format_size_full(settings->max_packed_size, G_FORMAT_SIZE_IEC_UNITS);
error_msg_and_die(_("The size of your archive is %s, "
"but the retrace server only accepts "
"archives smaller or equal to %s."),
human_size, max_size);
}
free_settings(settings);
int size_mb = file_stat.st_size / (1024 * 1024);
if (size_mb > 8) /* 8 MB - should be configurable */
{
char *question = xasprintf(_("You are going to upload %s. "
"Continue?"), human_size);
int response = ask_yes_no(question);
free(question);
if (!response)
{
set_xfunc_error_retval(EXIT_CANCEL_BY_USER);
error_msg_and_die(_("Cancelled by user"));
}
}
PRFileDesc *tcp_sock, *ssl_sock;
ssl_connect(&cfg, &tcp_sock, &ssl_sock);
/* Upload the archive. */
struct strbuf *http_request = strbuf_new();
strbuf_append_strf(http_request,
"POST /create HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Type: application/x-xz-compressed-tar\r\n"
"Content-Length: %lld\r\n"
"Connection: close\r\n"
"X-Task-Type: %d\r\n"
"%s"
"%s"
"\r\n",
cfg.url, (long long)file_stat.st_size, task_type,
lang.accept_charset,
lang.accept_language
);
PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
alert_connection_error(cfg.url);
error_msg_and_die(_("Failed to send HTTP header of length %d: NSS error %d"),
http_request->len, PR_GetError());
}
if (delay)
{
printf(_("Uploading %s\n"), human_size);
fflush(stdout);
}
g_free(human_size);
strbuf_free(http_request);
int result = 0;
int i;
char buf[32768];
time_t start, now;
time(&start);
for (i = 0;; ++i)
{
if (delay)
{
time(&now);
if (now - start >= delay)
{
time(&start);
int progress = 100 * i * sizeof(buf) / file_stat.st_size;
if (progress > 100)
continue;
printf(_("Uploading %d%%\n"), progress);
fflush(stdout);
}
}
int r = read(tempfd, buf, sizeof(buf));
if (r <= 0)
{
if (r == -1)
{
if (EINTR == errno || EAGAIN == errno || EWOULDBLOCK == errno)
continue;
perror_msg_and_die(_("Failed to read from a pipe"));
}
break;
}
written = PR_Send(tcp_sock, buf, r,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
/* Print error message, but do not exit. We need to check
if the server send some explanation regarding the
error. */
result = 1;
alert_connection_error(cfg.url);
error_msg(_("Failed to send data: NSS error %d (%s): %s"),
PR_GetError(),
PR_ErrorToName(PR_GetError()),
PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT));
break;
}
}
close(tempfd);
if (delay)
{
puts(_("Upload successful"));
fflush(stdout);
}
/* Read the HTTP header of the response from server. */
char *http_response = tcp_read_response(tcp_sock);
char *http_body = http_get_body(http_response);
if (!http_body)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing HTTP message body."));
}
if (http_show_headers)
http_print_headers(stderr, http_response);
int response_code = http_get_response_code(http_response);
if (response_code == 500 || response_code == 507)
{
alert_server_error(cfg.url);
error_msg_and_die("%s", http_body);
}
else if (response_code == 403)
{
alert(_("Your problem directory is corrupted and can not "
"be processed by the Retrace server."));
error_msg_and_die(_("The archive contains malicious files (such as symlinks) "
"and thus can not be processed."));
}
else if (response_code != 201)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Unexpected HTTP response from server: %d\n%s"), response_code, http_body);
}
free(http_body);
*task_id = http_get_header_value(http_response, "X-Task-Id");
if (!*task_id)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing X-Task-Id."));
}
*task_password = http_get_header_value(http_response, "X-Task-Password");
if (!*task_password)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing X-Task-Password."));
}
free(http_response);
ssl_disconnect(ssl_sock);
if (delay)
{
puts(_("Retrace job started"));
fflush(stdout);
}
return result;
}
static int run_create(bool delete_temp_archive)
{
char *task_id, *task_password;
int result = create(delete_temp_archive, &task_id, &task_password);
if (0 != result)
return result;
printf(_("Task Id: %s\nTask Password: %s\n"), task_id, task_password);
free(task_id);
free(task_password);
return 0;
}
/* Caller must free task_status and status_message */
static void status(const char *task_id,
const char *task_password,
char **task_status,
char **status_message)
{
PRFileDesc *tcp_sock, *ssl_sock;
ssl_connect(&cfg, &tcp_sock, &ssl_sock);
struct strbuf *http_request = strbuf_new();
strbuf_append_strf(http_request,
"GET /%s HTTP/1.1\r\n"
"Host: %s\r\n"
"X-Task-Password: %s\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n"
"%s"
"%s"
"\r\n",
task_id, cfg.url, task_password,
lang.accept_charset,
lang.accept_language
);
PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
alert_connection_error(cfg.url);
error_msg_and_die(_("Failed to send HTTP header of length %d: NSS error %d"),
http_request->len, PR_GetError());
}
strbuf_free(http_request);
char *http_response = tcp_read_response(tcp_sock);
char *http_body = http_get_body(http_response);
if (!*http_body)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing HTTP message body."));
}
if (http_show_headers)
http_print_headers(stderr, http_response);
int response_code = http_get_response_code(http_response);
if (response_code != 200)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Unexpected HTTP response from server: %d\n%s"),
response_code, http_body);
}
*task_status = http_get_header_value(http_response, "X-Task-Status");
if (!*task_status)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing X-Task-Status."));
}
*status_message = http_body;
free(http_response);
ssl_disconnect(ssl_sock);
}
static void run_status(const char *task_id, const char *task_password)
{
char *task_status;
char *status_message;
status(task_id, task_password, &task_status, &status_message);
printf(_("Task Status: %s\n%s\n"), task_status, status_message);
free(task_status);
free(status_message);
}
/* Caller must free backtrace */
static void backtrace(const char *task_id, const char *task_password,
char **backtrace)
{
PRFileDesc *tcp_sock, *ssl_sock;
ssl_connect(&cfg, &tcp_sock, &ssl_sock);
struct strbuf *http_request = strbuf_new();
strbuf_append_strf(http_request,
"GET /%s/backtrace HTTP/1.1\r\n"
"Host: %s\r\n"
"X-Task-Password: %s\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n"
"%s"
"%s"
"\r\n",
task_id, cfg.url, task_password,
lang.accept_charset,
lang.accept_language
);
PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
alert_connection_error(cfg.url);
error_msg_and_die(_("Failed to send HTTP header of length %d: NSS error %d."),
http_request->len, PR_GetError());
}
strbuf_free(http_request);
char *http_response = tcp_read_response(tcp_sock);
char *http_body = http_get_body(http_response);
if (!http_body)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing HTTP message body."));
}
if (http_show_headers)
http_print_headers(stderr, http_response);
int response_code = http_get_response_code(http_response);
if (response_code != 200)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Unexpected HTTP response from server: %d\n%s"),
response_code, http_body);
}
*backtrace = http_body;
free(http_response);
ssl_disconnect(ssl_sock);
}
static void run_backtrace(const char *task_id, const char *task_password)
{
char *backtrace_text;
backtrace(task_id, task_password, &backtrace_text);
printf("%s", backtrace_text);
free(backtrace_text);
}
/* This is not robust at all but will work for now */
static int get_exploitable_rating(const char *exploitable_text)
{
const char *colon = strrchr(exploitable_text, ':');
int result;
if (!colon || sscanf(colon, ": %d", &result) != 1)
{
log_notice("Unable to determine exploitable rating");
return -1;
}
log_notice("Exploitable rating: %d", result);
return result;
}
/* Caller must free exploitable_text */
static void exploitable(const char *task_id, const char *task_password,
char **exploitable_text)
{
PRFileDesc *tcp_sock, *ssl_sock;
ssl_connect(&cfg, &tcp_sock, &ssl_sock);
struct strbuf *http_request = strbuf_new();
strbuf_append_strf(http_request,
"GET /%s/exploitable HTTP/1.1\r\n"
"Host: %s\r\n"
"X-Task-Password: %s\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n"
"%s"
"%s"
"\r\n",
task_id, cfg.url, task_password,
lang.accept_charset,
lang.accept_language
);
PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
alert_connection_error(cfg.url);
error_msg_and_die(_("Failed to send HTTP header of length %d: NSS error %d."),
http_request->len, PR_GetError());
}
strbuf_free(http_request);
char *http_response = tcp_read_response(tcp_sock);
char *http_body = http_get_body(http_response);
if (!http_body)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing HTTP message body."));
}
if (http_show_headers)
http_print_headers(stderr, http_response);
int response_code = http_get_response_code(http_response);
free(http_response);
ssl_disconnect(ssl_sock);
/* 404 = exploitability results not available
200 = OK
anything else = error */
if (response_code == 404)
*exploitable_text = NULL;
else if (response_code == 200)
*exploitable_text = http_body;
else
{
alert_server_error(cfg.url);
error_msg_and_die(_("Unexpected HTTP response from server: %d\n%s"),
response_code, http_body);
}
}
static void run_exploitable(const char *task_id, const char *task_password)
{
char *exploitable_text;
exploitable(task_id, task_password, &exploitable_text);
if (exploitable_text)
{
printf("%s\n", exploitable_text);
free(exploitable_text);
}
else
puts("No exploitability information available.");
}
static void run_log(const char *task_id, const char *task_password)
{
PRFileDesc *tcp_sock, *ssl_sock;
ssl_connect(&cfg, &tcp_sock, &ssl_sock);
struct strbuf *http_request = strbuf_new();
strbuf_append_strf(http_request,
"GET /%s/log HTTP/1.1\r\n"
"Host: %s\r\n"
"X-Task-Password: %s\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n"
"%s"
"%s"
"\r\n",
task_id, cfg.url, task_password,
lang.accept_charset,
lang.accept_language
);
PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len,
/*flags:*/0, PR_INTERVAL_NO_TIMEOUT);
if (written == -1)
{
alert_connection_error(cfg.url);
error_msg_and_die(_("Failed to send HTTP header of length %d: NSS error %d."),
http_request->len, PR_GetError());
}
strbuf_free(http_request);
char *http_response = tcp_read_response(tcp_sock);
char *http_body = http_get_body(http_response);
if (!http_body)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Invalid response from server: missing HTTP message body."));
}
if (http_show_headers)
http_print_headers(stderr, http_response);
int response_code = http_get_response_code(http_response);
if (response_code != 200)
{
alert_server_error(cfg.url);
error_msg_and_die(_("Unexpected HTTP response from server: %d\n%s"),
response_code, http_body);
}
puts(http_body);
free(http_body);
free(http_response);
ssl_disconnect(ssl_sock);
}
static int run_batch(bool delete_temp_archive)
{
char *task_id, *task_password;
int retcode = create(delete_temp_archive, &task_id, &task_password);
if (0 != retcode)
return retcode;
char *task_status = xstrdup("");
char *status_message = xstrdup("");
int status_delay = delay ? delay : 10;
int dots = 0;
while (0 != strncmp(task_status, "FINISHED", strlen("finished")))
{
char *previous_status_message = status_message;
free(task_status);
sleep(status_delay);
status(task_id, task_password, &task_status, &status_message);
if (g_verbose > 0 || 0 != strcmp(previous_status_message, status_message))
{
if (dots)
{ /* A same message was received and a period was printed instead
* but the period wasn't followed by new line and now we are
* goning to print a new message thus we want to start at next line
*/
dots = 0;
putchar('\n');
}
puts(status_message);
fflush(stdout);
}
else
{
if (dots >= MAX_DOTS_PER_LINE)
{
dots = 0;
putchar('\n');
}
++dots;
client_log(".");
fflush(stdout);
}
free(previous_status_message);
previous_status_message = status_message;
}
if (0 == strcmp(task_status, "FINISHED_SUCCESS"))
{
char *backtrace_text;
backtrace(task_id, task_password, &backtrace_text);
char *exploitable_text = NULL;
if (task_type == TASK_RETRACE)
{
exploitable(task_id, task_password, &exploitable_text);
if (!exploitable_text)
log_notice("No exploitable data available");
}
if (dump_dir_name)
{
struct dump_dir *dd = dd_opendir(dump_dir_name, 0/* flags */);
if (!dd)
{
free(backtrace_text);
xfunc_die();
}
/* the result of TASK_VMCORE is not backtrace, but kernel log */
const char *target = task_type == TASK_VMCORE ? FILENAME_KERNEL_LOG : FILENAME_BACKTRACE;
dd_save_text(dd, target, backtrace_text);
if (exploitable_text)
{
int exploitable_rating = get_exploitable_rating(exploitable_text);
if (exploitable_rating >= MIN_EXPLOITABLE_RATING)
dd_save_text(dd, FILENAME_EXPLOITABLE, exploitable_text);
else
log_notice("Not saving exploitable data, rating < %d",
MIN_EXPLOITABLE_RATING);
}
dd_close(dd);
}
else
{
printf("%s\n", backtrace_text);
if (exploitable_text)
printf("%s\n", exploitable_text);
}
free(backtrace_text);
free(exploitable_text);
}
else
{
alert(_("Retrace failed. Try again later and if the problem persists "
"report this issue please."));
run_log(task_id, task_password);
retcode = 1;
}
free(task_status);
free(status_message);
free(task_id);
free(task_password);
return retcode;
}
int main(int argc, char **argv)
{
setlocale(LC_ALL, "");
#if ENABLE_NLS
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
#endif
abrt_init(argv);
get_language(&lang);
const char *task_id = NULL;
const char *task_password = NULL;
enum {
OPT_verbose = 1 << 0,
OPT_syslog = 1 << 1,
OPT_insecure = 1 << 2,
OPT_no_pkgchk = 1 << 3,
OPT_url = 1 << 4,
OPT_port = 1 << 5,
OPT_headers = 1 << 6,
OPT_group_1 = 1 << 7,
OPT_dir = 1 << 8,
OPT_core = 1 << 9,
OPT_delay = 1 << 10,
OPT_no_unlink = 1 << 11,
OPT_group_2 = 1 << 12,
OPT_task = 1 << 13,
OPT_password = 1 << 14
};
/* Keep enum above and order of options below in sync! */
struct options options[] = {
OPT__VERBOSE(&g_verbose),
OPT_BOOL('s', "syslog", NULL, _("log to syslog")),
OPT_BOOL('k', "insecure", NULL,
_("allow insecure connection to retrace server")),
OPT_BOOL(0, "no-pkgcheck", NULL,
_("do not check whether retrace server is able to "
"process given package before uploading the archive")),
OPT_STRING(0, "url", &(cfg.url), "URL",
_("retrace server URL")),
OPT_INTEGER(0, "port", &(cfg.port),
_("retrace server port")),
OPT_BOOL(0, "headers", NULL,
_("(debug) show received HTTP headers")),
OPT_GROUP(_("For create and batch operations")),
OPT_STRING('d', "dir", &dump_dir_name, "DIR",
_("read data from ABRT problem directory")),
OPT_STRING('c', "core", &coredump, "COREDUMP",
_("read data from coredump")),
OPT_INTEGER('l', "status-delay", &delay,
_("Delay for polling operations")),
OPT_BOOL(0, "no-unlink", NULL,
_("(debug) do not delete temporary archive created"
" from dump dir in "LARGE_DATA_TMP_DIR)),
OPT_GROUP(_("For status, backtrace, and log operations")),
OPT_STRING('t', "task", &task_id, "ID",
_("id of your task on server")),
OPT_STRING('p', "password", &task_password, "PWD",
_("password of your task on server")),
OPT_END()
};
const char *usage = _("abrt-retrace-client <operation> [options]\n"
"Operations: create/status/backtrace/log/batch/exploitable");
char *env_url = getenv("RETRACE_SERVER_URL");
if (env_url)
cfg.url = env_url;
char *env_port = getenv("RETRACE_SERVER_PORT");
if (env_port)
cfg.port = xatou(env_port);
char *env_delay = getenv("ABRT_STATUS_DELAY");
if (env_delay)
delay = xatou(env_delay);
char *env_insecure = getenv("RETRACE_SERVER_INSECURE");
if (env_insecure)
cfg.ssl_allow_insecure = strncmp(env_insecure, "insecure", strlen("insecure")) == 0;
unsigned opts = parse_opts(argc, argv, options, usage);
if (opts & OPT_syslog)
{
logmode = LOGMODE_JOURNAL;
}
const char *operation = NULL;
if (optind < argc)
operation = argv[optind];
else
show_usage_and_die(usage, options);
if (!cfg.ssl_allow_insecure)
cfg.ssl_allow_insecure = opts & OPT_insecure;
http_show_headers = opts & OPT_headers;
no_pkgcheck = opts & OPT_no_pkgchk;
/* Initialize NSS */
SECMODModule *mod;
nss_init(&mod);
/* Run the desired operation. */
int result = 0;
if (0 == strcasecmp(operation, "create"))
{
if (!dump_dir_name && !coredump)
error_msg_and_die(_("Either problem directory or coredump is needed."));
result = run_create(0 == (opts & OPT_no_unlink));
}
else if (0 == strcasecmp(operation, "batch"))
{
if (!dump_dir_name && !coredump)
error_msg_and_die(_("Either problem directory or coredump is needed."));
result = run_batch(0 == (opts & OPT_no_unlink));
}
else if (0 == strcasecmp(operation, "status"))
{
if (!task_id)
error_msg_and_die(_("Task id is needed."));
if (!task_password)
error_msg_and_die(_("Task password is needed."));
run_status(task_id, task_password);
}
else if (0 == strcasecmp(operation, "backtrace"))
{
if (!task_id)
error_msg_and_die(_("Task id is needed."));
if (!task_password)
error_msg_and_die(_("Task password is needed."));
run_backtrace(task_id, task_password);
}
else if (0 == strcasecmp(operation, "log"))
{
if (!task_id)
error_msg_and_die(_("Task id is needed."));
if (!task_password)
error_msg_and_die(_("Task password is needed."));
run_log(task_id, task_password);
}
else if (0 == strcasecmp(operation, "exploitable"))
{
if (!task_id)
error_msg_and_die(_("Task id is needed."));
if (!task_password)
error_msg_and_die(_("Task password is needed."));
run_exploitable(task_id, task_password);
}
else
error_msg_and_die(_("Unknown operation: %s."), operation);
/* Shutdown NSS. */
nss_close(mod);
return result;
}