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
41static AvahiSimplePoll *simple_poll = NULL;
42static 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 */
55static void
56resolve_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 */
127static void
128browse_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 */
168static void
169client_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 */
185ESCL_Device *
186escl_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);
225fail:
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