diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c index 733bd3fc..acb95c38 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c @@ -1,4 +1,6 @@ +#include + #include #include @@ -9,6 +11,127 @@ #include "rdpdr_service.h" #include "client.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", + "-f", + "-", + NULL +}; + +static void* guac_rdpdr_print_filter_output_thread(void* data) { + + guac_rdpdrPlugin* rdpdr = (guac_rdpdrPlugin*) data; + rdp_guac_client_data* client_data = (rdp_guac_client_data*) rdpdr->client->data; + + int length; + char buffer[8192]; + + /* Write all output as blobs */ + while ((length = read(rdpdr->printer_output, buffer, sizeof(buffer))) > 0) { + pthread_mutex_lock(&(client_data->update_lock)); + guac_protocol_send_blob(rdpdr->client->socket, + GUAC_RDPDR_PRINTER_BLOB, buffer, length); + pthread_mutex_unlock(&(client_data->update_lock)); + } + + /* Log any error */ + if (length < 0) + guac_client_log_error(rdpdr->client, "Error reading from filter: %s", strerror(errno)); + + return NULL; + +} + +static int guac_rdpdr_create_print_process(guac_rdpdrPlugin* rdpdr) { + + int child_pid; + int stdin_pipe[2]; + int stdout_pipe[2]; + + /* Create STDIN pipe */ + if (pipe(stdin_pipe)) { + guac_client_log_error(rdpdr->client, "Unable to create STDIN pipe for PDF filter process: %s", strerror(errno)); + return 1; + } + + /* Create STDOUT pipe */ + if (pipe(stdout_pipe)) { + guac_client_log_error(rdpdr->client, "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 */ + rdpdr->printer_input = stdin_pipe[1]; + rdpdr->printer_output = stdout_pipe[0]; + + /* Start output thread */ + if (pthread_create(&(rdpdr->printer_output_thread), NULL, guac_rdpdr_print_filter_output_thread, rdpdr)) { + guac_client_log_error(rdpdr->client, "Unable to fork PDF filter process"); + close(stdin_pipe[0]); + close(stdin_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + return 1; + } + + /* Fork child process */ + child_pid = fork(); + + /* Log fork errors */ + if (child_pid == -1) { + guac_client_log_error(rdpdr->client, "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_info(rdpdr->client, "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_error(rdpdr->client, "Unable to execute PDF filter command: %s", strerror(errno)); + else + guac_client_log_error(rdpdr->client, "Unable to execute PDF filter command, but no error given"); + + /* Terminate child process */ + exit(1); + + } + + /* Log fork success */ + guac_client_log_info(rdpdr->client, "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_rdpdrPlugin* rdpdr, STREAM* input_stream, int completion_id) { STREAM* output_stream = stream_new(24); @@ -23,7 +146,7 @@ void guac_rdpdr_process_print_job_create(guac_rdpdrPlugin* rdpdr, STREAM* input_ /* Write content */ stream_write_uint32(output_stream, GUAC_PRINTER_DEVICE_ID); stream_write_uint32(output_stream, completion_id); - stream_write_uint32(output_stream, 0); /* NTSTATUS - success */ + stream_write_uint32(output_stream, 0); /* Success */ stream_write_uint32(output_stream, 0); /* fileId */ svc_plugin_send((rdpSvcPlugin*) rdpdr, output_stream); @@ -32,7 +155,7 @@ void guac_rdpdr_process_print_job_create(guac_rdpdrPlugin* rdpdr, STREAM* input_ void guac_rdpdr_process_print_job_write(guac_rdpdrPlugin* rdpdr, STREAM* input_stream, int completion_id) { - int length; + int status=0, length; unsigned char* buffer; rdp_guac_client_data* client_data = (rdp_guac_client_data*) rdpdr->client->data; @@ -49,7 +172,7 @@ void guac_rdpdr_process_print_job_write(guac_rdpdrPlugin* rdpdr, STREAM* input_s /* Create print job, if not yet created */ if (rdpdr->bytes_received == 0) { - char filename[1024] = "guacamole-print.ps"; + char filename[1024] = "guacamole-print.pdf"; unsigned char* search = buffer; int i; @@ -64,7 +187,7 @@ void guac_rdpdr_process_print_job_write(guac_rdpdrPlugin* rdpdr, STREAM* input_s /* Copy as much of title as reasonable */ int j; - for (j=0; jclient, "Print job created"); guac_protocol_send_file(rdpdr->client->socket, - GUAC_RDPDR_PRINTER_BLOB, "application/postscript", filename); + GUAC_RDPDR_PRINTER_BLOB, "application/pdf", filename); + + /* Start print process */ + if (guac_rdpdr_create_print_process(rdpdr) != 0) { + status = 0x80000010; + length = 0; + } } rdpdr->bytes_received += length; - guac_protocol_send_blob(rdpdr->client->socket, - GUAC_RDPDR_PRINTER_BLOB, buffer, length); pthread_mutex_unlock(&(client_data->update_lock)); + /* If not yet failed, write received data */ + if (status == 0) { + + /* Write data to printer, translate output for RDP */ + length = write(rdpdr->printer_input, buffer, length); + if (length == -1) { + guac_client_log_error(rdpdr->client, "Error writing to printer: %s", strerror(errno)); + status = 0x80000010; + length = 0; + } + + } + /* Write header */ stream_write_uint16(output_stream, RDPDR_CTYP_CORE); stream_write_uint16(output_stream, PAKID_CORE_DEVICE_IOCOMPLETION); @@ -105,7 +246,7 @@ void guac_rdpdr_process_print_job_write(guac_rdpdrPlugin* rdpdr, STREAM* input_s /* Write content */ stream_write_uint32(output_stream, GUAC_PRINTER_DEVICE_ID); stream_write_uint32(output_stream, completion_id); - stream_write_uint32(output_stream, 0); /* NTSTATUS - success */ + stream_write_uint32(output_stream, status); stream_write_uint32(output_stream, length); stream_write_uint8(output_stream, 0); /* padding (stated as optional in spec, but requests fail without) */ @@ -118,6 +259,13 @@ void guac_rdpdr_process_print_job_close(guac_rdpdrPlugin* rdpdr, STREAM* input_s rdp_guac_client_data* client_data = (rdp_guac_client_data*) rdpdr->client->data; STREAM* output_stream = stream_new(24); + /* Close input and wait for output thread to finish */ + close(rdpdr->printer_input); + pthread_join(rdpdr->printer_output_thread, NULL); + + /* Close file descriptors */ + close(rdpdr->printer_output); + /* Close file */ guac_client_log_info(rdpdr->client, "Print job closed"); pthread_mutex_lock(&(client_data->update_lock)); diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h index c834d8a4..456b5388 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h @@ -54,5 +54,12 @@ void guac_rdpdr_process_print_job_create(guac_rdpdrPlugin* rdpdr, STREAM* input_ void guac_rdpdr_process_print_job_write(guac_rdpdrPlugin* rdpdr, STREAM* input_stream, int completion_id); void guac_rdpdr_process_print_job_close(guac_rdpdrPlugin* rdpdr, STREAM* input_stream, int completion_id); +/** + * 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 diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_service.h b/src/protocols/rdp/guac_rdpdr/rdpdr_service.h index f0232e09..ed8d0f62 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_service.h +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_service.h @@ -38,6 +38,8 @@ #ifndef __GUAC_RDPDR_SERVICE_H #define __GUAC_RDPDR_SERVICE_H +#include + #include /** @@ -58,6 +60,23 @@ typedef struct guac_rdpdrPlugin { */ guac_client* client; + /** + * File descriptor that should be written to when sending documents to the + * printer. + */ + int printer_input; + + /** + * File descriptor that should be read from when receiving output from the + * printer. + */ + int printer_output; + + /** + * Thread which transfers data from the printer to the Guacamole client. + */ + pthread_t printer_output_thread; + /** * The number of bytes received in the current print job. */