From 3fc43fba37043cb1f00bde0cccbf194c87a8dbc7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 13 Feb 2017 14:44:06 -0800 Subject: [PATCH 1/8] GUACAMOLE-200: Refactor RDPDR printer such that the "ack" handler cannot block. --- src/protocols/rdp/Makefile.am | 2 + .../rdp/guac_rdpdr/rdpdr_print_job.c | 581 ++++++++++++++++++ .../rdp/guac_rdpdr/rdpdr_print_job.h | 226 +++++++ src/protocols/rdp/guac_rdpdr/rdpdr_printer.c | 363 ++--------- src/protocols/rdp/guac_rdpdr/rdpdr_printer.h | 31 - 5 files changed, 850 insertions(+), 353 deletions(-) create mode 100644 src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c create mode 100644 src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index a190003b..7daf0e67 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -72,6 +72,7 @@ guacdr_sources = \ guac_rdpdr/rdpdr_fs_service.c \ guac_rdpdr/rdpdr_messages.c \ guac_rdpdr/rdpdr_printer.c \ + guac_rdpdr/rdpdr_print_job.c \ guac_rdpdr/rdpdr_service.c \ rdp_fs.c \ rdp_stream.c \ @@ -89,6 +90,7 @@ noinst_HEADERS = \ guac_rdpdr/rdpdr_fs_service.h \ guac_rdpdr/rdpdr_messages.h \ guac_rdpdr/rdpdr_printer.h \ + guac_rdpdr/rdpdr_print_job.h \ guac_rdpdr/rdpdr_service.h \ guac_rdpsnd/rdpsnd_messages.h \ guac_rdpsnd/rdpsnd_service.h \ diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c new file mode 100644 index 00000000..8beaac74 --- /dev/null +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c @@ -0,0 +1,581 @@ +/* + * 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 "rdpdr_print_job.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/** + * 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_rdpdr_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_rdpdr_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_rdpdr_print_job_set_state(guac_rdpdr_print_job* job, + guac_rdpdr_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_RDPDR_PRINT_JOB_WAITING_FOR_ACK. If the state of the print + * job is GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED, the state is automatically reset + * back to GUAC_RDPDR_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_RDPDR_PRINT_JOB_CLOSED, + * non-zero if the state was GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED and has been + * automatically reset to GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK. + */ +static int guac_rdpdr_print_job_wait_for_ack(guac_rdpdr_print_job* job) { + + /* Wait for ack if stream open and not yet received */ + pthread_mutex_lock(&(job->state_lock)); + if (job->state == GUAC_RDPDR_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_RDPDR_PRINT_JOB_ACK_RECEIVED); + if (got_ack) + job->state = GUAC_RDPDR_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_rdpdr_print_job representing the print job being + * streamed. + * + * @return + * Always NULL. + */ +static void* guac_rdpdr_print_job_begin_stream(guac_user* user, void* data) { + + guac_rdpdr_print_job* job = (guac_rdpdr_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_rdpdr_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_rdpdr_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_rdpdr_print_job_send_blob(guac_user* user, void* data) { + + guac_rdpdr_print_blob* blob = (guac_rdpdr_print_blob*) data; + guac_rdpdr_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_rdpdr_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_rdpdr_print_job representing the print job being + * streamed. + * + * @return + * Always NULL. + */ +static void* guac_rdpdr_print_job_end_stream(guac_user* user, void* data) { + + guac_rdpdr_print_job* job = (guac_rdpdr_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_rdpdr_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_rdpdr_print_filter_ack_handler(guac_user* user, + guac_stream* stream, char* message, guac_protocol_status status) { + + guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) stream->data; + + /* Update state for successful acks */ + if (status == GUAC_PROTOCOL_STATUS_SUCCESS) + guac_rdpdr_print_job_set_state(job, GUAC_RDPDR_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_rdpdr_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_rdpdr_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_rdpdr_pdf_filter_command[0]); + if (execvp(guac_rdpdr_pdf_filter_command[0], + guac_rdpdr_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_rdpdr_print_job representing the print job that + * should be read. + * + * @return + * Always NULL. + */ +static void* guac_rdpdr_print_job_output_thread(void* data) { + + int length; + char buffer[6048]; + + guac_rdpdr_print_job* job = (guac_rdpdr_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_rdpdr_print_job_wait_for_ack(job)) { + + guac_rdpdr_print_blob blob = { + .job = job, + .buffer = buffer, + .length = length + }; + + /* Write a single blob of output */ + guac_client_for_user(job->client, job->user, + guac_rdpdr_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_rdpdr_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_rdpdr_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_rdpdr_print_job* job = malloc(sizeof(guac_rdpdr_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_RDPDR_PRINT_JOB_DEFAULT_FILENAME); + + /* Prepare stream for receipt of acks */ + stream->ack_handler = guac_rdpdr_print_filter_ack_handler; + stream->data = job; + + /* Create print filter process */ + job->filter_pid = guac_rdpdr_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_RDPDR_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_rdpdr_print_job_output_thread, job); + + /* Print job allocated successfully */ + return job; + +} + +int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job, + void* buffer, int length) { + + /* Create print job, if not yet created */ + if (job->bytes_received == 0) { + + char* filename = job->filename; + unsigned char* search = buffer; + int i; + + /* Search for filename within buffer */ + for (i=0; iclient, job->user, + guac_rdpdr_print_job_begin_stream, job); + + } /* end if print job beginning */ + + /* Update counter of bytes received */ + job->bytes_received += length; + + /* Write data to filter process */ + return write(job->input_fd, buffer, length); + +} + +void guac_rdpdr_print_job_free(guac_rdpdr_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_rdpdr_print_job_kill(guac_rdpdr_print_job* job) { + + /* Stop all handling of I/O */ + close(job->input_fd); + close(job->output_fd); + + /* Mark stream as closed */ + guac_rdpdr_print_job_set_state(job, GUAC_RDPDR_PRINT_JOB_CLOSED); + +} + diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h new file mode 100644 index 00000000..81b1863c --- /dev/null +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h @@ -0,0 +1,226 @@ +/* + * 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_RDPDR_PRINT_JOB_H +#define GUAC_RDPDR_PRINT_JOB_H + +#include "config.h" + +#include +#include +#include + +#include +#include + +/** + * The maximum number of bytes in the filename of an RDPDR print job sent as a + * file over the Guacamole protocol, including NULL terminator. + */ +#define GUAC_RDPDR_PRINT_JOB_FILENAME_MAX_LENGTH 1024 + +/** + * The default filename to use for the PDF output of an RDPDR print job if no + * document title can be found within the printed data. + */ +#define GUAC_RDPDR_PRINT_JOB_DEFAULT_FILENAME "guacamole-print.pdf" + +/** + * The current state of an RDPDR print job. + */ +typedef enum guac_rdpdr_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_RDPDR_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_RDPDR_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_RDPDR_PRINT_JOB_CLOSED + +} guac_rdpdr_print_job_state; + +/** + * Data specific to an instance of the printer device. + */ +typedef struct guac_rdpdr_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_RDPDR_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_rdpdr_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_rdpdr_print_job; + +/** + * A blob of print data being sent to the Guacamole user. + */ +typedef struct guac_rdpdr_print_blob { + + /** + * The print job which generated the data being sent. + */ + guac_rdpdr_print_job* job; + + /** + * The data being sent. + */ + void* buffer; + + /** + * The number of bytes of data being sent. + */ + int length; + +} guac_rdpdr_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_rdpdr_print_job, or NULL if the + * print job could not be created. + */ +void* guac_rdpdr_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 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_rdpdr_print_job_write(guac_rdpdr_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_rdpdr_print_job_free(guac_rdpdr_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_rdpdr_print_job_free(). + * + * @param job + * The print job to kill. + */ +void guac_rdpdr_print_job_kill(guac_rdpdr_print_job* job); + +#endif + diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c index 41c36633..cd7c517e 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c @@ -21,6 +21,7 @@ #include "rdpdr_messages.h" #include "rdpdr_printer.h" +#include "rdpdr_print_job.h" #include "rdpdr_service.h" #include "rdp_status.h" @@ -38,330 +39,60 @@ #endif #include +#include #include #include #include #include -/* 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, wStream* input_stream, int completion_id) { - guac_rdpdr_printer_data* printer_data = - (guac_rdpdr_printer_data*) device->data; + /* Log creation of print job */ + guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, "Print job created"); + /* Create print job */ + device->data = guac_client_for_owner(device->rdpdr->client, + guac_rdpdr_print_job_alloc, NULL); + + /* Respond with success */ wStream* output_stream = guac_rdpdr_new_io_completion(device, completion_id, STATUS_SUCCESS, 4); - /* No bytes received yet */ - printer_data->bytes_received = 0; Stream_Write_UINT32(output_stream, 0); /* fileId */ - 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, wStream* input_stream, int completion_id) { - guac_rdpdr_printer_data* printer_data = (guac_rdpdr_printer_data*) device->data; - int status=0, length; + guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data; + unsigned char* buffer; + int length; + int status; - wStream* output_stream; - + /* Read buffer of print data */ Stream_Read_UINT32(input_stream, length); Stream_Seek(input_stream, 8); /* Offset */ Stream_Seek(input_stream, 20); /* Padding */ buffer = Stream_Pointer(input_stream); - /* Create print job, if not yet created */ - if (printer_data->bytes_received == 0) { - - char filename[1024] = "guacamole-print.pdf"; - unsigned char* search = buffer; - int i; - - /* Search for filename within buffer */ - for (i=0; irdpdr->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; - length = 0; - } - + /* Write data only if job exists, translating status for RDP */ + if (job != NULL && (length = guac_rdpdr_print_job_write(job, + buffer, length)) >= 0) { + status = STATUS_SUCCESS; } - output_stream = guac_rdpdr_new_io_completion(device, completion_id, - status, 5); + /* Report device offline if write fails */ + else { + status = STATUS_DEVICE_OFF_LINE; + length = 0; + } + + wStream* output_stream = guac_rdpdr_new_io_completion(device, + completion_id, status, 5); Stream_Write_UINT32(output_stream, length); Stream_Write_UINT8(output_stream, 0); /* Padding */ @@ -373,21 +104,22 @@ void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device, void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device, wStream* input_stream, int completion_id) { - guac_rdpdr_printer_data* printer_data = - (guac_rdpdr_printer_data*) device->data; + /* End print job */ + guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data; + if (job != NULL) { + guac_rdpdr_print_job_free(job); + device->data = NULL; + } wStream* output_stream = guac_rdpdr_new_io_completion(device, completion_id, STATUS_SUCCESS, 4); 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); + /* Log end of print job */ + guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, "Print job closed"); + } static void guac_rdpdr_device_printer_announce_handler(guac_rdpdr_device* device, @@ -447,19 +179,10 @@ static void guac_rdpdr_device_printer_iorequest_handler(guac_rdpdr_device* devic static void guac_rdpdr_device_printer_free_handler(guac_rdpdr_device* device) { - 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); + /* Close print job if open */ + guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data; + if (job != NULL) + guac_rdpdr_print_job_free(job); } @@ -469,7 +192,6 @@ void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) { /* Get new device */ guac_rdpdr_device* device = &(rdpdr->devices[id]); - guac_rdpdr_printer_data* printer_data; /* Init device */ device->rdpdr = rdpdr; @@ -481,11 +203,8 @@ void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) { device->iorequest_handler = guac_rdpdr_device_printer_iorequest_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; + /* No active print job yet */ + device->data = NULL; } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h index 72afa7ab..6f203d6b 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.h @@ -31,42 +31,11 @@ #include "compat/winpr-stream.h" #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 * before RDPDR connection finishes. */ 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 From d23a22b7c6e966cde6a175b38cc81f005263483d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 13 Feb 2017 18:42:03 -0800 Subject: [PATCH 2/8] GUACAMOLE-200: Clean up PostScript document title search logic. --- .../rdp/guac_rdpdr/rdpdr_print_job.c | 144 +++++++++++++----- .../rdp/guac_rdpdr/rdpdr_print_job.h | 6 + 2 files changed, 113 insertions(+), 37 deletions(-) diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c index 8beaac74..c3cb03c7 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c @@ -498,54 +498,124 @@ void* guac_rdpdr_print_job_alloc(guac_user* user, void* data) { } +/** + * 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_rdpdr_print_job_parse_title_header(guac_rdpdr_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_RDPDR_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_rdpdr_print_job_read_filename(guac_rdpdr_print_job* job, + void* buffer, int length) { + + char* current = buffer; + int i; + + /* Restrict search area */ + if (length > GUAC_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH) + length = GUAC_RDPDR_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_rdpdr_print_job_parse_title_header(job, current, length)) + break; + + /* Advance to next character */ + length--; + current++; + + } + +} + int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job, void* buffer, int length) { /* Create print job, if not yet created */ if (job->bytes_received == 0) { - char* filename = job->filename; - unsigned char* search = buffer; - int i; - - /* Search for filename within buffer */ - for (i=0; iclient, job->user, guac_rdpdr_print_job_begin_stream, job); - } /* end if print job beginning */ + } /* Update counter of bytes received */ job->bytes_received += length; diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h index 81b1863c..97e3d623 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h @@ -41,6 +41,12 @@ */ #define GUAC_RDPDR_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_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH 2048 + /** * The current state of an RDPDR print job. */ From 17093a81491d871504d614bf211efe4346fbdeff Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 13 Feb 2017 23:08:54 -0800 Subject: [PATCH 3/8] GUACAMOLE-200: Kill any remaining print job when connection closes. --- src/protocols/rdp/guac_rdpdr/rdpdr_printer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c index cd7c517e..0545f891 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c @@ -179,10 +179,12 @@ static void guac_rdpdr_device_printer_iorequest_handler(guac_rdpdr_device* devic static void guac_rdpdr_device_printer_free_handler(guac_rdpdr_device* device) { - /* Close print job if open */ + /* Terminate and free print job if open */ guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data; - if (job != NULL) + if (job != NULL) { + guac_rdpdr_print_job_kill(job); guac_rdpdr_print_job_free(job); + } } From 1537e475af475ff4e5388c573f9f83b4ef0a13ba Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 13 Feb 2017 23:51:33 -0800 Subject: [PATCH 4/8] GUACAMOLE-200: Refactor RDPDR print job object to top-level. --- src/protocols/rdp/Makefile.am | 5 +- src/protocols/rdp/guac_rdpdr/rdpdr_printer.c | 18 +-- .../rdpdr_print_job.c => rdp_print_job.c} | 124 +++++++++--------- .../rdpdr_print_job.h => rdp_print_job.h} | 52 ++++---- 4 files changed, 100 insertions(+), 99 deletions(-) rename src/protocols/rdp/{guac_rdpdr/rdpdr_print_job.c => rdp_print_job.c} (80%) rename src/protocols/rdp/{guac_rdpdr/rdpdr_print_job.h => rdp_print_job.h} (81%) diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 7daf0e67..c49afea0 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -41,6 +41,7 @@ libguac_client_rdp_la_SOURCES = \ rdp_gdi.c \ rdp_glyph.c \ rdp_keymap.c \ + rdp_print_job.c \ rdp_pointer.c \ rdp_rail.c \ rdp_settings.c \ @@ -72,9 +73,9 @@ guacdr_sources = \ guac_rdpdr/rdpdr_fs_service.c \ guac_rdpdr/rdpdr_messages.c \ guac_rdpdr/rdpdr_printer.c \ - guac_rdpdr/rdpdr_print_job.c \ guac_rdpdr/rdpdr_service.c \ rdp_fs.c \ + rdp_print_job.c \ rdp_stream.c \ unicode.c @@ -90,7 +91,6 @@ noinst_HEADERS = \ guac_rdpdr/rdpdr_fs_service.h \ guac_rdpdr/rdpdr_messages.h \ guac_rdpdr/rdpdr_printer.h \ - guac_rdpdr/rdpdr_print_job.h \ guac_rdpdr/rdpdr_service.h \ guac_rdpsnd/rdpsnd_messages.h \ guac_rdpsnd/rdpsnd_service.h \ @@ -111,6 +111,7 @@ noinst_HEADERS = \ rdp_glyph.h \ rdp_keymap.h \ rdp_pointer.h \ + rdp_print_job.h \ rdp_rail.h \ rdp_settings.h \ rdp_status.h \ diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c index 0545f891..812eaf0b 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c @@ -21,8 +21,8 @@ #include "rdpdr_messages.h" #include "rdpdr_printer.h" -#include "rdpdr_print_job.h" #include "rdpdr_service.h" +#include "rdp_print_job.h" #include "rdp_status.h" #include @@ -53,7 +53,7 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device, /* Create print job */ device->data = guac_client_for_owner(device->rdpdr->client, - guac_rdpdr_print_job_alloc, NULL); + guac_rdp_print_job_alloc, NULL); /* Respond with success */ wStream* output_stream = guac_rdpdr_new_io_completion(device, @@ -67,7 +67,7 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device, void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device, wStream* input_stream, int completion_id) { - guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data; + guac_rdp_print_job* job = (guac_rdp_print_job*) device->data; unsigned char* buffer; int length; @@ -80,7 +80,7 @@ void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device, buffer = Stream_Pointer(input_stream); /* Write data only if job exists, translating status for RDP */ - if (job != NULL && (length = guac_rdpdr_print_job_write(job, + if (job != NULL && (length = guac_rdp_print_job_write(job, buffer, length)) >= 0) { status = STATUS_SUCCESS; } @@ -105,9 +105,9 @@ void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device, wStream* input_stream, int completion_id) { /* End print job */ - guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data; + guac_rdp_print_job* job = (guac_rdp_print_job*) device->data; if (job != NULL) { - guac_rdpdr_print_job_free(job); + guac_rdp_print_job_free(job); device->data = NULL; } @@ -180,10 +180,10 @@ static void guac_rdpdr_device_printer_iorequest_handler(guac_rdpdr_device* devic static void guac_rdpdr_device_printer_free_handler(guac_rdpdr_device* device) { /* Terminate and free print job if open */ - guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data; + guac_rdp_print_job* job = (guac_rdp_print_job*) device->data; if (job != NULL) { - guac_rdpdr_print_job_kill(job); - guac_rdpdr_print_job_free(job); + guac_rdp_print_job_kill(job); + guac_rdp_print_job_free(job); } } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c b/src/protocols/rdp/rdp_print_job.c similarity index 80% rename from src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c rename to src/protocols/rdp/rdp_print_job.c index c3cb03c7..a3b51bc1 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c +++ b/src/protocols/rdp/rdp_print_job.c @@ -19,7 +19,7 @@ #include "config.h" -#include "rdpdr_print_job.h" +#include "rdp_print_job.h" #include #include @@ -38,7 +38,7 @@ * a NULL-terminated array of arguments, where the first argument is the name * of the file to run. */ -char* const guac_rdpdr_pdf_filter_command[] = { +char* const guac_rdp_pdf_filter_command[] = { "gs", "-q", "-dNOPAUSE", @@ -57,7 +57,7 @@ char* const guac_rdpdr_pdf_filter_command[] = { /** * Updates the state of the given print job. Any threads currently blocked by a - * call to guac_rdpdr_print_job_wait_for_ack() will be unblocked. + * call to guac_rdp_print_job_wait_for_ack() will be unblocked. * * @param job * The print job whose state should be updated. @@ -65,8 +65,8 @@ char* const guac_rdpdr_pdf_filter_command[] = { * @param state * The new state to assign to the given print job. */ -static void guac_rdpdr_print_job_set_state(guac_rdpdr_print_job* job, - guac_rdpdr_print_job_state state) { +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)); @@ -80,29 +80,29 @@ static void guac_rdpdr_print_job_set_state(guac_rdpdr_print_job* job, /** * Suspends execution of the current thread until the state of the given print - * job is not GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK. If the state of the print - * job is GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED, the state is automatically reset - * back to GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK prior to returning. + * 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_RDPDR_PRINT_JOB_CLOSED, - * non-zero if the state was GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED and has been - * automatically reset to GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK. + * 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_rdpdr_print_job_wait_for_ack(guac_rdpdr_print_job* job) { +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_RDPDR_PRINT_JOB_WAITING_FOR_ACK) + 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_RDPDR_PRINT_JOB_ACK_RECEIVED); + int got_ack = (job->state == GUAC_RDP_PRINT_JOB_ACK_RECEIVED); if (got_ack) - job->state = GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK; + job->state = GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK; /* Return whether ack was successfully received */ pthread_mutex_unlock(&(job->state_lock)); @@ -119,21 +119,21 @@ static int guac_rdpdr_print_job_wait_for_ack(guac_rdpdr_print_job* job) { * The user receiving the "file" instruction. * * @param data - * A pointer to the guac_rdpdr_print_job representing the print job being + * A pointer to the guac_rdp_print_job representing the print job being * streamed. * * @return * Always NULL. */ -static void* guac_rdpdr_print_job_begin_stream(guac_user* user, void* data) { +static void* guac_rdp_print_job_begin_stream(guac_user* user, void* data) { - guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) 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_rdpdr_print_job_kill(job); + guac_rdp_print_job_kill(job); return NULL; } @@ -155,24 +155,24 @@ static void* guac_rdpdr_print_job_begin_stream(guac_user* user, void* data) { * The user receiving the "blob" instruction. * * @param data - * A pointer to an guac_rdpdr_print_blob structure containing the data to + * 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_rdpdr_print_job_send_blob(guac_user* user, void* data) { +static void* guac_rdp_print_job_send_blob(guac_user* user, void* data) { - guac_rdpdr_print_blob* blob = (guac_rdpdr_print_blob*) data; - guac_rdpdr_print_job* job = blob->job; + 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_rdpdr_print_job_kill(job); + guac_rdp_print_job_kill(job); return NULL; } @@ -194,20 +194,20 @@ static void* guac_rdpdr_print_job_send_blob(guac_user* user, void* data) { * The user receiving the "end" instruction. * * @param data - * A pointer to the guac_rdpdr_print_job representing the print job being + * A pointer to the guac_rdp_print_job representing the print job being * streamed. * * @return * Always NULL. */ -static void* guac_rdpdr_print_job_end_stream(guac_user* user, void* data) { +static void* guac_rdp_print_job_end_stream(guac_user* user, void* data) { - guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) 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_rdpdr_print_job_kill(job); + guac_rdp_print_job_kill(job); return NULL; } @@ -248,14 +248,14 @@ static void* guac_rdpdr_print_job_end_stream(guac_user* user, void* data) { * @return * Always zero. */ -static int guac_rdpdr_print_filter_ack_handler(guac_user* user, +static int guac_rdp_print_filter_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status) { - guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) stream->data; + guac_rdp_print_job* job = (guac_rdp_print_job*) stream->data; /* Update state for successful acks */ if (status == GUAC_PROTOCOL_STATUS_SUCCESS) - guac_rdpdr_print_job_set_state(job, GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED); + guac_rdp_print_job_set_state(job, GUAC_RDP_PRINT_JOB_ACK_RECEIVED); /* Terminate stream if ack signals an error */ else { @@ -265,7 +265,7 @@ static int guac_rdpdr_print_filter_ack_handler(guac_user* user, "print stream."); /* Kill job (the results will no longer be received) */ - guac_rdpdr_print_job_kill(job); + guac_rdp_print_job_kill(job); } @@ -298,7 +298,7 @@ static int guac_rdpdr_print_filter_ack_handler(guac_user* user, * created. If the filter process could not be created, the values assigned * through input_fd and output_fd are undefined. */ -static pid_t guac_rdpdr_create_filter_process(guac_client* client, +static pid_t guac_rdp_create_filter_process(guac_client* client, int* input_fd, int* output_fd) { int child_pid; @@ -352,9 +352,9 @@ static pid_t guac_rdpdr_create_filter_process(guac_client* client, /* Run PDF filter */ guac_client_log(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_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 @@ -384,18 +384,18 @@ static pid_t guac_rdpdr_create_filter_process(guac_client* client, * processing or the associated Guacamole stream has closed. * * @param data - * A pointer to the guac_rdpdr_print_job representing the print job that + * A pointer to the guac_rdp_print_job representing the print job that * should be read. * * @return * Always NULL. */ -static void* guac_rdpdr_print_job_output_thread(void* data) { +static void* guac_rdp_print_job_output_thread(void* data) { int length; char buffer[6048]; - guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) data; + guac_rdp_print_job* job = (guac_rdp_print_job*) data; guac_client_log(job->client, GUAC_LOG_DEBUG, "Reading output from filter " "process..."); @@ -403,9 +403,9 @@ static void* guac_rdpdr_print_job_output_thread(void* data) { while ((length = read(job->output_fd, buffer, sizeof(buffer))) > 0) { /* Wait for client to be ready for blob */ - if (guac_rdpdr_print_job_wait_for_ack(job)) { + if (guac_rdp_print_job_wait_for_ack(job)) { - guac_rdpdr_print_blob blob = { + guac_rdp_print_blob blob = { .job = job, .buffer = buffer, .length = length @@ -413,7 +413,7 @@ static void* guac_rdpdr_print_job_output_thread(void* data) { /* Write a single blob of output */ guac_client_for_user(job->client, job->user, - guac_rdpdr_print_job_send_blob, &blob); + guac_rdp_print_job_send_blob, &blob); } @@ -433,7 +433,7 @@ static void* guac_rdpdr_print_job_output_thread(void* data) { /* Terminate stream */ guac_client_for_user(job->client, job->user, - guac_rdpdr_print_job_end_stream, job); + guac_rdp_print_job_end_stream, job); /* Ensure all associated file descriptors are closed */ close(job->input_fd); @@ -444,7 +444,7 @@ static void* guac_rdpdr_print_job_output_thread(void* data) { } -void* guac_rdpdr_print_job_alloc(guac_user* user, void* data) { +void* guac_rdp_print_job_alloc(guac_user* user, void* data) { /* Allocate nothing if user does not exist */ if (user == NULL) @@ -456,7 +456,7 @@ void* guac_rdpdr_print_job_alloc(guac_user* user, void* data) { return NULL; /* Bail early if allocation fails */ - guac_rdpdr_print_job* job = malloc(sizeof(guac_rdpdr_print_job)); + guac_rdp_print_job* job = malloc(sizeof(guac_rdp_print_job)); if (job == NULL) return NULL; @@ -467,14 +467,14 @@ void* guac_rdpdr_print_job_alloc(guac_user* user, void* data) { job->bytes_received = 0; /* Set default filename for job */ - strcpy(job->filename, GUAC_RDPDR_PRINT_JOB_DEFAULT_FILENAME); + strcpy(job->filename, GUAC_RDP_PRINT_JOB_DEFAULT_FILENAME); /* Prepare stream for receipt of acks */ - stream->ack_handler = guac_rdpdr_print_filter_ack_handler; + stream->ack_handler = guac_rdp_print_filter_ack_handler; stream->data = job; /* Create print filter process */ - job->filter_pid = guac_rdpdr_create_filter_process(job->client, + job->filter_pid = guac_rdp_create_filter_process(job->client, &job->input_fd, &job->output_fd); /* Abort if print filter process cannot be created */ @@ -485,13 +485,13 @@ void* guac_rdpdr_print_job_alloc(guac_user* user, void* data) { } /* Init stream state signal and lock */ - job->state = GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK; + 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_rdpdr_print_job_output_thread, job); + guac_rdp_print_job_output_thread, job); /* Print job allocated successfully */ return job; @@ -518,7 +518,7 @@ void* guac_rdpdr_print_job_alloc(guac_user* user, void* data) { * Non-zero if the given buffer began with the "%%Title:" header and this * header was successfully parsed, zero otherwise. */ -static int guac_rdpdr_print_job_parse_title_header(guac_rdpdr_print_job* job, +static int guac_rdp_print_job_parse_title_header(guac_rdp_print_job* job, void* buffer, int length) { int i; @@ -565,7 +565,7 @@ static int guac_rdpdr_print_job_parse_title_header(guac_rdpdr_print_job* job, * 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_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH bytes, this function has no effect. + * 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 @@ -577,21 +577,21 @@ static int guac_rdpdr_print_job_parse_title_header(guac_rdpdr_print_job* job, * @param length * The number of bytes within the buffer. */ -static void guac_rdpdr_print_job_read_filename(guac_rdpdr_print_job* job, +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_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH) - length = GUAC_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH; + 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_rdpdr_print_job_parse_title_header(job, current, length)) + if (guac_rdp_print_job_parse_title_header(job, current, length)) break; /* Advance to next character */ @@ -602,18 +602,18 @@ static void guac_rdpdr_print_job_read_filename(guac_rdpdr_print_job* job, } -int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job, +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_rdpdr_print_job_read_filename(job, buffer, length); + guac_rdp_print_job_read_filename(job, buffer, length); /* Begin print stream */ guac_client_for_user(job->client, job->user, - guac_rdpdr_print_job_begin_stream, job); + guac_rdp_print_job_begin_stream, job); } @@ -625,7 +625,7 @@ int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job, } -void guac_rdpdr_print_job_free(guac_rdpdr_print_job* job) { +void guac_rdp_print_job_free(guac_rdp_print_job* job) { /* No more input will be provided */ close(job->input_fd); @@ -638,14 +638,14 @@ void guac_rdpdr_print_job_free(guac_rdpdr_print_job* job) { } -void guac_rdpdr_print_job_kill(guac_rdpdr_print_job* 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_rdpdr_print_job_set_state(job, GUAC_RDPDR_PRINT_JOB_CLOSED); + guac_rdp_print_job_set_state(job, GUAC_RDP_PRINT_JOB_CLOSED); } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h b/src/protocols/rdp/rdp_print_job.h similarity index 81% rename from src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h rename to src/protocols/rdp/rdp_print_job.h index 97e3d623..aa879d9c 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h +++ b/src/protocols/rdp/rdp_print_job.h @@ -17,8 +17,8 @@ * under the License. */ -#ifndef GUAC_RDPDR_PRINT_JOB_H -#define GUAC_RDPDR_PRINT_JOB_H +#ifndef GUAC_RDP_PRINT_JOB_H +#define GUAC_RDP_PRINT_JOB_H #include "config.h" @@ -30,33 +30,33 @@ #include /** - * The maximum number of bytes in the filename of an RDPDR print job sent as a + * 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_RDPDR_PRINT_JOB_FILENAME_MAX_LENGTH 1024 +#define GUAC_RDP_PRINT_JOB_FILENAME_MAX_LENGTH 1024 /** - * The default filename to use for the PDF output of an RDPDR print job if no + * 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_RDPDR_PRINT_JOB_DEFAULT_FILENAME "guacamole-print.pdf" +#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_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH 2048 +#define GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH 2048 /** - * The current state of an RDPDR print job. + * The current state of an RDP print job. */ -typedef enum guac_rdpdr_print_job_state { +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_RDPDR_PRINT_JOB_WAITING_FOR_ACK, + GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK, /** * The print stream has been opened with the Guacamole client, and the @@ -64,20 +64,20 @@ typedef enum guac_rdpdr_print_job_state { * receive data (or that data has been received and it is ready to receive * more). */ - GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED, + 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_RDPDR_PRINT_JOB_CLOSED + GUAC_RDP_PRINT_JOB_CLOSED -} guac_rdpdr_print_job_state; +} guac_rdp_print_job_state; /** * Data specific to an instance of the printer device. */ -typedef struct guac_rdpdr_print_job { +typedef struct guac_rdp_print_job { guac_client* client; @@ -101,7 +101,7 @@ typedef struct guac_rdpdr_print_job { * streamed to the Guacamole user. This value will be automatically * determined based on the contents of the printed document. */ - char filename[GUAC_RDPDR_PRINT_JOB_FILENAME_MAX_LENGTH]; + char filename[GUAC_RDP_PRINT_JOB_FILENAME_MAX_LENGTH]; /** * File descriptor that should be written to when sending documents to the @@ -121,7 +121,7 @@ typedef struct guac_rdpdr_print_job { * acknowledged receipt of data along the steam, and whether the print * stream itself has closed. */ - guac_rdpdr_print_job_state state; + guac_rdp_print_job_state state; /** * Lock which is acquired prior to modifying the state property or waiting @@ -145,17 +145,17 @@ typedef struct guac_rdpdr_print_job { */ int bytes_received; -} guac_rdpdr_print_job; +} guac_rdp_print_job; /** * A blob of print data being sent to the Guacamole user. */ -typedef struct guac_rdpdr_print_blob { +typedef struct guac_rdp_print_blob { /** * The print job which generated the data being sent. */ - guac_rdpdr_print_job* job; + guac_rdp_print_job* job; /** * The data being sent. @@ -167,7 +167,7 @@ typedef struct guac_rdpdr_print_blob { */ int length; -} guac_rdpdr_print_blob; +} guac_rdp_print_blob; /** * Allocates a new print job for the given user. It is expected that this @@ -183,10 +183,10 @@ typedef struct guac_rdpdr_print_blob { * always be NULL. * * @return - * A pointer to a newly-allocated guac_rdpdr_print_job, or NULL if the + * A pointer to a newly-allocated guac_rdp_print_job, or NULL if the * print job could not be created. */ -void* guac_rdpdr_print_job_alloc(guac_user* user, void* data); +void* guac_rdp_print_job_alloc(guac_user* user, void* data); /** * Writes PostScript print data to the given active print job. The print job @@ -204,7 +204,7 @@ void* guac_rdpdr_print_job_alloc(guac_user* user, void* data); * The number of bytes written, or -1 if an error occurs which prevents * further writes. */ -int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job, +int guac_rdp_print_job_write(guac_rdp_print_job* job, void* buffer, int length); /** @@ -216,17 +216,17 @@ int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job, * @param job * The print job to free. */ -void guac_rdpdr_print_job_free(guac_rdpdr_print_job* job); +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_rdpdr_print_job_free(). + * reclaimed via guac_rdp_print_job_free(). * * @param job * The print job to kill. */ -void guac_rdpdr_print_job_kill(guac_rdpdr_print_job* job); +void guac_rdp_print_job_kill(guac_rdp_print_job* job); #endif From 5a68f932d6cc048c3ec9df8c48e64cb4e7a36202 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 14 Feb 2017 00:02:17 -0800 Subject: [PATCH 5/8] GUACAMOLE-200: Maintain print jobs at top level. Do not rely on proper free of RDPDR plugin. --- src/protocols/rdp/client.c | 8 +++++ src/protocols/rdp/guac_rdpdr/rdpdr_printer.c | 31 ++++++++++---------- src/protocols/rdp/rdp.h | 6 ++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 2a9a6905..203bdb08 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -23,6 +23,8 @@ #include "client.h" #include "rdp.h" #include "rdp_disp.h" +#include "rdp_fs.h" +#include "rdp_print_job.h" #include "user.h" #ifdef ENABLE_COMMON_SSH @@ -105,6 +107,12 @@ int guac_rdp_client_free_handler(guac_client* client) { if (rdp_client->filesystem != NULL) guac_rdp_fs_free(rdp_client->filesystem); + /* 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); + } + #ifdef ENABLE_COMMON_SSH /* Free SFTP filesystem, if loaded */ if (rdp_client->sftp_filesystem) diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c index 812eaf0b..3733b013 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c @@ -22,6 +22,7 @@ #include "rdpdr_messages.h" #include "rdpdr_printer.h" #include "rdpdr_service.h" +#include "rdp.h" #include "rdp_print_job.h" #include "rdp_status.h" @@ -48,11 +49,14 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device, wStream* input_stream, int completion_id) { + guac_client* client = device->rdpdr->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + /* Log creation of print job */ - guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, "Print job created"); + guac_client_log(client, GUAC_LOG_INFO, "Print job created"); /* Create print job */ - device->data = guac_client_for_owner(device->rdpdr->client, + rdp_client->active_job = guac_client_for_owner(client, guac_rdp_print_job_alloc, NULL); /* Respond with success */ @@ -67,7 +71,9 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* device, void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device, wStream* input_stream, int completion_id) { - guac_rdp_print_job* job = (guac_rdp_print_job*) device->data; + guac_client* client = device->rdpdr->client; + 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; int length; @@ -104,8 +110,11 @@ void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device, void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device, wStream* input_stream, int completion_id) { + guac_client* client = device->rdpdr->client; + 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 */ - guac_rdp_print_job* job = (guac_rdp_print_job*) device->data; if (job != NULL) { guac_rdp_print_job_free(job); device->data = NULL; @@ -118,7 +127,7 @@ void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device, svc_plugin_send((rdpSvcPlugin*) device->rdpdr, output_stream); /* Log end of print job */ - guac_client_log(device->rdpdr->client, GUAC_LOG_INFO, "Print job closed"); + guac_client_log(client, GUAC_LOG_INFO, "Print job closed"); } @@ -178,14 +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) { - - /* Terminate and free print job if open */ - guac_rdp_print_job* job = (guac_rdp_print_job*) device->data; - if (job != NULL) { - guac_rdp_print_job_kill(job); - guac_rdp_print_job_free(job); - } - + /* Do nothing */ } void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) { @@ -205,8 +207,5 @@ void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) { device->iorequest_handler = guac_rdpdr_device_printer_iorequest_handler; device->free_handler = guac_rdpdr_device_printer_free_handler; - /* No active print job yet */ - device->data = NULL; - } diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index af26408f..e3e33119 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -30,6 +30,7 @@ #include "keyboard.h" #include "rdp_disp.h" #include "rdp_fs.h" +#include "rdp_print_job.h" #include "rdp_settings.h" #include @@ -120,6 +121,11 @@ typedef struct guac_rdp_client { */ 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 /** * The user and credentials used to authenticate for SFTP. From bf2a5885d05dca09ee4b8d73130e25e70d66a512 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 14 Feb 2017 00:09:24 -0800 Subject: [PATCH 6/8] GUACAMOLE-200: Move print job cleanup into main RDP client thread. --- src/protocols/rdp/client.c | 7 ------- src/protocols/rdp/rdp.c | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 203bdb08..2dcb401b 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -24,7 +24,6 @@ #include "rdp.h" #include "rdp_disp.h" #include "rdp_fs.h" -#include "rdp_print_job.h" #include "user.h" #ifdef ENABLE_COMMON_SSH @@ -107,12 +106,6 @@ int guac_rdp_client_free_handler(guac_client* client) { if (rdp_client->filesystem != NULL) guac_rdp_fs_free(rdp_client->filesystem); - /* 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); - } - #ifdef ENABLE_COMMON_SSH /* Free SFTP filesystem, if loaded */ if (rdp_client->sftp_filesystem) diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 389c7049..a48230a0 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -31,6 +31,7 @@ #include "rdp_cliprdr.h" #include "rdp_disp.h" #include "rdp_fs.h" +#include "rdp_print_job.h" #include "rdp_gdi.h" #include "rdp_glyph.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)); /* Disconnect client and channels */ From 7a4b737a3ab07f43fd183625f5fdb8a58285376b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 15 Feb 2017 19:59:50 -0800 Subject: [PATCH 7/8] GUACAMOLE-200: Document all parameters of guac_rdp_print_job_write(). --- src/protocols/rdp/rdp_print_job.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/protocols/rdp/rdp_print_job.h b/src/protocols/rdp/rdp_print_job.h index aa879d9c..5682f186 100644 --- a/src/protocols/rdp/rdp_print_job.h +++ b/src/protocols/rdp/rdp_print_job.h @@ -194,6 +194,9 @@ void* guac_rdp_print_job_alloc(guac_user* user, void* data); * 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. * From 12cde5d520a9b1cf9deedf6ad10696cb1720b235 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 15 Feb 2017 20:11:26 -0800 Subject: [PATCH 8/8] GUACAMOLE-200: Clear reference to active job after the job has been freed. --- src/protocols/rdp/guac_rdpdr/rdpdr_printer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c index 3733b013..40ac36f2 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c @@ -117,7 +117,7 @@ void guac_rdpdr_process_print_job_close(guac_rdpdr_device* device, /* End print job */ if (job != NULL) { guac_rdp_print_job_free(job); - device->data = NULL; + rdp_client->active_job = NULL; } wStream* output_stream = guac_rdpdr_new_io_completion(device,