Merge 0.9.12-incubating changes back to master.
This commit is contained in:
commit
cd8646b38f
@ -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 \
|
||||
@ -74,6 +75,7 @@ guacdr_sources = \
|
||||
guac_rdpdr/rdpdr_printer.c \
|
||||
guac_rdpdr/rdpdr_service.c \
|
||||
rdp_fs.c \
|
||||
rdp_print_job.c \
|
||||
rdp_stream.c \
|
||||
unicode.c
|
||||
|
||||
@ -109,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 \
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "client.h"
|
||||
#include "rdp.h"
|
||||
#include "rdp_disp.h"
|
||||
#include "rdp_fs.h"
|
||||
#include "user.h"
|
||||
|
||||
#ifdef ENABLE_COMMON_SSH
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "rdpdr_messages.h"
|
||||
#include "rdpdr_printer.h"
|
||||
#include "rdpdr_service.h"
|
||||
#include "rdp.h"
|
||||
#include "rdp_print_job.h"
|
||||
#include "rdp_status.h"
|
||||
|
||||
#include <freerdp/utils/svc_plugin.h>
|
||||
@ -38,330 +40,65 @@
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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,
|
||||
wStream* input_stream, int completion_id) {
|
||||
|
||||
guac_rdpdr_printer_data* printer_data =
|
||||
(guac_rdpdr_printer_data*) device->data;
|
||||
guac_client* client = device->rdpdr->client;
|
||||
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,
|
||||
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_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;
|
||||
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; 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 */
|
||||
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 {
|
||||
|
||||
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;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
/* Write data only if job exists, translating status for RDP */
|
||||
if (job != NULL && (length = guac_rdp_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 +110,25 @@ 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;
|
||||
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 */
|
||||
if (job != NULL) {
|
||||
guac_rdp_print_job_free(job);
|
||||
rdp_client->active_job = 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(client, GUAC_LOG_INFO, "Print job closed");
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
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);
|
||||
|
||||
/* Do nothing */
|
||||
}
|
||||
|
||||
void guac_rdpdr_register_printer(guac_rdpdrPlugin* rdpdr) {
|
||||
@ -469,7 +196,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 +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;
|
||||
|
||||
/* 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"
|
||||
#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
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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 <freerdp/freerdp.h>
|
||||
@ -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.
|
||||
|
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