summaryrefslogtreecommitdiffstats
path: root/channels/printer/client/cups/printer_cups.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /channels/printer/client/cups/printer_cups.c
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'channels/printer/client/cups/printer_cups.c')
-rw-r--r--channels/printer/client/cups/printer_cups.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/channels/printer/client/cups/printer_cups.c b/channels/printer/client/cups/printer_cups.c
new file mode 100644
index 0000000..043b44e
--- /dev/null
+++ b/channels/printer/client/cups/printer_cups.c
@@ -0,0 +1,462 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Print Virtual Channel - CUPS driver
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ *
+ * Licensed 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 <winpr/assert.h>
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <time.h>
+#include <cups/cups.h>
+
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/string.h>
+
+#include <freerdp/channels/rdpdr.h>
+
+#include <freerdp/client/printer.h>
+
+#include <freerdp/channels/log.h>
+#define TAG CHANNELS_TAG("printer.client.cups")
+
+#if defined(__APPLE__)
+#include <errno.h>
+#include <sys/sysctl.h>
+
+static bool is_mac_os_sonoma_or_later(void)
+{
+ char str[256] = { 0 };
+ size_t size = sizeof(str);
+
+ errno = 0;
+ int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
+ if (ret != 0)
+ {
+ char buffer[256] = { 0 };
+ WLog_WARN(TAG, "sysctlbyname('kern.osrelease') failed with %s [%d]",
+ winpr_strerror(errno, buffer, sizeof(buffer)), errno);
+ return false;
+ }
+
+ int major = 0;
+ int minor = 0;
+ int patch = 0;
+ const int rc = sscanf(str, "%d.%d.%d", &major, &minor, &patch);
+ if (rc != 3)
+ {
+ WLog_WARN(TAG, "could not match '%s' to format '%d.%d.%d'");
+ return false;
+ }
+
+ if (major < 23)
+ return false;
+ return true;
+}
+#endif
+
+typedef struct
+{
+ rdpPrinterDriver driver;
+
+ size_t id_sequence;
+ size_t references;
+} rdpCupsPrinterDriver;
+
+typedef struct
+{
+ rdpPrintJob printjob;
+
+ http_t* printjob_object;
+ int printjob_id;
+} rdpCupsPrintJob;
+
+typedef struct
+{
+ rdpPrinter printer;
+
+ rdpCupsPrintJob* printjob;
+} rdpCupsPrinter;
+
+static void printer_cups_get_printjob_name(char* buf, size_t size, size_t id)
+{
+ struct tm tres;
+ const time_t tt = time(NULL);
+ const struct tm* t = localtime_r(&tt, &tres);
+
+ WINPR_ASSERT(buf);
+ WINPR_ASSERT(size > 0);
+
+ sprintf_s(buf, size - 1, "FreeRDP Print %04d-%02d-%02d %02d-%02d-%02d - Job %" PRIdz,
+ t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, id);
+}
+
+static bool http_status_ok(http_status_t status)
+{
+ switch (status)
+ {
+ case HTTP_OK:
+ case HTTP_CONTINUE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static UINT write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
+{
+ rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
+
+ WINPR_ASSERT(cups_printjob);
+
+ http_status_t rc =
+ cupsWriteRequestData(cups_printjob->printjob_object, (const char*)data, size);
+ if (!http_status_ok(rc))
+ WLog_WARN(TAG, "cupsWriteRequestData returned %s", httpStatus(rc));
+
+ return CHANNEL_RC_OK;
+}
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT printer_cups_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
+{
+ rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
+
+ WINPR_ASSERT(cups_printjob);
+
+ return write_printjob(printjob, data, size);
+}
+
+static void printer_cups_close_printjob(rdpPrintJob* printjob)
+{
+ rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
+ rdpCupsPrinter* cups_printer = NULL;
+
+ WINPR_ASSERT(cups_printjob);
+
+ ipp_status_t rc = cupsFinishDocument(cups_printjob->printjob_object, printjob->printer->name);
+ if (rc != IPP_OK)
+ WLog_WARN(TAG, "cupsFinishDocument returned %s", ippErrorString(rc));
+
+ cups_printjob->printjob_id = 0;
+ httpClose(cups_printjob->printjob_object);
+
+ cups_printer = (rdpCupsPrinter*)printjob->printer;
+ WINPR_ASSERT(cups_printer);
+
+ cups_printer->printjob = NULL;
+ free(cups_printjob);
+}
+
+static rdpPrintJob* printer_cups_create_printjob(rdpPrinter* printer, UINT32 id)
+{
+ rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
+ rdpCupsPrintJob* cups_printjob = NULL;
+
+ WINPR_ASSERT(cups_printer);
+
+ if (cups_printer->printjob != NULL)
+ {
+ WLog_WARN(TAG, "printjob [printer '%s'] already existing, abort!", printer->name);
+ return NULL;
+ }
+
+ cups_printjob = (rdpCupsPrintJob*)calloc(1, sizeof(rdpCupsPrintJob));
+ if (!cups_printjob)
+ return NULL;
+
+ cups_printjob->printjob.id = id;
+ cups_printjob->printjob.printer = printer;
+
+ cups_printjob->printjob.Write = printer_cups_write_printjob;
+ cups_printjob->printjob.Close = printer_cups_close_printjob;
+
+ {
+ char buf[100] = { 0 };
+
+ cups_printjob->printjob_object = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC,
+ HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, NULL);
+
+ if (!cups_printjob->printjob_object)
+ {
+ WLog_WARN(TAG, "httpConnect2 failed for '%s:%d", cupsServer(), ippPort());
+ free(cups_printjob);
+ return NULL;
+ }
+
+ printer_cups_get_printjob_name(buf, sizeof(buf), cups_printjob->printjob.id);
+
+ cups_printjob->printjob_id =
+ cupsCreateJob(cups_printjob->printjob_object, printer->name, buf, 0, NULL);
+
+ if (!cups_printjob->printjob_id)
+ {
+ WLog_WARN(TAG, "cupsCreateJob failed for printer '%s', driver '%s'", printer->name,
+ printer->driver);
+ httpClose(cups_printjob->printjob_object);
+ free(cups_printjob);
+ return NULL;
+ }
+
+ http_status_t rc = cupsStartDocument(cups_printjob->printjob_object, printer->name,
+ cups_printjob->printjob_id, buf, CUPS_FORMAT_AUTO, 1);
+ if (!http_status_ok(rc))
+ WLog_WARN(TAG, "cupsStartDocument [printer '%s', driver '%s'] returned %s",
+ printer->name, printer->driver, httpStatus(rc));
+ }
+
+ cups_printer->printjob = cups_printjob;
+
+ return &cups_printjob->printjob;
+}
+
+static rdpPrintJob* printer_cups_find_printjob(rdpPrinter* printer, UINT32 id)
+{
+ rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
+
+ WINPR_ASSERT(cups_printer);
+
+ if (cups_printer->printjob == NULL)
+ return NULL;
+ if (cups_printer->printjob->printjob.id != id)
+ return NULL;
+
+ return &cups_printer->printjob->printjob;
+}
+
+static void printer_cups_free_printer(rdpPrinter* printer)
+{
+ rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
+
+ WINPR_ASSERT(cups_printer);
+
+ if (cups_printer->printjob)
+ {
+ WINPR_ASSERT(cups_printer->printjob->printjob.Close);
+ cups_printer->printjob->printjob.Close(&cups_printer->printjob->printjob);
+ }
+
+ if (printer->backend)
+ {
+ WINPR_ASSERT(printer->backend->ReleaseRef);
+ printer->backend->ReleaseRef(printer->backend);
+ }
+ free(printer->name);
+ free(printer->driver);
+ free(printer);
+}
+
+static void printer_cups_add_ref_printer(rdpPrinter* printer)
+{
+ if (printer)
+ printer->references++;
+}
+
+static void printer_cups_release_ref_printer(rdpPrinter* printer)
+{
+ if (!printer)
+ return;
+ if (printer->references <= 1)
+ printer_cups_free_printer(printer);
+ else
+ printer->references--;
+}
+
+static rdpPrinter* printer_cups_new_printer(rdpCupsPrinterDriver* cups_driver, const char* name,
+ const char* driverName, BOOL is_default)
+{
+ rdpCupsPrinter* cups_printer = NULL;
+
+ cups_printer = (rdpCupsPrinter*)calloc(1, sizeof(rdpCupsPrinter));
+ if (!cups_printer)
+ return NULL;
+
+ cups_printer->printer.backend = &cups_driver->driver;
+
+ cups_printer->printer.id = cups_driver->id_sequence++;
+ cups_printer->printer.name = _strdup(name);
+ if (!cups_printer->printer.name)
+ goto fail;
+
+ if (driverName)
+ cups_printer->printer.driver = _strdup(driverName);
+ else
+ {
+ const char* dname = "MS Publisher Imagesetter";
+#if defined(__APPLE__)
+ if (is_mac_os_sonoma_or_later())
+ dname = "Microsoft Print to PDF";
+#endif
+ cups_printer->printer.driver = _strdup(dname);
+ }
+ if (!cups_printer->printer.driver)
+ goto fail;
+
+ cups_printer->printer.is_default = is_default;
+
+ cups_printer->printer.CreatePrintJob = printer_cups_create_printjob;
+ cups_printer->printer.FindPrintJob = printer_cups_find_printjob;
+ cups_printer->printer.AddRef = printer_cups_add_ref_printer;
+ cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer;
+
+ WINPR_ASSERT(cups_printer->printer.AddRef);
+ cups_printer->printer.AddRef(&cups_printer->printer);
+
+ WINPR_ASSERT(cups_printer->printer.backend->AddRef);
+ cups_printer->printer.backend->AddRef(cups_printer->printer.backend);
+
+ return &cups_printer->printer;
+
+fail:
+ printer_cups_free_printer(&cups_printer->printer);
+ return NULL;
+}
+
+static void printer_cups_release_enum_printers(rdpPrinter** printers)
+{
+ rdpPrinter** cur = printers;
+
+ while ((cur != NULL) && ((*cur) != NULL))
+ {
+ if ((*cur)->ReleaseRef)
+ (*cur)->ReleaseRef(*cur);
+ cur++;
+ }
+ free(printers);
+}
+
+static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver)
+{
+ rdpPrinter** printers = NULL;
+ int num_printers = 0;
+ cups_dest_t* dests = NULL;
+ BOOL haveDefault = FALSE;
+ const int num_dests = cupsGetDests(&dests);
+
+ WINPR_ASSERT(driver);
+ if (num_dests >= 0)
+ printers = (rdpPrinter**)calloc((size_t)num_dests + 1, sizeof(rdpPrinter*));
+ if (!printers)
+ return NULL;
+
+ for (size_t i = 0; i < num_dests; i++)
+ {
+ const cups_dest_t* dest = &dests[i];
+ if (dest->instance == NULL)
+ {
+ rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver,
+ dest->name, NULL, dest->is_default);
+ if (!current)
+ {
+ printer_cups_release_enum_printers(printers);
+ printers = NULL;
+ break;
+ }
+
+ if (current->is_default)
+ haveDefault = TRUE;
+
+ printers[num_printers++] = current;
+ }
+ }
+ cupsFreeDests(num_dests, dests);
+
+ if (!haveDefault && (num_dests > 0) && printers)
+ {
+ if (printers[0])
+ printers[0]->is_default = TRUE;
+ }
+
+ return printers;
+}
+
+static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver, const char* name,
+ const char* driverName, BOOL isDefault)
+{
+ rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
+
+ WINPR_ASSERT(cups_driver);
+ return printer_cups_new_printer(cups_driver, name, driverName, isDefault);
+}
+
+static void printer_cups_add_ref_driver(rdpPrinterDriver* driver)
+{
+ rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
+ if (cups_driver)
+ cups_driver->references++;
+}
+
+/* Singleton */
+static rdpCupsPrinterDriver* uniq_cups_driver = NULL;
+
+static void printer_cups_release_ref_driver(rdpPrinterDriver* driver)
+{
+ rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
+
+ WINPR_ASSERT(cups_driver);
+
+ if (cups_driver->references <= 1)
+ {
+ if (uniq_cups_driver == cups_driver)
+ uniq_cups_driver = NULL;
+ free(cups_driver);
+ }
+ else
+ cups_driver->references--;
+}
+
+FREERDP_ENTRY_POINT(UINT cups_freerdp_printer_client_subsystem_entry(void* arg))
+{
+ rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg;
+ if (!ppPrinter)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!uniq_cups_driver)
+ {
+ uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1, sizeof(rdpCupsPrinterDriver));
+
+ if (!uniq_cups_driver)
+ return ERROR_OUTOFMEMORY;
+
+ uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers;
+ uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers;
+ uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer;
+
+ uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver;
+ uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver;
+
+ uniq_cups_driver->id_sequence = 1;
+ }
+
+ WINPR_ASSERT(uniq_cups_driver->driver.AddRef);
+ uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver);
+
+ *ppPrinter = &uniq_cups_driver->driver;
+ return CHANNEL_RC_OK;
+}