FreeRDP
Loading...
Searching...
No Matches
printer_cups.c
1
23#include <winpr/assert.h>
24
25#include <freerdp/config.h>
26#include <freerdp/utils/helpers.h>
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <pthread.h>
32
33#include <time.h>
34#include <cups/cups.h>
35
36#include <winpr/crt.h>
37#include <winpr/file.h>
38#include <winpr/string.h>
39
40#include <freerdp/channels/rdpdr.h>
41
42#include <freerdp/client/printer.h>
43
44#include <freerdp/channels/log.h>
45#define TAG CHANNELS_TAG("printer.client.cups")
46
47#if defined(__APPLE__)
48#include <errno.h>
49#include <sys/sysctl.h>
50
51static bool is_mac_os_sonoma_or_later(void)
52{
53 char str[256] = { 0 };
54 size_t size = sizeof(str);
55
56 errno = 0;
57 int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
58 if (ret != 0)
59 {
60 char buffer[256] = { 0 };
61 WLog_WARN(TAG, "sysctlbyname('kern.osrelease') failed with %s [%d]",
62 winpr_strerror(errno, buffer, sizeof(buffer)), errno);
63 return false;
64 }
65
66 int major = 0;
67 int minor = 0;
68 int patch = 0;
69
70 // NOLINTNEXTLINE(cert-err34-c)
71 const int rc = sscanf(str, "%d.%d.%d", &major, &minor, &patch);
72 if (rc != 3)
73 {
74 WLog_WARN(TAG, "could not match '%s' to format '%d.%d.%d'", str, major, minor, patch);
75 return false;
76 }
77
78 if (major < 23)
79 return false;
80 return true;
81}
82#endif
83
84typedef struct
85{
86 rdpPrinterDriver driver;
87
88 size_t id_sequence;
89 size_t references;
90} rdpCupsPrinterDriver;
91
92typedef struct
93{
94 rdpPrintJob printjob;
95
96 http_t* printjob_object;
97 int printjob_id;
98} rdpCupsPrintJob;
99
100typedef struct
101{
102 rdpPrinter printer;
103
104 rdpCupsPrintJob* printjob;
105} rdpCupsPrinter;
106
107WINPR_ATTR_MALLOC(free, 1)
108static char* printer_cups_get_printjob_name(size_t id)
109{
110 struct tm tres = { 0 };
111 const time_t tt = time(NULL);
112 const struct tm* t = localtime_r(&tt, &tres);
113
114 char* str = NULL;
115 size_t len = 0;
116 const int rc =
117 winpr_asprintf(&str, &len, "%s Print %04d-%02d-%02d %02d-%02d-%02d - Job %" PRIuz,
118 freerdp_getApplicationDetailsString(), t->tm_year + 1900, t->tm_mon + 1,
119 t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, id);
120 if (rc <= 0)
121 {
122 free(str);
123 return NULL;
124 }
125
126 return str;
127}
128
129static bool http_status_ok(http_status_t status)
130{
131 switch (status)
132 {
133 case HTTP_OK:
134 case HTTP_CONTINUE:
135 return true;
136 default:
137 return false;
138 }
139}
140
141static UINT write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
142{
143 rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
144
145 WINPR_ASSERT(cups_printjob);
146
147 http_status_t rc =
148 cupsWriteRequestData(cups_printjob->printjob_object, (const char*)data, size);
149 if (!http_status_ok(rc))
150 WLog_WARN(TAG, "cupsWriteRequestData returned %s", httpStatus(rc));
151
152 return CHANNEL_RC_OK;
153}
159static UINT printer_cups_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
160{
161 return write_printjob(printjob, data, size);
162}
163
164static void printer_cups_close_printjob(rdpPrintJob* printjob)
165{
166 rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
167 rdpCupsPrinter* cups_printer = NULL;
168
169 WINPR_ASSERT(cups_printjob);
170
171 ipp_status_t rc = cupsFinishDocument(cups_printjob->printjob_object, printjob->printer->name);
172 if (rc != IPP_OK)
173 WLog_WARN(TAG, "cupsFinishDocument returned %s", ippErrorString(rc));
174
175 cups_printjob->printjob_id = 0;
176 httpClose(cups_printjob->printjob_object);
177
178 cups_printer = (rdpCupsPrinter*)printjob->printer;
179 WINPR_ASSERT(cups_printer);
180
181 cups_printer->printjob = NULL;
182 free(cups_printjob);
183}
184
185static rdpPrintJob* printer_cups_create_printjob(rdpPrinter* printer, UINT32 id)
186{
187 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
188 rdpCupsPrintJob* cups_printjob = NULL;
189
190 WINPR_ASSERT(cups_printer);
191
192 if (cups_printer->printjob != NULL)
193 {
194 WLog_WARN(TAG, "printjob [printer '%s'] already existing, abort!", printer->name);
195 return NULL;
196 }
197
198 cups_printjob = (rdpCupsPrintJob*)calloc(1, sizeof(rdpCupsPrintJob));
199 if (!cups_printjob)
200 return NULL;
201
202 cups_printjob->printjob.id = id;
203 cups_printjob->printjob.printer = printer;
204
205 cups_printjob->printjob.Write = printer_cups_write_printjob;
206 cups_printjob->printjob.Close = printer_cups_close_printjob;
207
208 {
209 cups_printjob->printjob_object = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC,
210 HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, NULL);
211
212 if (!cups_printjob->printjob_object)
213 {
214 WLog_WARN(TAG, "httpConnect2 failed for '%s:%d", cupsServer(), ippPort());
215 free(cups_printjob);
216 return NULL;
217 }
218
219 char* jobTitle = printer_cups_get_printjob_name(cups_printjob->printjob.id);
220 if (!jobTitle)
221 {
222 httpClose(cups_printjob->printjob_object);
223 free(cups_printjob);
224 return NULL;
225 }
226
227 cups_printjob->printjob_id =
228 cupsCreateJob(cups_printjob->printjob_object, printer->name, jobTitle, 0, NULL);
229
230 if (!cups_printjob->printjob_id)
231 {
232 WLog_WARN(TAG, "cupsCreateJob failed for printer '%s', driver '%s'", printer->name,
233 printer->driver);
234 httpClose(cups_printjob->printjob_object);
235 free(cups_printjob);
236 free(jobTitle);
237 return NULL;
238 }
239
240 http_status_t rc =
241 cupsStartDocument(cups_printjob->printjob_object, printer->name,
242 cups_printjob->printjob_id, jobTitle, CUPS_FORMAT_AUTO, 1);
243 free(jobTitle);
244 if (!http_status_ok(rc))
245 WLog_WARN(TAG, "cupsStartDocument [printer '%s', driver '%s'] returned %s",
246 printer->name, printer->driver, httpStatus(rc));
247 }
248
249 cups_printer->printjob = cups_printjob;
250
251 return &cups_printjob->printjob;
252}
253
254static rdpPrintJob* printer_cups_find_printjob(rdpPrinter* printer, UINT32 id)
255{
256 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
257
258 WINPR_ASSERT(cups_printer);
259
260 if (cups_printer->printjob == NULL)
261 return NULL;
262 if (cups_printer->printjob->printjob.id != id)
263 return NULL;
264
265 return &cups_printer->printjob->printjob;
266}
267
268static void printer_cups_free_printer(rdpPrinter* printer)
269{
270 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
271
272 WINPR_ASSERT(cups_printer);
273
274 if (cups_printer->printjob)
275 {
276 WINPR_ASSERT(cups_printer->printjob->printjob.Close);
277 cups_printer->printjob->printjob.Close(&cups_printer->printjob->printjob);
278 }
279
280 if (printer->backend)
281 {
282 WINPR_ASSERT(printer->backend->ReleaseRef);
283 printer->backend->ReleaseRef(printer->backend);
284 }
285 free(printer->name);
286 free(printer->driver);
287 free(printer);
288}
289
290static void printer_cups_add_ref_printer(rdpPrinter* printer)
291{
292 if (printer)
293 printer->references++;
294}
295
296static void printer_cups_release_ref_printer(rdpPrinter* printer)
297{
298 if (!printer)
299 return;
300 if (printer->references <= 1)
301 printer_cups_free_printer(printer);
302 else
303 printer->references--;
304}
305
306static rdpPrinter* printer_cups_new_printer(rdpCupsPrinterDriver* cups_driver, const char* name,
307 const char* driverName, BOOL is_default)
308{
309 rdpCupsPrinter* cups_printer = NULL;
310
311 cups_printer = (rdpCupsPrinter*)calloc(1, sizeof(rdpCupsPrinter));
312 if (!cups_printer)
313 return NULL;
314
315 cups_printer->printer.backend = &cups_driver->driver;
316
317 cups_printer->printer.id = cups_driver->id_sequence++;
318 cups_printer->printer.name = _strdup(name);
319 if (!cups_printer->printer.name)
320 goto fail;
321
322 if (driverName)
323 cups_printer->printer.driver = _strdup(driverName);
324 else
325 {
326 const char* dname = "MS Publisher Imagesetter";
327#if defined(__APPLE__)
328 if (is_mac_os_sonoma_or_later())
329 dname = "Microsoft Print to PDF";
330#endif
331 cups_printer->printer.driver = _strdup(dname);
332 }
333 if (!cups_printer->printer.driver)
334 goto fail;
335
336 cups_printer->printer.is_default = is_default;
337
338 cups_printer->printer.CreatePrintJob = printer_cups_create_printjob;
339 cups_printer->printer.FindPrintJob = printer_cups_find_printjob;
340 cups_printer->printer.AddRef = printer_cups_add_ref_printer;
341 cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer;
342
343 WINPR_ASSERT(cups_printer->printer.AddRef);
344 cups_printer->printer.AddRef(&cups_printer->printer);
345
346 WINPR_ASSERT(cups_printer->printer.backend->AddRef);
347 cups_printer->printer.backend->AddRef(cups_printer->printer.backend);
348
349 return &cups_printer->printer;
350
351fail:
352 printer_cups_free_printer(&cups_printer->printer);
353 return NULL;
354}
355
356static void printer_cups_release_enum_printers(rdpPrinter** printers)
357{
358 rdpPrinter** cur = printers;
359
360 while ((cur != NULL) && ((*cur) != NULL))
361 {
362 if ((*cur)->ReleaseRef)
363 (*cur)->ReleaseRef(*cur);
364 cur++;
365 }
366 free((void*)printers);
367}
368
369static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver)
370{
371 rdpPrinter** printers = NULL;
372 int num_printers = 0;
373 cups_dest_t* dests = NULL;
374 BOOL haveDefault = FALSE;
375 const int num_dests = cupsGetDests(&dests);
376
377 WINPR_ASSERT(driver);
378 if (num_dests >= 0)
379 printers = (rdpPrinter**)calloc((size_t)num_dests + 1, sizeof(rdpPrinter*));
380 if (!printers)
381 return NULL;
382
383 for (size_t i = 0; i < (size_t)num_dests; i++)
384 {
385 const cups_dest_t* dest = &dests[i];
386 if (dest->instance == NULL)
387 {
388 rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver,
389 dest->name, NULL, dest->is_default);
390 if (!current)
391 {
392 printer_cups_release_enum_printers(printers);
393 printers = NULL;
394 break;
395 }
396
397 if (current->is_default)
398 haveDefault = TRUE;
399
400 printers[num_printers++] = current;
401 }
402 }
403 cupsFreeDests(num_dests, dests);
404
405 if (!haveDefault && (num_dests > 0) && printers)
406 {
407 if (printers[0])
408 printers[0]->is_default = TRUE;
409 }
410
411 return printers;
412}
413
414static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver, const char* name,
415 const char* driverName, BOOL isDefault)
416{
417 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
418
419 WINPR_ASSERT(cups_driver);
420 return printer_cups_new_printer(cups_driver, name, driverName, isDefault);
421}
422
423static void printer_cups_add_ref_driver(rdpPrinterDriver* driver)
424{
425 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
426 if (cups_driver)
427 cups_driver->references++;
428}
429
430/* Singleton */
431static rdpCupsPrinterDriver* uniq_cups_driver = NULL;
432
433static void printer_cups_release_ref_driver(rdpPrinterDriver* driver)
434{
435 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
436
437 WINPR_ASSERT(cups_driver);
438
439 if (cups_driver->references <= 1)
440 {
441 if (uniq_cups_driver == cups_driver)
442 uniq_cups_driver = NULL;
443 free(cups_driver);
444 }
445 else
446 cups_driver->references--;
447}
448
449FREERDP_ENTRY_POINT(UINT VCAPITYPE cups_freerdp_printer_client_subsystem_entry(void* arg))
450{
451 rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg;
452 if (!ppPrinter)
453 return ERROR_INVALID_PARAMETER;
454
455 if (!uniq_cups_driver)
456 {
457 uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1, sizeof(rdpCupsPrinterDriver));
458
459 if (!uniq_cups_driver)
460 return ERROR_OUTOFMEMORY;
461
462 uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers;
463 uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers;
464 uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer;
465
466 uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver;
467 uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver;
468
469 uniq_cups_driver->id_sequence = 1;
470 }
471
472 WINPR_ASSERT(uniq_cups_driver->driver.AddRef);
473 uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver);
474
475 *ppPrinter = &uniq_cups_driver->driver;
476 return CHANNEL_RC_OK;
477}