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