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] = WINPR_C_ARRAY_INIT;
54 size_t size = sizeof(str);
55
56 errno = 0;
57 int ret = sysctlbyname("kern.osrelease", str, &size, nullptr, 0);
58 if (ret != 0)
59 {
60 char buffer[256] = WINPR_C_ARRAY_INIT;
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 = WINPR_C_ARRAY_INIT;
111 const time_t tt = time(nullptr);
112 const struct tm* t = localtime_r(&tt, &tres);
113
114 char* str = nullptr;
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 nullptr;
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 = nullptr;
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 = nullptr;
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 = nullptr;
189
190 WINPR_ASSERT(cups_printer);
191
192 if (cups_printer->printjob != nullptr)
193 {
194 WLog_WARN(TAG, "printjob [printer '%s'] already existing, abort!", printer->name);
195 return nullptr;
196 }
197
198 cups_printjob = (rdpCupsPrintJob*)calloc(1, sizeof(rdpCupsPrintJob));
199 if (!cups_printjob)
200 return nullptr;
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(), nullptr, AF_UNSPEC,
210 HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, nullptr);
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 nullptr;
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 nullptr;
225 }
226
227 cups_printjob->printjob_id =
228 cupsCreateJob(cups_printjob->printjob_object, printer->name, jobTitle, 0, nullptr);
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 nullptr;
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 == nullptr)
261 return nullptr;
262 if (cups_printer->printjob->printjob.id != id)
263 return nullptr;
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 = nullptr;
310
311 cups_printer = (rdpCupsPrinter*)calloc(1, sizeof(rdpCupsPrinter));
312 if (!cups_printer)
313 return nullptr;
314
315 cups_printer->printer.backend = &cups_driver->driver;
316 WINPR_ASSERT(cups_printer->printer.backend->AddRef);
317 cups_printer->printer.backend->AddRef(cups_printer->printer.backend);
318
319 cups_printer->printer.id = cups_driver->id_sequence++;
320 cups_printer->printer.name = _strdup(name);
321 if (!cups_printer->printer.name)
322 goto fail;
323
324 if (driverName)
325 cups_printer->printer.driver = _strdup(driverName);
326 else
327 {
328 const char* dname = "MS Publisher Imagesetter";
329#if defined(__APPLE__)
330 if (is_mac_os_sonoma_or_later())
331 dname = "Microsoft Print to PDF";
332#endif
333 cups_printer->printer.driver = _strdup(dname);
334 }
335 if (!cups_printer->printer.driver)
336 goto fail;
337
338 cups_printer->printer.is_default = is_default;
339
340 cups_printer->printer.CreatePrintJob = printer_cups_create_printjob;
341 cups_printer->printer.FindPrintJob = printer_cups_find_printjob;
342 cups_printer->printer.AddRef = printer_cups_add_ref_printer;
343 cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer;
344
345 WINPR_ASSERT(cups_printer->printer.AddRef);
346 cups_printer->printer.AddRef(&cups_printer->printer);
347
348 return &cups_printer->printer;
349
350fail:
351 printer_cups_free_printer(&cups_printer->printer);
352 return nullptr;
353}
354
355static void printer_cups_release_enum_printers(rdpPrinter** printers)
356{
357 rdpPrinter** cur = printers;
358
359 while ((cur != nullptr) && ((*cur) != nullptr))
360 {
361 if ((*cur)->ReleaseRef)
362 (*cur)->ReleaseRef(*cur);
363 cur++;
364 }
365 free((void*)printers);
366}
367
368static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver)
369{
370 rdpPrinter** printers = nullptr;
371 int num_printers = 0;
372 cups_dest_t* dests = nullptr;
373 BOOL haveDefault = FALSE;
374 const int num_dests = cupsGetDests(&dests);
375
376 WINPR_ASSERT(driver);
377 if (num_dests >= 0)
378 printers = (rdpPrinter**)calloc((size_t)num_dests + 1, sizeof(rdpPrinter*));
379 if (!printers)
380 return nullptr;
381
382 for (size_t i = 0; i < (size_t)num_dests; i++)
383 {
384 const cups_dest_t* dest = &dests[i];
385 if (dest->instance == nullptr)
386 {
387 rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver,
388 dest->name, nullptr, dest->is_default);
389 if (!current)
390 {
391 printer_cups_release_enum_printers(printers);
392 printers = nullptr;
393 break;
394 }
395
396 if (current->is_default)
397 haveDefault = TRUE;
398
399 printers[num_printers++] = current;
400 }
401 }
402 cupsFreeDests(num_dests, dests);
403
404 if (!haveDefault && (num_dests > 0) && printers)
405 {
406 if (printers[0])
407 printers[0]->is_default = TRUE;
408 }
409
410 return printers;
411}
412
413static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver, const char* name,
414 const char* driverName, BOOL isDefault)
415{
416 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
417
418 WINPR_ASSERT(cups_driver);
419 return printer_cups_new_printer(cups_driver, name, driverName, isDefault);
420}
421
422static void printer_cups_add_ref_driver(rdpPrinterDriver* driver)
423{
424 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
425 if (cups_driver)
426 cups_driver->references++;
427}
428
429/* Singleton */
430static rdpCupsPrinterDriver* uniq_cups_driver = nullptr;
431
432static void printer_cups_release_ref_driver(rdpPrinterDriver* driver)
433{
434 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
435
436 WINPR_ASSERT(cups_driver);
437
438 if (cups_driver->references <= 1)
439 {
440 if (uniq_cups_driver == cups_driver)
441 uniq_cups_driver = nullptr;
442 free(cups_driver);
443 }
444 else
445 cups_driver->references--;
446}
447
448FREERDP_ENTRY_POINT(UINT VCAPITYPE cups_freerdp_printer_client_subsystem_entry(void* arg))
449{
450 rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg;
451 if (!ppPrinter)
452 return ERROR_INVALID_PARAMETER;
453
454 if (!uniq_cups_driver)
455 {
456 uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1, sizeof(rdpCupsPrinterDriver));
457
458 if (!uniq_cups_driver)
459 return ERROR_OUTOFMEMORY;
460
461 uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers;
462 uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers;
463 uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer;
464
465 uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver;
466 uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver;
467
468 uniq_cups_driver->id_sequence = 1;
469 }
470
471 WINPR_ASSERT(uniq_cups_driver->driver.AddRef);
472 uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver);
473
474 *ppPrinter = &uniq_cups_driver->driver;
475 return CHANNEL_RC_OK;
476}