1 /* sane - Scanner Access Now Easy.
2
3 Copyright (C) 2019 Touboul Nathane
4 Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
5
6 This file is part of the SANE package.
7
8 SANE is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 3 of the License, or (at your
11 option) any later version.
12
13 SANE is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with sane; see the file COPYING.
20 If not, see <https://www.gnu.org/licenses/>.
21
22 This file implements a SANE backend for eSCL scanners. */
23
24 #define DEBUG_DECLARE_ONLY
25 #include "../include/sane/config.h"
26
27 #include "escl.h"
28
29 #include <assert.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <arpa/inet.h>
34
35 #include <avahi-client/lookup.h>
36 #include <avahi-common/error.h>
37 #include <avahi-common/simple-watch.h>
38
39 #include "../include/sane/sanei.h"
40
41 static AvahiSimplePoll *simple_poll = NULL;
42 static int count_finish = 0;
43
44 /**
45 * \fn static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED
46 * AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol,
47 * AvahiResolverEvent event, const char *name,
48 * const char *type, const char *domain, const char *host_name,
49 * const AvahiAddress *address, uint16_t port,
50 * AvahiStringList *txt, AvahiLookupResultFlags flags,
51 * void *userdata)
52 * \brief Callback function that will check if the selected scanner follows the escl
53 * protocol or not.
54 */
55 static void
resolve_callback(AvahiServiceResolver * r,AVAHI_GCC_UNUSED AvahiIfIndex interface,AvahiProtocol protocol,AvahiResolverEvent event,const char * name,const char __sane_unused__ * type,const char __sane_unused__ * domain,const char __sane_unused__ * host_name,const AvahiAddress * address,uint16_t port,AvahiStringList * txt,AvahiLookupResultFlags __sane_unused__ flags,void __sane_unused__ * userdata)56 resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface,
57 AvahiProtocol protocol,
58 AvahiResolverEvent event,
59 const char *name,
60 const char __sane_unused__ *type,
61 const char __sane_unused__ *domain,
62 const char __sane_unused__ *host_name,
63 const AvahiAddress *address,
64 uint16_t port,
65 AvahiStringList *txt,
66 AvahiLookupResultFlags __sane_unused__ flags,
67 void __sane_unused__ *userdata)
68 {
69 char a[(AVAHI_ADDRESS_STR_MAX + 10)] = { 0 };
70 char *t;
71 const char *is;
72 const char *uuid;
73 AvahiStringList *s;
74 assert(r);
75 switch (event) {
76 case AVAHI_RESOLVER_FAILURE:
77 break;
78 case AVAHI_RESOLVER_FOUND:
79 {
80 char *psz_addr = ((void*)0);
81 char b[128] = { 0 };
82 avahi_address_snprint(b, (sizeof(b)/sizeof(b[0]))-1, address);
83 #ifdef ENABLE_IPV6
84 if (protocol == AVAHI_PROTO_INET6 && strchr(b, ':'))
85 {
86 if ( asprintf( &psz_addr, "[%s]", b ) == -1 )
87 break;
88 }
89 else
90 #endif
91 {
92 if ( asprintf( &psz_addr, "%s", b ) == -1 )
93 break;
94 }
95 t = avahi_string_list_to_string(txt);
96 if (strstr(t, "\"rs=eSCL\"") || strstr(t, "\"rs=/eSCL\"")) {
97 s = avahi_string_list_find(txt, "is");
98 if (s && s->size > 3)
99 is = (const char*)s->text + 3;
100 else
101 is = (const char*)NULL;
102 s = avahi_string_list_find(txt, "uuid");
103 if (s && s->size > 5)
104 uuid = (const char*)s->text + 5;
105 else
106 uuid = (const char*)NULL;
107 DBG (10, "resolve_callback [%s]\n", a);
108 if (strstr(psz_addr, "127.0.0.1") != NULL) {
109 escl_device_add(port, name, "localhost", is, uuid, (char*)type);
110 DBG (10,"resolve_callback fix redirect [localhost]\n");
111 }
112 else
113 escl_device_add(port, name, psz_addr, is, uuid, (char*)type);
114 }
115 }
116 }
117 }
118
119 /**
120 * \fn static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
121 * AvahiProtocol protocol, AvahiBrowserEvent event, const char *name,
122 * const char *type, const char *domain,
123 * AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata)
124 * \brief Callback function that will browse tanks to 'avahi' the scanners
125 * connected in network.
126 */
127 static void
browse_callback(AvahiServiceBrowser * b,AvahiIfIndex interface,AvahiProtocol protocol,AvahiBrowserEvent event,const char * name,const char * type,const char * domain,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)128 browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
129 AvahiProtocol protocol, AvahiBrowserEvent event,
130 const char *name, const char *type,
131 const char *domain,
132 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
133 void* userdata)
134 {
135 AvahiClient *c = userdata;
136 assert(b);
137 switch (event) {
138 case AVAHI_BROWSER_FAILURE:
139 avahi_simple_poll_quit(simple_poll);
140 return;
141 case AVAHI_BROWSER_NEW:
142 if (!(avahi_service_resolver_new(c, interface, protocol, name,
143 type, domain,
144 AVAHI_PROTO_UNSPEC, 0,
145 resolve_callback, c)))
146 break;
147 case AVAHI_BROWSER_REMOVE:
148 break;
149 case AVAHI_BROWSER_ALL_FOR_NOW:
150 case AVAHI_BROWSER_CACHE_EXHAUSTED:
151 if (event != AVAHI_BROWSER_CACHE_EXHAUSTED)
152 {
153 count_finish++;
154 if (count_finish == 2)
155 avahi_simple_poll_quit(simple_poll);
156 }
157 break;
158 }
159 }
160
161 /**
162 * \fn static void client_callback(AvahiClient *c, AvahiClientState state,
163 * AVAHI_GCC_UNUSED void *userdata)
164 * \brief Callback Function that quit if it doesn't find a connected scanner,
165 * possible thanks the "Hello Protocol".
166 * --> Waiting for a answer by the scanner to continue the avahi process.
167 */
168 static void
client_callback(AvahiClient * c,AvahiClientState state,AVAHI_GCC_UNUSED void * userdata)169 client_callback(AvahiClient *c, AvahiClientState state,
170 AVAHI_GCC_UNUSED void *userdata)
171 {
172 assert(c);
173 if (state == AVAHI_CLIENT_FAILURE)
174 avahi_simple_poll_quit(simple_poll);
175 }
176
177 /**
178 * \fn ESCL_Device *escl_devices(SANE_Status *status)
179 * \brief Function that calls all the avahi functions and then, recovers the
180 * connected eSCL devices.
181 * This function is called in the 'sane_get_devices' function.
182 *
183 * \return NULL (the eSCL devices found)
184 */
185 ESCL_Device *
escl_devices(SANE_Status * status)186 escl_devices(SANE_Status *status)
187 {
188 AvahiClient *client = NULL;
189 AvahiServiceBrowser *sb = NULL;
190 int error;
191
192 count_finish = 0;
193
194 *status = SANE_STATUS_GOOD;
195 if (!(simple_poll = avahi_simple_poll_new())) {
196 DBG( 1, "Failed to create simple poll object.\n");
197 *status = SANE_STATUS_INVAL;
198 goto fail;
199 }
200 client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0,
201 client_callback, NULL, &error);
202 if (!client) {
203 DBG( 1, "Failed to create client: %s\n", avahi_strerror(error));
204 *status = SANE_STATUS_INVAL;
205 goto fail;
206 }
207 if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
208 AVAHI_PROTO_UNSPEC, "_uscan._tcp",
209 NULL, 0, browse_callback, client))) {
210 DBG( 1, "Failed to create service browser: %s\n",
211 avahi_strerror(avahi_client_errno(client)));
212 *status = SANE_STATUS_INVAL;
213 goto fail;
214 }
215 if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
216 AVAHI_PROTO_UNSPEC,
217 "_uscans._tcp", NULL, 0,
218 browse_callback, client))) {
219 DBG( 1, "Failed to create service browser: %s\n",
220 avahi_strerror(avahi_client_errno(client)));
221 *status = SANE_STATUS_INVAL;
222 goto fail;
223 }
224 avahi_simple_poll_loop(simple_poll);
225 fail:
226 if (sb)
227 avahi_service_browser_free(sb);
228 if (client)
229 avahi_client_free(client);
230 if (simple_poll)
231 avahi_simple_poll_free(simple_poll);
232 return (NULL);
233 }
234