FreeRDP
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 
50 static 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 
83 typedef struct
84 {
85  rdpPrinterDriver driver;
86 
87  size_t id_sequence;
88  size_t references;
89 } rdpCupsPrinterDriver;
90 
91 typedef struct
92 {
93  rdpPrintJob printjob;
94 
95  http_t* printjob_object;
96  int printjob_id;
97 } rdpCupsPrintJob;
98 
99 typedef struct
100 {
101  rdpPrinter printer;
102 
103  rdpCupsPrintJob* printjob;
104 } rdpCupsPrinter;
105 
106 static void printer_cups_get_printjob_name(char* buf, size_t size, size_t id)
107 {
108  struct tm tres;
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 
120 static 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 
132 static 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 }
150 static UINT printer_cups_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size)
151 {
152  return write_printjob(printjob, data, size);
153 }
154 
155 static 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 
176 static 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 
238 static 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 
252 static 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 
274 static void printer_cups_add_ref_printer(rdpPrinter* printer)
275 {
276  if (printer)
277  printer->references++;
278 }
279 
280 static 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 
290 static 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 
335 fail:
336  printer_cups_free_printer(&cups_printer->printer);
337  return NULL;
338 }
339 
340 static 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(printers);
351 }
352 
353 static 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 
398 static 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 
407 static 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 */
415 static rdpCupsPrinterDriver* uniq_cups_driver = NULL;
416 
417 static 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 
433 FREERDP_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 }