GCC Code Coverage Report


Directory: ./
File: panels/printers/pp-host.c
Date: 2024-05-03 09:46:52
Exec Total Coverage
Lines: 0 301 0.0%
Functions: 0 25 0.0%
Branches: 0 145 0.0%

Line Branch Exec Source
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright 2012 Red Hat, Inc,
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marek Kasik <mkasik@redhat.com>
19 */
20
21 #include "config.h"
22
23 #include "pp-host.h"
24
25 #include <glib/gi18n.h>
26
27 #define BUFFER_LENGTH 1024
28
29 typedef struct
30 {
31 gchar *hostname;
32 gint port;
33 } PpHostPrivate;
34
35 G_DEFINE_TYPE_WITH_PRIVATE (PpHost, pp_host, G_TYPE_OBJECT);
36
37 enum {
38 PROP_0 = 0,
39 PROP_HOSTNAME,
40 PROP_PORT,
41 };
42
43 enum {
44 AUTHENTICATION_REQUIRED,
45 LAST_SIGNAL
46 };
47
48 static guint signals[LAST_SIGNAL] = { 0 };
49
50 static void
51 pp_host_finalize (GObject *object)
52 {
53 PpHost *self = PP_HOST (object);
54 PpHostPrivate *priv = pp_host_get_instance_private (self);
55
56 g_clear_pointer (&priv->hostname, g_free);
57
58 G_OBJECT_CLASS (pp_host_parent_class)->finalize (object);
59 }
60
61 static void
62 pp_host_get_property (GObject *object,
63 guint prop_id,
64 GValue *value,
65 GParamSpec *param_spec)
66 {
67 PpHost *self = PP_HOST (object);
68 PpHostPrivate *priv = pp_host_get_instance_private (self);
69
70 switch (prop_id)
71 {
72 case PROP_HOSTNAME:
73 g_value_set_string (value, priv->hostname);
74 break;
75 case PROP_PORT:
76 g_value_set_int (value, priv->port);
77 break;
78 default:
79 G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
80 prop_id,
81 param_spec);
82 break;
83 }
84 }
85
86 static void
87 pp_host_set_property (GObject *object,
88 guint prop_id,
89 const GValue *value,
90 GParamSpec *param_spec)
91 {
92 PpHost *self = PP_HOST (object);
93 PpHostPrivate *priv = pp_host_get_instance_private (self);
94
95 switch (prop_id)
96 {
97 case PROP_HOSTNAME:
98 g_free (priv->hostname);
99 priv->hostname = g_value_dup_string (value);
100 break;
101 case PROP_PORT:
102 priv->port = g_value_get_int (value);
103 break;
104 default:
105 G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
106 prop_id,
107 param_spec);
108 break;
109 }
110 }
111
112 static void
113 pp_host_class_init (PpHostClass *klass)
114 {
115 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
116
117 gobject_class->set_property = pp_host_set_property;
118 gobject_class->get_property = pp_host_get_property;
119 gobject_class->finalize = pp_host_finalize;
120
121 g_object_class_install_property (gobject_class, PROP_HOSTNAME,
122 g_param_spec_string ("hostname",
123 "Hostname",
124 "The hostname",
125 NULL,
126 G_PARAM_READWRITE));
127
128 g_object_class_install_property (gobject_class, PROP_PORT,
129 g_param_spec_int ("port",
130 "Port",
131 "The port",
132 -1, G_MAXINT32, PP_HOST_UNSET_PORT,
133 G_PARAM_READWRITE));
134
135 signals[AUTHENTICATION_REQUIRED] =
136 g_signal_new ("authentication-required",
137 G_TYPE_FROM_CLASS (klass),
138 G_SIGNAL_RUN_LAST,
139 0,
140 NULL, NULL, NULL,
141 G_TYPE_NONE, 0);
142 }
143
144 static void
145 pp_host_init (PpHost *self)
146 {
147 PpHostPrivate *priv = pp_host_get_instance_private (self);
148 priv->port = PP_HOST_UNSET_PORT;
149 }
150
151 PpHost *
152 pp_host_new (const gchar *hostname)
153 {
154 return g_object_new (PP_TYPE_HOST,
155 "hostname", hostname,
156 NULL);
157 }
158
159 static gchar **
160 line_split (gchar *line)
161 {
162 gboolean escaped = FALSE;
163 gboolean quoted = FALSE;
164 gboolean in_word = FALSE;
165 gchar **words = NULL;
166 gchar **result = NULL;
167 g_autofree gchar *buffer = NULL;
168 gchar ch;
169 gint n = 0;
170 gint i, j = 0, k = 0;
171
172 if (line)
173 {
174 n = strlen (line);
175 words = g_new0 (gchar *, n + 1);
176 buffer = g_new0 (gchar, n + 1);
177
178 for (i = 0; i < n; i++)
179 {
180 ch = line[i];
181
182 if (escaped)
183 {
184 buffer[k++] = ch;
185 escaped = FALSE;
186 continue;
187 }
188
189 if (ch == '\\')
190 {
191 in_word = TRUE;
192 escaped = TRUE;
193 continue;
194 }
195
196 if (in_word)
197 {
198 if (quoted)
199 {
200 if (ch == '"')
201 quoted = FALSE;
202 else
203 buffer[k++] = ch;
204 }
205 else if (g_ascii_isspace (ch))
206 {
207 words[j++] = g_strdup (buffer);
208 memset (buffer, 0, n + 1);
209 k = 0;
210 in_word = FALSE;
211 }
212 else if (ch == '"')
213 quoted = TRUE;
214 else
215 buffer[k++] = ch;
216 }
217 else
218 {
219 if (ch == '"')
220 {
221 in_word = TRUE;
222 quoted = TRUE;
223 }
224 else if (!g_ascii_isspace (ch))
225 {
226 in_word = TRUE;
227 buffer[k++] = ch;
228 }
229 }
230 }
231 }
232
233 if (buffer && buffer[0] != '\0')
234 words[j++] = g_strdup (buffer);
235
236 result = g_strdupv (words);
237 g_strfreev (words);
238
239 return result;
240 }
241
242 static void
243 _pp_host_get_snmp_devices_thread (GTask *task,
244 gpointer source_object,
245 gpointer task_data,
246 GCancellable *cancellable)
247 {
248 PpHost *self = source_object;
249 PpHostPrivate *priv = pp_host_get_instance_private (self);
250 g_autoptr(GPtrArray) devices = NULL;
251 g_autoptr(GError) error = NULL;
252 g_auto(GStrv) argv = NULL;
253 g_autofree gchar *stdout_string = NULL;
254 gint exit_status;
255
256 devices = g_ptr_array_new_with_free_func (g_object_unref);
257
258 argv = g_new0 (gchar *, 3);
259 argv[0] = g_strdup ("/usr/lib/cups/backend/snmp");
260 argv[1] = g_strdup (priv->hostname);
261
262 /* Use SNMP to get printer's informations */
263 g_spawn_sync (NULL,
264 argv,
265 NULL,
266 G_SPAWN_STDERR_TO_DEV_NULL,
267 NULL,
268 NULL,
269 &stdout_string,
270 NULL,
271 &exit_status,
272 &error);
273
274 if (exit_status == 0 && stdout_string)
275 {
276 g_auto(GStrv) printer_informations = NULL;
277 gint length;
278
279 printer_informations = line_split (stdout_string);
280 length = g_strv_length (printer_informations);
281
282 if (length >= 4)
283 {
284 g_autofree gchar *device_name = NULL;
285 gboolean is_network_device;
286 PpPrintDevice *device;
287
288 device_name = g_strdup (printer_informations[3]);
289 g_strcanon (device_name, ALLOWED_CHARACTERS, '-');
290 is_network_device = g_strcmp0 (printer_informations[0], "network") == 0;
291
292 device = g_object_new (PP_TYPE_PRINT_DEVICE,
293 "is-network-device", is_network_device,
294 "device-uri", printer_informations[1],
295 "device-make-and-model", printer_informations[2],
296 "device-info", printer_informations[3],
297 "acquisition-method", ACQUISITION_METHOD_SNMP,
298 "device-name", device_name,
299 NULL);
300
301 if (length >= 5 && printer_informations[4][0] != '\0')
302 g_object_set (device, "device-id", printer_informations[4], NULL);
303
304 if (length >= 6 && printer_informations[5][0] != '\0')
305 g_object_set (device, "device-location", printer_informations[5], NULL);
306
307 g_ptr_array_add (devices, device);
308 }
309 }
310
311 g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
312 }
313
314 void
315 pp_host_get_snmp_devices_async (PpHost *self,
316 GCancellable *cancellable,
317 GAsyncReadyCallback callback,
318 gpointer user_data)
319 {
320 g_autoptr(GTask) task = NULL;
321
322 task = g_task_new (self, cancellable, callback, user_data);
323 g_task_run_in_thread (task, _pp_host_get_snmp_devices_thread);
324 }
325
326 GPtrArray *
327 pp_host_get_snmp_devices_finish (PpHost *self,
328 GAsyncResult *res,
329 GError **error)
330 {
331 g_return_val_if_fail (g_task_is_valid (res, self), NULL);
332 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
333 return g_task_propagate_pointer (G_TASK (res), error);
334 }
335
336 static void
337 _pp_host_get_remote_cups_devices_thread (GTask *task,
338 gpointer source_object,
339 gpointer task_data,
340 GCancellable *cancellable)
341 {
342 cups_dest_t *dests = NULL;
343 PpHost *self = (PpHost *) source_object;
344 PpHostPrivate *priv = pp_host_get_instance_private (self);
345 g_autoptr(GPtrArray) devices = NULL;
346 http_t *http;
347 gint num_of_devices = 0;
348 gint port;
349 gint i;
350
351 devices = g_ptr_array_new_with_free_func (g_object_unref);
352
353 if (priv->port == PP_HOST_UNSET_PORT)
354 port = PP_HOST_DEFAULT_IPP_PORT;
355 else
356 port = priv->port;
357
358 /* Connect to remote CUPS server and get its devices */
359 #ifdef HAVE_CUPS_HTTPCONNECT2
360 http = httpConnect2 (priv->hostname, port, NULL, AF_UNSPEC,
361 HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL);
362 #else
363 http = httpConnect (priv->hostname, port);
364 #endif
365 if (http)
366 {
367 num_of_devices = cupsGetDests2 (http, &dests);
368 if (num_of_devices > 0)
369 {
370 for (i = 0; i < num_of_devices; i++)
371 {
372 g_autofree gchar *device_uri = NULL;
373 const char *device_location;
374 PpPrintDevice *device;
375
376 device_uri = g_strdup_printf ("ipp://%s:%d/printers/%s",
377 priv->hostname,
378 port,
379 dests[i].name);
380
381 device_location = cupsGetOption ("printer-location",
382 dests[i].num_options,
383 dests[i].options);
384
385 device = g_object_new (PP_TYPE_PRINT_DEVICE,
386 "is-network-device", TRUE,
387 "device-uri", device_uri,
388 "device-name", dests[i].name,
389 "device-location", device_location,
390 "host-name", priv->hostname,
391 "host-port", port,
392 "acquisition-method", ACQUISITION_METHOD_REMOTE_CUPS_SERVER,
393 NULL);
394 g_ptr_array_add (devices, device);
395 }
396 }
397
398 httpClose (http);
399 }
400
401 g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
402 }
403
404 void
405 pp_host_get_remote_cups_devices_async (PpHost *self,
406 GCancellable *cancellable,
407 GAsyncReadyCallback callback,
408 gpointer user_data)
409 {
410 g_autoptr(GTask) task = NULL;
411
412 task = g_task_new (self, cancellable, callback, user_data);
413 g_task_run_in_thread (task, _pp_host_get_remote_cups_devices_thread);
414 }
415
416 GPtrArray *
417 pp_host_get_remote_cups_devices_finish (PpHost *self,
418 GAsyncResult *res,
419 GError **error)
420 {
421 g_return_val_if_fail (g_task_is_valid (res, self), NULL);
422 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
423 return g_task_propagate_pointer (G_TASK (res), error);
424 }
425
426 typedef struct
427 {
428 PpHost *host;
429 gint port;
430 } JetDirectData;
431
432 static void
433 jetdirect_data_free (JetDirectData *data)
434 {
435 if (data != NULL)
436 {
437 g_clear_object (&data->host);
438 g_free (data);
439 }
440 }
441
442 static void
443 jetdirect_connection_test_cb (GObject *source_object,
444 GAsyncResult *res,
445 gpointer user_data)
446 {
447 g_autoptr(GSocketConnection) connection = NULL;
448 PpHostPrivate *priv;
449 JetDirectData *data;
450 g_autoptr(GPtrArray) devices = NULL;
451 g_autoptr(GError) error = NULL;
452 g_autoptr(GTask) task = G_TASK (user_data);
453
454 data = g_task_get_task_data (task);
455 priv = pp_host_get_instance_private (data->host);
456
457 devices = g_ptr_array_new_with_free_func (g_object_unref);
458
459 connection = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (source_object),
460 res,
461 &error);
462
463 if (connection != NULL)
464 {
465 g_autofree gchar *device_uri = NULL;
466 PpPrintDevice *device;
467
468 g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
469
470 device_uri = g_strdup_printf ("socket://%s:%d",
471 priv->hostname,
472 data->port);
473
474 device = g_object_new (PP_TYPE_PRINT_DEVICE,
475 "is-network-device", TRUE,
476 "device-uri", device_uri,
477 /* Translators: The found device is a JetDirect printer */
478 "device-name", _("JetDirect Printer"),
479 "host-name", priv->hostname,
480 "host-port", data->port,
481 "acquisition-method", ACQUISITION_METHOD_JETDIRECT,
482 NULL);
483 g_ptr_array_add (devices, device);
484 }
485
486 g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
487 }
488
489 /* Test whether given host has an AppSocket/HP JetDirect printer connected.
490 See http://en.wikipedia.org/wiki/JetDirect
491 http://www.cups.org/documentation.php/network.html */
492 void
493 pp_host_get_jetdirect_devices_async (PpHost *self,
494 GCancellable *cancellable,
495 GAsyncReadyCallback callback,
496 gpointer user_data)
497 {
498 PpHostPrivate *priv = pp_host_get_instance_private (self);
499 JetDirectData *data;
500 g_autoptr(GTask) task = NULL;
501 g_autofree gchar *address = NULL;
502
503 data = g_new0 (JetDirectData, 1);
504 data->host = g_object_ref (self);
505
506 if (priv->port == PP_HOST_UNSET_PORT)
507 data->port = PP_HOST_DEFAULT_JETDIRECT_PORT;
508 else
509 data->port = priv->port;
510
511 task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
512 g_task_set_task_data (task, data, (GDestroyNotify) jetdirect_data_free);
513
514 address = g_strdup_printf ("%s:%d", priv->hostname, data->port);
515 if (address != NULL && address[0] != '/')
516 {
517 g_autoptr(GSocketClient) client = NULL;
518
519 client = g_socket_client_new ();
520
521 g_socket_client_connect_to_host_async (client,
522 address,
523 data->port,
524 cancellable,
525 jetdirect_connection_test_cb,
526 g_steal_pointer (&task));
527 }
528 else
529 {
530 GPtrArray *devices = g_ptr_array_new_with_free_func (g_object_unref);
531 g_task_return_pointer (task, devices, (GDestroyNotify) g_ptr_array_unref);
532 }
533 }
534
535 GPtrArray *
536 pp_host_get_jetdirect_devices_finish (PpHost *self,
537 GAsyncResult *res,
538 GError **error)
539 {
540 g_return_val_if_fail (g_task_is_valid (res, self), NULL);
541 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
542 return g_task_propagate_pointer (G_TASK (res), error);
543 }
544
545 static gboolean
546 test_lpd_queue (GSocketClient *client,
547 gchar *address,
548 gint port,
549 GCancellable *cancellable,
550 gchar *queue_name)
551 {
552 g_autoptr(GSocketConnection) connection = NULL;
553 gboolean result = FALSE;
554 g_autoptr(GError) error = NULL;
555
556 connection = g_socket_client_connect_to_host (client,
557 address,
558 port,
559 cancellable,
560 &error);
561
562 if (connection != NULL)
563 {
564 if (G_IS_TCP_CONNECTION (connection))
565 {
566 GOutputStream *output;
567 GInputStream *input;
568 gssize bytes_read, bytes_written;
569 gchar buffer[BUFFER_LENGTH];
570 gint length;
571
572 output = g_io_stream_get_output_stream (G_IO_STREAM (connection));
573 input = g_io_stream_get_input_stream (G_IO_STREAM (connection));
574
575 /* This LPD command is explained in RFC 1179, section 5.2 */
576 length = g_snprintf (buffer, BUFFER_LENGTH, "\2%s\n", queue_name);
577
578 bytes_written = g_output_stream_write (output,
579 buffer,
580 length,
581 NULL,
582 &error);
583
584 if (bytes_written != -1)
585 {
586 bytes_read = g_input_stream_read (input,
587 buffer,
588 BUFFER_LENGTH,
589 NULL,
590 &error);
591
592 if (bytes_read != -1)
593 {
594 if (bytes_read > 0 && buffer[0] == 0)
595 {
596 /* This LPD command is explained in RFC 1179, section 6.1 */
597 length = g_snprintf (buffer, BUFFER_LENGTH, "\1\n");
598
599 bytes_written = g_output_stream_write (output,
600 buffer,
601 length,
602 NULL,
603 &error);
604
605 result = TRUE;
606 }
607 }
608 }
609 }
610
611 g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
612 }
613
614 return result;
615 }
616
617 static void
618 _pp_host_get_lpd_devices_thread (GTask *task,
619 gpointer source_object,
620 gpointer task_data,
621 GCancellable *cancellable)
622 {
623 g_autoptr(GSocketConnection) connection = NULL;
624 PpHost *self = source_object;
625 PpHostPrivate *priv = pp_host_get_instance_private (self);
626 g_autoptr(GPtrArray) devices = NULL;
627 g_autoptr(GSocketClient) client = NULL;
628 g_autoptr(GError) error = NULL;
629 GList *candidates = NULL;
630 GList *iter;
631 gchar *found_queue = NULL;
632 gchar *candidate;
633 g_autofree gchar *address = NULL;
634 gint port;
635 gint i;
636
637 if (priv->port == PP_HOST_UNSET_PORT)
638 port = PP_HOST_DEFAULT_LPD_PORT;
639 else
640 port = priv->port;
641
642 devices = g_ptr_array_new_with_free_func (g_object_unref);
643
644 address = g_strdup_printf ("%s:%d", priv->hostname, port);
645 if (address == NULL || address[0] == '/')
646 {
647 g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
648 return;
649 }
650
651 client = g_socket_client_new ();
652
653 connection = g_socket_client_connect_to_host (client,
654 address,
655 port,
656 cancellable,
657 &error);
658
659 if (connection != NULL)
660 {
661 g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
662
663 /* Most of this list is taken from system-config-printer */
664 candidates = g_list_append (candidates, g_strdup ("PASSTHRU"));
665 candidates = g_list_append (candidates, g_strdup ("AUTO"));
666 candidates = g_list_append (candidates, g_strdup ("BINPS"));
667 candidates = g_list_append (candidates, g_strdup ("RAW"));
668 candidates = g_list_append (candidates, g_strdup ("TEXT"));
669 candidates = g_list_append (candidates, g_strdup ("ps"));
670 candidates = g_list_append (candidates, g_strdup ("lp"));
671 candidates = g_list_append (candidates, g_strdup ("PORT1"));
672
673 for (i = 0; i < 8; i++)
674 {
675 candidates = g_list_append (candidates, g_strdup_printf ("LPT%d", i));
676 candidates = g_list_append (candidates, g_strdup_printf ("LPT%d_PASSTHRU", i));
677 candidates = g_list_append (candidates, g_strdup_printf ("COM%d", i));
678 candidates = g_list_append (candidates, g_strdup_printf ("COM%d_PASSTHRU", i));
679 }
680
681 for (i = 0; i < 50; i++)
682 candidates = g_list_append (candidates, g_strdup_printf ("pr%d", i));
683
684 for (iter = candidates; iter != NULL; iter = iter->next)
685 {
686 candidate = (gchar *) iter->data;
687
688 if (test_lpd_queue (client,
689 address,
690 port,
691 cancellable,
692 candidate))
693 {
694 found_queue = g_strdup (candidate);
695 break;
696 }
697 }
698
699 if (found_queue != NULL)
700 {
701 g_autofree gchar *device_uri = NULL;
702 PpPrintDevice *device;
703
704 device_uri = g_strdup_printf ("lpd://%s:%d/%s",
705 priv->hostname,
706 port,
707 found_queue);
708
709 device = g_object_new (PP_TYPE_PRINT_DEVICE,
710 "is-network-device", TRUE,
711 "device-uri", device_uri,
712 /* Translators: The found device is a Line Printer Daemon printer */
713 "device-name", _("LPD Printer"),
714 "host-name", priv->hostname,
715 "host-port", port,
716 "acquisition-method", ACQUISITION_METHOD_LPD,
717 NULL);
718 g_ptr_array_add (devices, device);
719 }
720
721 g_list_free_full (candidates, g_free);
722 }
723
724 g_task_return_pointer (task, g_ptr_array_ref (devices), (GDestroyNotify) g_ptr_array_unref);
725 }
726
727 void
728 pp_host_get_lpd_devices_async (PpHost *self,
729 GCancellable *cancellable,
730 GAsyncReadyCallback callback,
731 gpointer user_data)
732 {
733 g_autoptr(GTask) task = NULL;
734
735 task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
736 g_task_run_in_thread (task, _pp_host_get_lpd_devices_thread);
737 }
738
739 GPtrArray *
740 pp_host_get_lpd_devices_finish (PpHost *self,
741 GAsyncResult *res,
742 GError **error)
743 {
744 g_return_val_if_fail (g_task_is_valid (res, self), NULL);
745 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
746 return g_task_propagate_pointer (G_TASK (res), error);
747 }
748