GUACAMOLE-200: Merge fix for print blocking input handling.
This commit is contained in:
commit
a7ee5edd8b
@ -41,6 +41,7 @@ libguac_client_rdp_la_SOURCES = \
|
|||||||
rdp_gdi.c \
|
rdp_gdi.c \
|
||||||
rdp_glyph.c \
|
rdp_glyph.c \
|
||||||
rdp_keymap.c \
|
rdp_keymap.c \
|
||||||
|
rdp_print_job.c \
|
||||||
rdp_pointer.c \
|
rdp_pointer.c \
|
||||||
rdp_rail.c \
|
rdp_rail.c \
|
||||||
rdp_settings.c \
|
rdp_settings.c \
|
||||||
@ -74,6 +75,7 @@ guacdr_sources = \
|
|||||||
guac_rdpdr/rdpdr_printer.c \
|
guac_rdpdr/rdpdr_printer.c \
|
||||||
guac_rdpdr/rdpdr_service.c \
|
guac_rdpdr/rdpdr_service.c \
|
||||||
rdp_fs.c \
|
rdp_fs.c \
|
||||||
|
rdp_print_job.c \
|
||||||
rdp_stream.c \
|
rdp_stream.c \
|
||||||
unicode.c
|
unicode.c
|
||||||
|
|
||||||
@ -109,6 +111,7 @@ noinst_HEADERS = \
|
|||||||
rdp_glyph.h \
|
rdp_glyph.h \
|
||||||
rdp_keymap.h \
|
rdp_keymap.h \
|
||||||
rdp_pointer.h \
|
rdp_pointer.h \
|
||||||
|
rdp_print_job.h \
|
||||||
rdp_rail.h \
|
rdp_rail.h \
|
||||||
rdp_settings.h \
|
rdp_settings.h \
|
||||||
rdp_status.h \
|
rdp_status.h \
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "rdp.h"
|
#include "rdp.h"
|
||||||
#include "rdp_disp.h"
|
#include "rdp_disp.h"
|
||||||
|
#include "rdp_fs.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
#include "rdpdr_messages.h"
|
#include "rdpdr_messages.h"
|
||||||
#include "rdpdr_printer.h"
|
#include "rdpdr_printer.h"
|
||||||
#include "rdpdr_service.h"
|
#include "rdpdr_service.h"
|
||||||
|
#include "rdp.h"
|
||||||
|
#include "rdp_print_job.h"
|
||||||
#include "rdp_status.h"
|
#include "rdp_status.h"
|
||||||
|
|
||||||
#include <freerdp/utils/svc_plugin.h>
|
#include <freerdp/utils/svc_plugin.h>
|
||||||
@ -38,330 +40,65 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
/* Command to run GhostScript safely as a filter writing PDF */
|
|
||||||
char* const guac_rdpdr_pdf_filter_command[] = {
|
|
||||||
"gs",
|
|
||||||
"-q",
|
|
||||||
"-dNOPAUSE",
|
|
||||||
"-dBATCH",
|
|
||||||
"-dSAFER",
|
|
||||||
"-dPARANOIDSAFER",
|
|
||||||
"-sDEVICE=pdfwrite",
|
|
||||||
"-sOutputFile=-",
|
|
||||||
"-c",
|
|
||||||
".setpdfwrite",
|
|
||||||
"-sstdout=/dev/null",
|
|
||||||
"-f",
|
|
||||||
"-",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for "ack" messages received in response to printed data. Additional
|
|
||||||
* data will be sent as a result or, if no data remains, the stream will be
|
|
||||||
* terminated. It is required that the data pointer of the provided stream be
|
|
||||||
* set to the file descriptor from which the printed data should be read.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user to whom the printed data is being sent.
|
|
||||||
*
|
|
||||||
* @param stream
|
|
||||||
* The stream along which the printed data is to be sent. The data pointer
|
|
||||||
* of this stream MUST be set to the file descriptor from which the data
|
|
||||||
* being sent is to be read.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* An arbitrary, human-readable message describing the success/failure of
|
|
||||||
* the operation being acknowledged (either stream creation or receipt of
|
|
||||||
* a blob).
|
|
||||||
*
|
|
||||||
* @param status
|
|
||||||
* The status code describing the success/failure of the operation being
|
|
||||||
* acknowledged (either stream creation or receipt of a blob).
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Always zero.
|
|
||||||
*/
|
|
||||||
static int guac_rdpdr_print_filter_ack_handler(guac_user* user,
|
|
||||||
guac_stream* stream, char* message, guac_protocol_status status) {
|
|
||||||
|
|
||||||
char buffer[6048];
|
|
||||||
|
|
||||||
/* Pull file descriptor from stream data */
|
|
||||||
int fd = (intptr_t) stream->data;
|
|
||||||
|
|
||||||
/* Reading only if ack reports success */
|
|
||||||
if (status == GUAC_PROTOCOL_STATUS_SUCCESS) {
|
|
||||||
|
|
||||||
/* Write a single blob of output */
|
|
||||||
int length = read(fd, buffer, sizeof(buffer));
|
|
||||||
if (length > 0) {
|
|
||||||
guac_protocol_send_blob(user->socket, stream, buffer, length);
|
|
||||||
guac_socket_flush(user->socket);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Warn of read errors, fall through to termination */
|
|
||||||
else if (length < 0)
|
|
||||||
guac_user_log(user, GUAC_LOG_ERROR,
|
|
||||||
"Error reading from filter: %s", strerror(errno));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Note if stream aborted by user, fall through to termination */
|
|
||||||
else
|
|
||||||
guac_user_log(user, GUAC_LOG_INFO, "Print stream aborted.");
|
|
||||||
|
|
||||||
/* Explicitly close down stream */
|
|
||||||
guac_protocol_send_end(user->socket, stream);
|
|
||||||
guac_socket_flush(user->socket);
|
|
||||||
|
|
||||||
/* Clean up our end of the stream */
|
|
||||||
guac_user_free_stream(user, stream);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static int guac_rdpdr_create_print_process(guac_rdpdr_device* device) {
|
|
||||||
|
|
||||||
guac_rdpdr_printer_data* printer_data = (guac_rdpdr_printer_data*) device->data;
|
|
||||||
|
|
||||||
int child_pid;
|
|
||||||
int stdin_pipe[2];
|
|
||||||
int stdout_pipe[2];
|
|
||||||
|
|
||||||
/* Create STDIN pipe */
|
|
||||||
if (pipe(stdin_pipe)) {
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR,
|
|
||||||
"Unable to create STDIN pipe for PDF filter process: %s", strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create STDOUT pipe */
|
|
||||||
if (pipe(stdout_pipe)) {
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR,
|
|
||||||
"Unable to create STDIN pipe for PDF filter process: %s", strerror(errno));
|
|
||||||
close(stdin_pipe[0]);
|
|
||||||
close(stdin_pipe[1]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Store our side of stdin/stdout */
|
|
||||||
printer_data->printer_input = stdin_pipe[1];
|
|
||||||
printer_data->printer_output = stdout_pipe[0];
|
|
||||||
|
|
||||||
/* Fork child process */
|
|
||||||
child_pid = fork();
|
|
||||||
|
|
||||||
/* Log fork errors */
|
|
||||||
if (child_pid == -1) {
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR,
|
|
||||||
"Unable to fork PDF filter process: %s", strerror(errno));
|
|
||||||
close(stdin_pipe[0]);
|
|
||||||
close(stdin_pipe[1]);
|
|
||||||
close(stdout_pipe[0]);
|
|
||||||
close(stdout_pipe[1]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Child process */
|
|
||||||
if (child_pid == 0) {
|
|
||||||
|
|
||||||
/* Close unneeded ends of pipe */
|
|
||||||
close(stdin_pipe[1]);
|
|
||||||
close(stdout_pipe[0]);
|
|
||||||
|
|
||||||
/* Reassign file descriptors as STDIN/STDOUT */
|
|
||||||
dup2(stdin_pipe[0], STDIN_FILENO);
|
|
||||||
dup2(stdout_pipe[1], STDOUT_FILENO);
|
|
||||||
|
|
||||||
/* Run PDF filter */
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, "Running %s", guac_rdpdr_pdf_filter_command[0]);
|
|
||||||
if (execvp(guac_rdpdr_pdf_filter_command[0], guac_rdpdr_pdf_filter_command) < 0)
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR, "Unable to execute PDF filter command: %s", strerror(errno));
|
|
||||||
else
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR, "Unable to execute PDF filter command, but no error given");
|
|
||||||
|
|
||||||
/* Terminate child process */
|
|
||||||
exit(1);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Log fork success */
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, "Created PDF filter process PID=%i", child_pid);
|
|
||||||
|
|
||||||
/* Close unneeded ends of pipe */
|
|
||||||
close(stdin_pipe[0]);
|
|
||||||
close(stdout_pipe[1]);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device,
|
void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device,
|
||||||
wStream* input_stream, int completion_id) {
|
wStream* input_stream, int completion_id) {
|
||||||
|
|
||||||
guac_rdpdr_printer_data* printer_data =
|
guac_client* client = device->rdpdr->client;
|
||||||
(guac_rdpdr_printer_data*) device->data;
|
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||||
|
|
||||||
|
/* Log creation of print job */
|
||||||
|
guac_client_log(client, GUAC_LOG_INFO, "Print job created");
|
||||||
|
|
||||||
|
/* Create print job */
|
||||||
|
rdp_client->active_job = guac_client_for_owner(client,
|
||||||
|
guac_rdp_print_job_alloc, NULL);
|
||||||
|
|
||||||
|
/* Respond with success */
|
||||||
wStream* output_stream = guac_rdpdr_new_io_completion(device,
|
wStream* output_stream = guac_rdpdr_new_io_completion(device,
|
||||||
completion_id, STATUS_SUCCESS, 4);
|
completion_id, STATUS_SUCCESS, 4);
|
||||||
|
|
||||||
/* No bytes received yet */
|
|
||||||
printer_data->bytes_received = 0;
|
|
||||||
Stream_Write_UINT32(output_stream, 0); /* fileId */
|
Stream_Write_UINT32(output_stream, 0); /* fileId */
|
||||||
|
|
||||||
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream);
|
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Given data representing a print device with a pending pring job, allocates a
|
|
||||||
* new stream for the given user, associating it with the provided data and
|
|
||||||
* returning the resulting guac_stream. The stream will be pre-configured to
|
|
||||||
* send blobs of print data in response to "ack" messages received from the
|
|
||||||
* user. If the given user is NULL, no stream will be allocated, and the
|
|
||||||
* print job will be immediately aborted.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user to whom the print job is being sent, or NULL if no stream
|
|
||||||
* should be allocated.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* A pointer to the guac_rdpdr_device instance associated with the new
|
|
||||||
* print job.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The guac_stream allocated for the new print job, or NULL if no stream
|
|
||||||
* could be allocated.
|
|
||||||
*/
|
|
||||||
static void* guac_rdpdr_alloc_printer_stream(guac_user* owner, void* data) {
|
|
||||||
|
|
||||||
guac_rdpdr_device* device = (guac_rdpdr_device*) data;
|
|
||||||
guac_rdpdr_printer_data* printer_data =
|
|
||||||
(guac_rdpdr_printer_data*) device->data;
|
|
||||||
|
|
||||||
/* Abort immediately if there is no owner */
|
|
||||||
if (owner == NULL) {
|
|
||||||
close(printer_data->printer_output);
|
|
||||||
close(printer_data->printer_input);
|
|
||||||
printer_data->printer_output = -1;
|
|
||||||
printer_data->printer_input = -1;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate stream for owner */
|
|
||||||
guac_stream* stream = guac_user_alloc_stream(owner);
|
|
||||||
stream->ack_handler = guac_rdpdr_print_filter_ack_handler;
|
|
||||||
stream->data = (void*) (intptr_t) printer_data->printer_output;
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device,
|
void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device,
|
||||||
wStream* input_stream, int completion_id) {
|
wStream* input_stream, int completion_id) {
|
||||||
|
|
||||||
guac_rdpdr_printer_data* printer_data = (guac_rdpdr_printer_data*) device->data;
|
guac_client* client = device->rdpdr->client;
|
||||||
int status=0, length;
|
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||||
|
guac_rdp_print_job* job = (guac_rdp_print_job*) rdp_client->active_job;
|
||||||
|
|
||||||
unsigned char* buffer;
|
unsigned char* buffer;
|
||||||
|
int length;
|
||||||
|
int status;
|
||||||
|
|
||||||
wStream* output_stream;
|
/* Read buffer of print data */
|
||||||
|
|
||||||
Stream_Read_UINT32(input_stream, length);
|
Stream_Read_UINT32(input_stream, length);
|
||||||
Stream_Seek(input_stream, 8); /* Offset */
|
Stream_Seek(input_stream, 8); /* Offset */
|
||||||
Stream_Seek(input_stream, 20); /* Padding */
|
Stream_Seek(input_stream, 20); /* Padding */
|
||||||
buffer = Stream_Pointer(input_stream);
|
buffer = Stream_Pointer(input_stream);
|
||||||
|
|
||||||
/* Create print job, if not yet created */
|
/* Write data only if job exists, translating status for RDP */
|
||||||
if (printer_data->bytes_received == 0) {
|
if (job != NULL && (length = guac_rdp_print_job_write(job,
|
||||||
|
buffer, length)) >= 0) {
|
||||||
char filename[1024] = "guacamole-print.pdf";
|
status = STATUS_SUCCESS;
|
||||||
unsigned char* search = buffer;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* Search for filename within buffer */
|
|
||||||
for (i=0; i<length-9 && i < 2048; i++) {
|
|
||||||
|
|
||||||
/* If title. use as filename */
|
|
||||||
if (memcmp(search, "%%Title: ", 9) == 0) {
|
|
||||||
|
|
||||||
/* Skip past "%%Title: " */
|
|
||||||
search += 9;
|
|
||||||
|
|
||||||
/* Copy as much of title as reasonable */
|
|
||||||
int j;
|
|
||||||
for (j=0; j<sizeof(filename) - 5 /* extension + 1 */ && i<length; i++, j++) {
|
|
||||||
|
|
||||||
/* Get character, stop at EOL */
|
|
||||||
char c = *(search++);
|
|
||||||
if (c == '\r' || c == '\n')
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* Copy to filename */
|
|
||||||
filename[j] = c;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Append filename with extension */
|
/* Report device offline if write fails */
|
||||||
strcpy(&(filename[j]), ".pdf");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Next character */
|
|
||||||
search++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start print process */
|
|
||||||
if (guac_rdpdr_create_print_process(device) != 0) {
|
|
||||||
status = STATUS_DEVICE_OFF_LINE;
|
|
||||||
length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If print started successfully, create outbound stream */
|
|
||||||
else {
|
else {
|
||||||
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO,
|
|
||||||
"Print job created");
|
|
||||||
|
|
||||||
/* Allocate stream */
|
|
||||||
guac_stream* stream = (guac_stream*) guac_client_for_owner(
|
|
||||||
device->rdpdr->client, guac_rdpdr_alloc_printer_stream,
|
|
||||||
device);
|
|
||||||
|
|
||||||
/* Begin file if stream allocation was successful */
|
|
||||||
if (stream != NULL)
|
|
||||||
guac_protocol_send_file(device->rdpdr->client->socket, stream,
|
|
||||||
"application/pdf", filename);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* end if print job beginning */
|
|
||||||
|
|
||||||
printer_data->bytes_received += length;
|
|
||||||
|
|
||||||
/* If not yet failed, write received data */
|
|
||||||
if (status == 0) {
|
|
||||||
|
|
||||||
/* Write data to printer, translate output for RDP */
|
|
||||||
length = write(printer_data->printer_input, buffer, length);
|
|
||||||
if (length == -1) {
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_ERROR, "Error writing to printer: %s", strerror(errno));
|
|
||||||
status = STATUS_DEVICE_OFF_LINE;
|
status = STATUS_DEVICE_OFF_LINE;
|
||||||
length = 0;
|
length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
wStream* output_stream = guac_rdpdr_new_io_completion(device,
|
||||||
|
completion_id, status, 5);
|
||||||
output_stream = guac_rdpdr_new_io_completion(device, completion_id,
|
|
||||||
status, 5);
|
|
||||||
|
|
||||||
Stream_Write_UINT32(output_stream, length);
|
Stream_Write_UINT32(output_stream, length);
|
||||||
Stream_Write_UINT8(output_stream, 0); /* Padding */
|
Stream_Write_UINT8(output_stream, 0); /* Padding */
|
||||||
@ -373,21 +110,25 @@ void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device,
|
|||||||
void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device,
|
void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device,
|
||||||
wStream* input_stream, int completion_id) {
|
wStream* input_stream, int completion_id) {
|
||||||
|
|
||||||
guac_rdpdr_printer_data* printer_data =
|
guac_client* client = device->rdpdr->client;
|
||||||
(guac_rdpdr_printer_data*) device->data;
|
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||||
|
guac_rdp_print_job* job = (guac_rdp_print_job*) rdp_client->active_job;
|
||||||
|
|
||||||
|
/* End print job */
|
||||||
|
if (job != NULL) {
|
||||||
|
guac_rdp_print_job_free(job);
|
||||||
|
rdp_client->active_job = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
wStream* output_stream = guac_rdpdr_new_io_completion(device,
|
wStream* output_stream = guac_rdpdr_new_io_completion(device,
|
||||||
completion_id, STATUS_SUCCESS, 4);
|
completion_id, STATUS_SUCCESS, 4);
|
||||||
|
|
||||||
Stream_Write_UINT32(output_stream, 0); /* Padding */
|
Stream_Write_UINT32(output_stream, 0); /* Padding */
|
||||||
|
|
||||||
/* Close input - the Guacamole stream will continue while output remains */
|
|
||||||
close(printer_data->printer_input);
|
|
||||||
printer_data->printer_input = -1;
|
|
||||||
|
|
||||||
guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, "Print job closed");
|
|
||||||
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream);
|
svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream);
|
||||||
|
|
||||||
|
/* Log end of print job */
|
||||||
|
guac_client_log(client, GUAC_LOG_INFO, "Print job closed");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void guac_rdpdr_device_printer_announce_handler(guac_rdpdr_device* device,
|
static void guac_rdpdr_device_printer_announce_handler(guac_rdpdr_device* device,
|
||||||
@ -446,21 +187,7 @@ static void guac_rdpdr_device_printer_iorequest_handler(guac_rdpdr_device* devic
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void guac_rdpdr_device_printer_free_handler(guac_rdpdr_device* device) {
|
static void guac_rdpdr_device_printer_free_handler(guac_rdpdr_device* device) {
|
||||||
|
/* Do nothing */
|
||||||
guac_rdpdr_printer_data* printer_data =
|
|
||||||
(guac_rdpdr_printer_data*) device->data;
|
|
||||||
|
|
||||||
/* Close print job input (STDIN for filter process) if open */
|
|
||||||
if (printer_data->printer_input != -1)
|
|
||||||
close(printer_data->printer_input);
|
|
||||||
|
|
||||||
/* Close print job output (STDOUT for filter process) if open */
|
|
||||||
if (printer_data->printer_output != -1)
|
|
||||||
close(printer_data->printer_output);
|
|
||||||
|
|
||||||
/* Free underlying data */
|
|
||||||
free(device->data);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) {
|
void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) {
|
||||||
@ -469,7 +196,6 @@ void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) {
|
|||||||
|
|
||||||
/* Get new device */
|
/* Get new device */
|
||||||
guac_rdpdr_device* device = &(rdpdr->devices[id]);
|
guac_rdpdr_device* device = &(rdpdr->devices[id]);
|
||||||
guac_rdpdr_printer_data* printer_data;
|
|
||||||
|
|
||||||
/* Init device */
|
/* Init device */
|
||||||
device->rdpdr = rdpdr;
|
device->rdpdr = rdpdr;
|
||||||
@ -481,11 +207,5 @@ void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) {
|
|||||||
device->iorequest_handler = guac_rdpdr_device_printer_iorequest_handler;
|
device->iorequest_handler = guac_rdpdr_device_printer_iorequest_handler;
|
||||||
device->free_handler = guac_rdpdr_device_printer_free_handler;
|
device->free_handler = guac_rdpdr_device_printer_free_handler;
|
||||||
|
|
||||||
/* Init data */
|
|
||||||
printer_data = malloc(sizeof(guac_rdpdr_printer_data));
|
|
||||||
printer_data->printer_input = -1;
|
|
||||||
printer_data->printer_output = -1;
|
|
||||||
device->data = printer_data;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,42 +31,11 @@
|
|||||||
#include "compat/winpr-stream.h"
|
#include "compat/winpr-stream.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* Data specific to an instance of the printer device.
|
|
||||||
*/
|
|
||||||
typedef struct guac_rdpdr_printer_data {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File descriptor that should be written to when sending documents to the
|
|
||||||
* printer. If no print job is in progress, this will be -1.
|
|
||||||
*/
|
|
||||||
int printer_input;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File descriptor that should be read from when receiving output from the
|
|
||||||
* printer. If no print job is in progress, this will be -1.
|
|
||||||
*/
|
|
||||||
int printer_output;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of bytes received in the current print job.
|
|
||||||
*/
|
|
||||||
int bytes_received;
|
|
||||||
|
|
||||||
} guac_rdpdr_printer_data;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new printer device within the RDPDR plugin. This must be done
|
* Registers a new printer device within the RDPDR plugin. This must be done
|
||||||
* before RDPDR connection finishes.
|
* before RDPDR connection finishes.
|
||||||
*/
|
*/
|
||||||
void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr);
|
void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr);
|
||||||
|
|
||||||
/**
|
|
||||||
* The command to run when filtering postscript to produce PDF. This must be
|
|
||||||
* a NULL-terminated array of arguments, where the first argument is the name
|
|
||||||
* of the file to run.
|
|
||||||
*/
|
|
||||||
extern char* const guac_rdpdr_pdf_filter_command[];
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "rdp_cliprdr.h"
|
#include "rdp_cliprdr.h"
|
||||||
#include "rdp_disp.h"
|
#include "rdp_disp.h"
|
||||||
#include "rdp_fs.h"
|
#include "rdp_fs.h"
|
||||||
|
#include "rdp_print_job.h"
|
||||||
#include "rdp_gdi.h"
|
#include "rdp_gdi.h"
|
||||||
#include "rdp_glyph.h"
|
#include "rdp_glyph.h"
|
||||||
#include "rdp_pointer.h"
|
#include "rdp_pointer.h"
|
||||||
@ -853,6 +854,12 @@ static int guac_rdp_handle_connection(guac_client* client) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Clean up print job, if active */
|
||||||
|
if (rdp_client->active_job != NULL) {
|
||||||
|
guac_rdp_print_job_kill(rdp_client->active_job);
|
||||||
|
guac_rdp_print_job_free(rdp_client->active_job);
|
||||||
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&(rdp_client->rdp_lock));
|
pthread_mutex_lock(&(rdp_client->rdp_lock));
|
||||||
|
|
||||||
/* Disconnect client and channels */
|
/* Disconnect client and channels */
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "keyboard.h"
|
#include "keyboard.h"
|
||||||
#include "rdp_disp.h"
|
#include "rdp_disp.h"
|
||||||
#include "rdp_fs.h"
|
#include "rdp_fs.h"
|
||||||
|
#include "rdp_print_job.h"
|
||||||
#include "rdp_settings.h"
|
#include "rdp_settings.h"
|
||||||
|
|
||||||
#include <freerdp/freerdp.h>
|
#include <freerdp/freerdp.h>
|
||||||
@ -120,6 +121,11 @@ typedef struct guac_rdp_client {
|
|||||||
*/
|
*/
|
||||||
guac_rdp_fs* filesystem;
|
guac_rdp_fs* filesystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently-active print job, or NULL if no print job is active.
|
||||||
|
*/
|
||||||
|
guac_rdp_print_job* active_job;
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
/**
|
/**
|
||||||
* The user and credentials used to authenticate for SFTP.
|
* The user and credentials used to authenticate for SFTP.
|
||||||
|
651
src/protocols/rdp/rdp_print_job.c
Normal file
651
src/protocols/rdp/rdp_print_job.c
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "rdp_print_job.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/socket.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command to run when filtering postscript to produce PDF. This must be
|
||||||
|
* a NULL-terminated array of arguments, where the first argument is the name
|
||||||
|
* of the file to run.
|
||||||
|
*/
|
||||||
|
char* const guac_rdp_pdf_filter_command[] = {
|
||||||
|
"gs",
|
||||||
|
"-q",
|
||||||
|
"-dNOPAUSE",
|
||||||
|
"-dBATCH",
|
||||||
|
"-dSAFER",
|
||||||
|
"-dPARANOIDSAFER",
|
||||||
|
"-sDEVICE=pdfwrite",
|
||||||
|
"-sOutputFile=-",
|
||||||
|
"-c",
|
||||||
|
".setpdfwrite",
|
||||||
|
"-sstdout=/dev/null",
|
||||||
|
"-f",
|
||||||
|
"-",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the state of the given print job. Any threads currently blocked by a
|
||||||
|
* call to guac_rdp_print_job_wait_for_ack() will be unblocked.
|
||||||
|
*
|
||||||
|
* @param job
|
||||||
|
* The print job whose state should be updated.
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
* The new state to assign to the given print job.
|
||||||
|
*/
|
||||||
|
static void guac_rdp_print_job_set_state(guac_rdp_print_job* job,
|
||||||
|
guac_rdp_print_job_state state) {
|
||||||
|
|
||||||
|
pthread_mutex_lock(&(job->state_lock));
|
||||||
|
|
||||||
|
/* Update stream state, signalling modification */
|
||||||
|
job->state = state;
|
||||||
|
pthread_cond_signal(&(job->state_modified));
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&(job->state_lock));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspends execution of the current thread until the state of the given print
|
||||||
|
* job is not GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK. If the state of the print
|
||||||
|
* job is GUAC_RDP_PRINT_JOB_ACK_RECEIVED, the state is automatically reset
|
||||||
|
* back to GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK prior to returning.
|
||||||
|
*
|
||||||
|
* @param job
|
||||||
|
* The print job to wait for.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if the state of the print job is GUAC_RDP_PRINT_JOB_CLOSED,
|
||||||
|
* non-zero if the state was GUAC_RDP_PRINT_JOB_ACK_RECEIVED and has been
|
||||||
|
* automatically reset to GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK.
|
||||||
|
*/
|
||||||
|
static int guac_rdp_print_job_wait_for_ack(guac_rdp_print_job* job) {
|
||||||
|
|
||||||
|
/* Wait for ack if stream open and not yet received */
|
||||||
|
pthread_mutex_lock(&(job->state_lock));
|
||||||
|
if (job->state == GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK)
|
||||||
|
pthread_cond_wait(&job->state_modified, &job->state_lock);
|
||||||
|
|
||||||
|
/* Reset state if ack received */
|
||||||
|
int got_ack = (job->state == GUAC_RDP_PRINT_JOB_ACK_RECEIVED);
|
||||||
|
if (got_ack)
|
||||||
|
job->state = GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK;
|
||||||
|
|
||||||
|
/* Return whether ack was successfully received */
|
||||||
|
pthread_mutex_unlock(&(job->state_lock));
|
||||||
|
return got_ack;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a "file" instruction to the given user describing the PDF file that
|
||||||
|
* will be sent using the output of the given print job. If the given user no
|
||||||
|
* longer exists, the print stream will be automatically terminated.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user receiving the "file" instruction.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to the guac_rdp_print_job representing the print job being
|
||||||
|
* streamed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* guac_rdp_print_job_begin_stream(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
guac_rdp_print_job* job = (guac_rdp_print_job*) data;
|
||||||
|
guac_client_log(job->client, GUAC_LOG_DEBUG, "Beginning print stream: %s",
|
||||||
|
job->filename);
|
||||||
|
|
||||||
|
/* Kill job and do nothing if user no longer exists */
|
||||||
|
if (user == NULL) {
|
||||||
|
guac_rdp_print_job_kill(job);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send document as a PDF file stream */
|
||||||
|
guac_protocol_send_file(user->socket, job->stream,
|
||||||
|
"application/pdf", job->filename);
|
||||||
|
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a "blob" instruction to the given user containing the provided data
|
||||||
|
* along the stream associated with the provided print job. If the given user
|
||||||
|
* no longer exists, the print stream will be automatically terminated.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user receiving the "blob" instruction.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to an guac_rdp_print_blob structure containing the data to
|
||||||
|
* be written, the number of bytes being written, and the print job being
|
||||||
|
* streamed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* guac_rdp_print_job_send_blob(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
guac_rdp_print_blob* blob = (guac_rdp_print_blob*) data;
|
||||||
|
guac_rdp_print_job* job = blob->job;
|
||||||
|
|
||||||
|
guac_client_log(job->client, GUAC_LOG_DEBUG, "Sending %i byte(s) "
|
||||||
|
"of filtered output.", blob->length);
|
||||||
|
|
||||||
|
/* Kill job and do nothing if user no longer exists */
|
||||||
|
if (user == NULL) {
|
||||||
|
guac_rdp_print_job_kill(job);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send single blob of print data */
|
||||||
|
guac_protocol_send_blob(user->socket, job->stream,
|
||||||
|
blob->buffer, blob->length);
|
||||||
|
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an "end" instruction to the given user, closing the stream associated
|
||||||
|
* with the given print job. If the given user no longer exists, the print
|
||||||
|
* stream will be automatically terminated.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user receiving the "end" instruction.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to the guac_rdp_print_job representing the print job being
|
||||||
|
* streamed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* guac_rdp_print_job_end_stream(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
guac_rdp_print_job* job = (guac_rdp_print_job*) data;
|
||||||
|
guac_client_log(job->client, GUAC_LOG_DEBUG, "End of print stream.");
|
||||||
|
|
||||||
|
/* Kill job and do nothing if user no longer exists */
|
||||||
|
if (user == NULL) {
|
||||||
|
guac_rdp_print_job_kill(job);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Explicitly close down stream */
|
||||||
|
guac_protocol_send_end(user->socket, job->stream);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
|
||||||
|
/* Clean up our end of the stream */
|
||||||
|
guac_user_free_stream(job->user, job->stream);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for "ack" messages received in response to printed data. Additional
|
||||||
|
* data will be sent as a result or, if no data remains, the stream will be
|
||||||
|
* terminated. It is required that the data pointer of the provided stream be
|
||||||
|
* set to the file descriptor from which the printed data should be read.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user to whom the printed data is being sent.
|
||||||
|
*
|
||||||
|
* @param stream
|
||||||
|
* The stream along which the printed data is to be sent. The data pointer
|
||||||
|
* of this stream MUST be set to the file descriptor from which the data
|
||||||
|
* being sent is to be read.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* An arbitrary, human-readable message describing the success/failure of
|
||||||
|
* the operation being acknowledged (either stream creation or receipt of
|
||||||
|
* a blob).
|
||||||
|
*
|
||||||
|
* @param status
|
||||||
|
* The status code describing the success/failure of the operation being
|
||||||
|
* acknowledged (either stream creation or receipt of a blob).
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always zero.
|
||||||
|
*/
|
||||||
|
static int guac_rdp_print_filter_ack_handler(guac_user* user,
|
||||||
|
guac_stream* stream, char* message, guac_protocol_status status) {
|
||||||
|
|
||||||
|
guac_rdp_print_job* job = (guac_rdp_print_job*) stream->data;
|
||||||
|
|
||||||
|
/* Update state for successful acks */
|
||||||
|
if (status == GUAC_PROTOCOL_STATUS_SUCCESS)
|
||||||
|
guac_rdp_print_job_set_state(job, GUAC_RDP_PRINT_JOB_ACK_RECEIVED);
|
||||||
|
|
||||||
|
/* Terminate stream if ack signals an error */
|
||||||
|
else {
|
||||||
|
|
||||||
|
/* Note that the stream was aborted by the user */
|
||||||
|
guac_client_log(job->client, GUAC_LOG_INFO, "User explicitly aborted "
|
||||||
|
"print stream.");
|
||||||
|
|
||||||
|
/* Kill job (the results will no longer be received) */
|
||||||
|
guac_rdp_print_job_kill(job);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forks a new print filtering process which accepts PostScript input and
|
||||||
|
* produces PDF output. File descriptors for writing input and reading output
|
||||||
|
* will automatically be allocated and must be manually closed when processing
|
||||||
|
* is complete.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The guac_client associated with the print job for which this filter
|
||||||
|
* process is being created.
|
||||||
|
*
|
||||||
|
* @param input_fd
|
||||||
|
* A pointer to an int which should receive the input file descriptor of
|
||||||
|
* the filter process. PostScript input for the filter process should be
|
||||||
|
* written to this file descriptor.
|
||||||
|
*
|
||||||
|
* @param output_fd
|
||||||
|
* A pointer to an int which should receive the output file descriptor of
|
||||||
|
* the filter process. PDF output from the filter process must be
|
||||||
|
* continuously read from this file descriptor or the pipeline may block.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The PID of the filter process, or -1 if the filter process could not be
|
||||||
|
* created. If the filter process could not be created, the values assigned
|
||||||
|
* through input_fd and output_fd are undefined.
|
||||||
|
*/
|
||||||
|
static pid_t guac_rdp_create_filter_process(guac_client* client,
|
||||||
|
int* input_fd, int* output_fd) {
|
||||||
|
|
||||||
|
int child_pid;
|
||||||
|
int stdin_pipe[2];
|
||||||
|
int stdout_pipe[2];
|
||||||
|
|
||||||
|
/* Create STDIN pipe */
|
||||||
|
if (pipe(stdin_pipe)) {
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR, "Unable to create STDIN "
|
||||||
|
"pipe for PDF filter process: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create STDOUT pipe */
|
||||||
|
if (pipe(stdout_pipe)) {
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR, "Unable to create STDOUT "
|
||||||
|
"pipe for PDF filter process: %s", strerror(errno));
|
||||||
|
close(stdin_pipe[0]);
|
||||||
|
close(stdin_pipe[1]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store parent side of stdin/stdout */
|
||||||
|
*input_fd = stdin_pipe[1];
|
||||||
|
*output_fd = stdout_pipe[0];
|
||||||
|
|
||||||
|
/* Fork child process */
|
||||||
|
child_pid = fork();
|
||||||
|
|
||||||
|
/* Log fork errors */
|
||||||
|
if (child_pid == -1) {
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR, "Unable to fork PDF filter "
|
||||||
|
"process: %s", strerror(errno));
|
||||||
|
close(stdin_pipe[0]);
|
||||||
|
close(stdin_pipe[1]);
|
||||||
|
close(stdout_pipe[0]);
|
||||||
|
close(stdout_pipe[1]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Child process */
|
||||||
|
if (child_pid == 0) {
|
||||||
|
|
||||||
|
/* Close unneeded ends of pipe */
|
||||||
|
close(stdin_pipe[1]);
|
||||||
|
close(stdout_pipe[0]);
|
||||||
|
|
||||||
|
/* Reassign file descriptors as STDIN/STDOUT */
|
||||||
|
dup2(stdin_pipe[0], STDIN_FILENO);
|
||||||
|
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||||
|
|
||||||
|
/* Run PDF filter */
|
||||||
|
guac_client_log(client, GUAC_LOG_INFO, "Running %s",
|
||||||
|
guac_rdp_pdf_filter_command[0]);
|
||||||
|
if (execvp(guac_rdp_pdf_filter_command[0],
|
||||||
|
guac_rdp_pdf_filter_command) < 0)
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR, "Unable to execute PDF "
|
||||||
|
"filter command: %s", strerror(errno));
|
||||||
|
else
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR, "Unable to execute PDF "
|
||||||
|
"filter command, but no error given");
|
||||||
|
|
||||||
|
/* Terminate child process */
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log fork success */
|
||||||
|
guac_client_log(client, GUAC_LOG_INFO, "Created PDF filter process "
|
||||||
|
"PID=%i", child_pid);
|
||||||
|
|
||||||
|
/* Close unneeded ends of pipe */
|
||||||
|
close(stdin_pipe[0]);
|
||||||
|
close(stdout_pipe[1]);
|
||||||
|
return child_pid;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread which continuously reads from the output file descriptor associated
|
||||||
|
* with the given print job, writing filtered PDF output to the associated
|
||||||
|
* Guacamole stream, and terminating only after the print job has completed
|
||||||
|
* processing or the associated Guacamole stream has closed.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to the guac_rdp_print_job representing the print job that
|
||||||
|
* should be read.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* guac_rdp_print_job_output_thread(void* data) {
|
||||||
|
|
||||||
|
int length;
|
||||||
|
char buffer[6048];
|
||||||
|
|
||||||
|
guac_rdp_print_job* job = (guac_rdp_print_job*) data;
|
||||||
|
guac_client_log(job->client, GUAC_LOG_DEBUG, "Reading output from filter "
|
||||||
|
"process...");
|
||||||
|
|
||||||
|
/* Read continuously while data remains */
|
||||||
|
while ((length = read(job->output_fd, buffer, sizeof(buffer))) > 0) {
|
||||||
|
|
||||||
|
/* Wait for client to be ready for blob */
|
||||||
|
if (guac_rdp_print_job_wait_for_ack(job)) {
|
||||||
|
|
||||||
|
guac_rdp_print_blob blob = {
|
||||||
|
.job = job,
|
||||||
|
.buffer = buffer,
|
||||||
|
.length = length
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Write a single blob of output */
|
||||||
|
guac_client_for_user(job->client, job->user,
|
||||||
|
guac_rdp_print_job_send_blob, &blob);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Abort if stream is closed */
|
||||||
|
else {
|
||||||
|
guac_client_log(job->client, GUAC_LOG_DEBUG, "Print stream "
|
||||||
|
"explicitly aborted.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warn of read errors */
|
||||||
|
if (length < 0)
|
||||||
|
guac_client_log(job->client, GUAC_LOG_ERROR,
|
||||||
|
"Error reading from filter: %s", strerror(errno));
|
||||||
|
|
||||||
|
/* Terminate stream */
|
||||||
|
guac_client_for_user(job->client, job->user,
|
||||||
|
guac_rdp_print_job_end_stream, job);
|
||||||
|
|
||||||
|
/* Ensure all associated file descriptors are closed */
|
||||||
|
close(job->input_fd);
|
||||||
|
close(job->output_fd);
|
||||||
|
|
||||||
|
guac_client_log(job->client, GUAC_LOG_DEBUG, "Print job completed.");
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void* guac_rdp_print_job_alloc(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
/* Allocate nothing if user does not exist */
|
||||||
|
if (user == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Allocate stream for print job output */
|
||||||
|
guac_stream* stream = guac_user_alloc_stream(user);
|
||||||
|
if (stream == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Bail early if allocation fails */
|
||||||
|
guac_rdp_print_job* job = malloc(sizeof(guac_rdp_print_job));
|
||||||
|
if (job == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Associate job with stream and dependent data */
|
||||||
|
job->client = user->client;
|
||||||
|
job->user = user;
|
||||||
|
job->stream = stream;
|
||||||
|
job->bytes_received = 0;
|
||||||
|
|
||||||
|
/* Set default filename for job */
|
||||||
|
strcpy(job->filename, GUAC_RDP_PRINT_JOB_DEFAULT_FILENAME);
|
||||||
|
|
||||||
|
/* Prepare stream for receipt of acks */
|
||||||
|
stream->ack_handler = guac_rdp_print_filter_ack_handler;
|
||||||
|
stream->data = job;
|
||||||
|
|
||||||
|
/* Create print filter process */
|
||||||
|
job->filter_pid = guac_rdp_create_filter_process(job->client,
|
||||||
|
&job->input_fd, &job->output_fd);
|
||||||
|
|
||||||
|
/* Abort if print filter process cannot be created */
|
||||||
|
if (job->filter_pid == -1) {
|
||||||
|
guac_user_free_stream(user, stream);
|
||||||
|
free(job);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init stream state signal and lock */
|
||||||
|
job->state = GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK;
|
||||||
|
pthread_cond_init(&job->state_modified, NULL);
|
||||||
|
pthread_mutex_init(&job->state_lock, NULL);
|
||||||
|
|
||||||
|
/* Start output thread */
|
||||||
|
pthread_create(&job->output_thread, NULL,
|
||||||
|
guac_rdp_print_job_output_thread, job);
|
||||||
|
|
||||||
|
/* Print job allocated successfully */
|
||||||
|
return job;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to parse the given PostScript "%%Title:" header, storing the
|
||||||
|
* contents within the filename of the given print job. If the given buffer
|
||||||
|
* does not immediately begin with the "%%Title:" header, this function has no
|
||||||
|
* effect.
|
||||||
|
*
|
||||||
|
* @param job
|
||||||
|
* The job whose filename should be set if the "%%Title:" header is
|
||||||
|
* successfully parsed.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to parse as the "%%Title:" header.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The number of bytes within the buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Non-zero if the given buffer began with the "%%Title:" header and this
|
||||||
|
* header was successfully parsed, zero otherwise.
|
||||||
|
*/
|
||||||
|
static int guac_rdp_print_job_parse_title_header(guac_rdp_print_job* job,
|
||||||
|
void* buffer, int length) {
|
||||||
|
|
||||||
|
int i;
|
||||||
|
char* current = buffer;
|
||||||
|
char* filename = job->filename;
|
||||||
|
|
||||||
|
/* Verify that the buffer begins with "%%Title: " */
|
||||||
|
if (strncmp(current, "%%Title: ", 9) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Skip past "%%Title: " */
|
||||||
|
current += 9;
|
||||||
|
length -= 9;
|
||||||
|
|
||||||
|
/* Calculate space remaining in filename */
|
||||||
|
int remaining = sizeof(job->filename) - 5 /* ".pdf\0" */;
|
||||||
|
|
||||||
|
/* Do not exceed bounds of provided buffer */
|
||||||
|
if (length < remaining)
|
||||||
|
remaining = length;
|
||||||
|
|
||||||
|
/* Copy as much of title as reasonable */
|
||||||
|
for (i = 0; i < remaining; i++) {
|
||||||
|
|
||||||
|
/* Get character, stop at EOL */
|
||||||
|
char c = *(current++);
|
||||||
|
if (c == '\r' || c == '\n')
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Copy to filename */
|
||||||
|
*(filename++) = c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append extension to filename */
|
||||||
|
strcpy(filename, ".pdf");
|
||||||
|
|
||||||
|
/* Title successfully parsed */
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches through the given buffer for PostScript headers denoting the title
|
||||||
|
* of the document, assigning the filename of the given print job using the
|
||||||
|
* discovered title. If no title can be found within
|
||||||
|
* GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH bytes, this function has no effect.
|
||||||
|
*
|
||||||
|
* @param job
|
||||||
|
* The job whose filename should be set if the document title can be found
|
||||||
|
* within the given buffer.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to search for the document title.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The number of bytes within the buffer.
|
||||||
|
*/
|
||||||
|
static void guac_rdp_print_job_read_filename(guac_rdp_print_job* job,
|
||||||
|
void* buffer, int length) {
|
||||||
|
|
||||||
|
char* current = buffer;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Restrict search area */
|
||||||
|
if (length > GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH)
|
||||||
|
length = GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH;
|
||||||
|
|
||||||
|
/* Search for document title within buffer */
|
||||||
|
for (i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
/* If document title has been found, we're done */
|
||||||
|
if (guac_rdp_print_job_parse_title_header(job, current, length))
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Advance to next character */
|
||||||
|
length--;
|
||||||
|
current++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_rdp_print_job_write(guac_rdp_print_job* job,
|
||||||
|
void* buffer, int length) {
|
||||||
|
|
||||||
|
/* Create print job, if not yet created */
|
||||||
|
if (job->bytes_received == 0) {
|
||||||
|
|
||||||
|
/* Attempt to read document title from first buffer of data */
|
||||||
|
guac_rdp_print_job_read_filename(job, buffer, length);
|
||||||
|
|
||||||
|
/* Begin print stream */
|
||||||
|
guac_client_for_user(job->client, job->user,
|
||||||
|
guac_rdp_print_job_begin_stream, job);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update counter of bytes received */
|
||||||
|
job->bytes_received += length;
|
||||||
|
|
||||||
|
/* Write data to filter process */
|
||||||
|
return write(job->input_fd, buffer, length);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guac_rdp_print_job_free(guac_rdp_print_job* job) {
|
||||||
|
|
||||||
|
/* No more input will be provided */
|
||||||
|
close(job->input_fd);
|
||||||
|
|
||||||
|
/* Wait for job to terminate */
|
||||||
|
pthread_join(job->output_thread, NULL);
|
||||||
|
|
||||||
|
/* Free base structure */
|
||||||
|
free(job);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guac_rdp_print_job_kill(guac_rdp_print_job* job) {
|
||||||
|
|
||||||
|
/* Stop all handling of I/O */
|
||||||
|
close(job->input_fd);
|
||||||
|
close(job->output_fd);
|
||||||
|
|
||||||
|
/* Mark stream as closed */
|
||||||
|
guac_rdp_print_job_set_state(job, GUAC_RDP_PRINT_JOB_CLOSED);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
235
src/protocols/rdp/rdp_print_job.h
Normal file
235
src/protocols/rdp/rdp_print_job.h
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUAC_RDP_PRINT_JOB_H
|
||||||
|
#define GUAC_RDP_PRINT_JOB_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes in the filename of an RDP print job sent as a
|
||||||
|
* file over the Guacamole protocol, including NULL terminator.
|
||||||
|
*/
|
||||||
|
#define GUAC_RDP_PRINT_JOB_FILENAME_MAX_LENGTH 1024
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default filename to use for the PDF output of an RDP print job if no
|
||||||
|
* document title can be found within the printed data.
|
||||||
|
*/
|
||||||
|
#define GUAC_RDP_PRINT_JOB_DEFAULT_FILENAME "guacamole-print.pdf"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes to search through at the beginning of a
|
||||||
|
* PostScript document when locating the document title.
|
||||||
|
*/
|
||||||
|
#define GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH 2048
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current state of an RDP print job.
|
||||||
|
*/
|
||||||
|
typedef enum guac_rdp_print_job_state {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The print stream has been opened with the Guacamole client, but the
|
||||||
|
* client has not yet confirmed that it is ready to receive data.
|
||||||
|
*/
|
||||||
|
GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The print stream has been opened with the Guacamole client, and the
|
||||||
|
* client has responded with an "ack", confirming that it is ready to
|
||||||
|
* receive data (or that data has been received and it is ready to receive
|
||||||
|
* more).
|
||||||
|
*/
|
||||||
|
GUAC_RDP_PRINT_JOB_ACK_RECEIVED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The print stream has been closed or the printer is terminating, and no
|
||||||
|
* further data should be sent to the client.
|
||||||
|
*/
|
||||||
|
GUAC_RDP_PRINT_JOB_CLOSED
|
||||||
|
|
||||||
|
} guac_rdp_print_job_state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data specific to an instance of the printer device.
|
||||||
|
*/
|
||||||
|
typedef struct guac_rdp_print_job {
|
||||||
|
|
||||||
|
guac_client* client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user receiving the output from the print job.
|
||||||
|
*/
|
||||||
|
guac_user* user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream along which the print job output should be sent.
|
||||||
|
*/
|
||||||
|
guac_stream* stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PID of the print filter process converting PostScript data into PDF.
|
||||||
|
*/
|
||||||
|
pid_t filter_pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filename that should be used when the converted PDF output is
|
||||||
|
* streamed to the Guacamole user. This value will be automatically
|
||||||
|
* determined based on the contents of the printed document.
|
||||||
|
*/
|
||||||
|
char filename[GUAC_RDP_PRINT_JOB_FILENAME_MAX_LENGTH];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File descriptor that should be written to when sending documents to the
|
||||||
|
* printer.
|
||||||
|
*/
|
||||||
|
int input_fd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File descriptor that should be read from when receiving output from the
|
||||||
|
* printer.
|
||||||
|
*/
|
||||||
|
int output_fd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current state of the print stream, dependent on whether the client
|
||||||
|
* has acknowledged creation of the stream, whether the client has
|
||||||
|
* acknowledged receipt of data along the steam, and whether the print
|
||||||
|
* stream itself has closed.
|
||||||
|
*/
|
||||||
|
guac_rdp_print_job_state state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock which is acquired prior to modifying the state property or waiting
|
||||||
|
* on the state_modified conditional.
|
||||||
|
*/
|
||||||
|
pthread_mutex_t state_lock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditional which signals modification to the state property of this
|
||||||
|
* structure.
|
||||||
|
*/
|
||||||
|
pthread_cond_t state_modified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread which transfers data from the printer to the Guacamole client.
|
||||||
|
*/
|
||||||
|
pthread_t output_thread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes received in the current print job.
|
||||||
|
*/
|
||||||
|
int bytes_received;
|
||||||
|
|
||||||
|
} guac_rdp_print_job;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A blob of print data being sent to the Guacamole user.
|
||||||
|
*/
|
||||||
|
typedef struct guac_rdp_print_blob {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The print job which generated the data being sent.
|
||||||
|
*/
|
||||||
|
guac_rdp_print_job* job;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data being sent.
|
||||||
|
*/
|
||||||
|
void* buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes of data being sent.
|
||||||
|
*/
|
||||||
|
int length;
|
||||||
|
|
||||||
|
} guac_rdp_print_blob;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a new print job for the given user. It is expected that this
|
||||||
|
* function will be invoked via a call to guac_client_for_user() or
|
||||||
|
* guac_client_for_owner().
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user that should receive the output from the print job.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* An arbitrary data parameter required by guac_client_for_user() and
|
||||||
|
* guac_client_for_owner() but ignored by this function. This should
|
||||||
|
* always be NULL.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A pointer to a newly-allocated guac_rdp_print_job, or NULL if the
|
||||||
|
* print job could not be created.
|
||||||
|
*/
|
||||||
|
void* guac_rdp_print_job_alloc(guac_user* user, void* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes PostScript print data to the given active print job. The print job
|
||||||
|
* will automatically convert this data to PDF, streaming the result to the
|
||||||
|
* Guacamole user associated with the print job. This function may block if
|
||||||
|
* the print job is not yet ready for more data.
|
||||||
|
*
|
||||||
|
* @param job
|
||||||
|
* The print job to write to.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The PostScript print data to write to the given print job.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The number of bytes of PostScript print data to write.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The number of bytes written, or -1 if an error occurs which prevents
|
||||||
|
* further writes.
|
||||||
|
*/
|
||||||
|
int guac_rdp_print_job_write(guac_rdp_print_job* job,
|
||||||
|
void* buffer, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the memory associated with the given print job, closing all underlying
|
||||||
|
* file descriptors, and ending the file transfer to the associated Guacamole
|
||||||
|
* user. This function may block if the print filter process has not yet
|
||||||
|
* finished processing the received data.
|
||||||
|
*
|
||||||
|
* @param job
|
||||||
|
* The print job to free.
|
||||||
|
*/
|
||||||
|
void guac_rdp_print_job_free(guac_rdp_print_job* job);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forcibly kills the given print job, stopping all associated processing and
|
||||||
|
* streaming. The memory associated with the print job will still need to be
|
||||||
|
* reclaimed via guac_rdp_print_job_free().
|
||||||
|
*
|
||||||
|
* @param job
|
||||||
|
* The print job to kill.
|
||||||
|
*/
|
||||||
|
void guac_rdp_print_job_kill(guac_rdp_print_job* job);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user