1/*
2 * Author:  Daniel S. Haischt <me@daniel.stefan.haischt.name>
3 * Purpose: Avahi based Zeroconf support
4 * Docs:    http://avahi.org/download/doxygen/
5 *
6 */
7
8#ifdef HAVE_CONFIG_H
9#include <config.h>
10#endif
11
12#ifdef HAVE_AVAHI
13
14#include <unistd.h>
15
16#include <avahi-common/strlst.h>
17
18#include <atalk/logger.h>
19#include <atalk/util.h>
20#include <atalk/dsi.h>
21#include <atalk/unicode.h>
22
23#include "afp_avahi.h"
24#include "afp_config.h"
25#include "volume.h"
26
27/*****************************************************************
28 * Global variables
29 *****************************************************************/
30struct context *ctx = NULL;
31
32/*****************************************************************
33 * Private functions
34 *****************************************************************/
35
36static void publish_reply(AvahiEntryGroup *g,
37                          AvahiEntryGroupState state,
38                          void *userdata);
39
40/*
41 * This function tries to register the AFP DNS
42 * SRV service type.
43 */
44static void register_stuff(void) {
45    uint port;
46    const AFPConfig *config;
47    const struct vol *volume;
48    DSI *dsi;
49    char name[MAXINSTANCENAMELEN+1];
50    AvahiStringList *strlist = NULL;
51    AvahiStringList *strlist2 = NULL;
52    char tmpname[256];
53
54    assert(ctx->client);
55
56    if (!ctx->group) {
57        if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) {
58            LOG(log_error, logtype_afpd, "Failed to create entry group: %s",
59                avahi_strerror(avahi_client_errno(ctx->client)));
60            goto fail;
61        }
62    }
63
64    if (avahi_entry_group_is_empty(ctx->group)) {
65        /* Register our service */
66
67        /* Build AFP volumes list */
68        int i = 0;
69        strlist = avahi_string_list_add_printf(strlist, "sys=waMa=0,adVF=0x100");
70
71        for (volume = getvolumes(); volume; volume = volume->v_next) {
72
73            if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_name, -1, tmpname, 255) <= 0) {
74                LOG ( log_error, logtype_afpd, "Could not set Zeroconf volume name for TimeMachine");
75                goto fail;
76            }
77
78            if (volume->v_flags & AFPVOL_TM) {
79                if (volume->v_uuid) {
80                    LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine",
81                        volume->v_localname, volume->v_uuid);
82                    strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1,adVU=%s",
83                                                           i++, tmpname, volume->v_uuid);
84                } else {
85                    LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.",
86                        volume->v_localname);
87                    strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1",
88                                                           i++, tmpname);
89                }
90            }
91        }
92
93        /* AFP server */
94        for (config = ctx->configs; config; config = config->next) {
95
96            dsi = (DSI *)config->obj.handle;
97            port = getip_port((struct sockaddr *)&dsi->server);
98
99            if (convert_string(config->obj.options.unixcharset,
100                               CH_UTF8,
101                               config->obj.options.server ?
102                               config->obj.options.server :
103                               config->obj.options.hostname,
104                               -1,
105                               name,
106                               MAXINSTANCENAMELEN) <= 0) {
107                LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name");
108                goto fail;
109            }
110            if ((dsi->bonjourname = strdup(name)) == NULL) {
111                LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name");
112                goto fail;
113
114            }
115            LOG(log_info, logtype_afpd, "Registering server '%s' with with Bonjour",
116                dsi->bonjourname);
117
118            if (avahi_entry_group_add_service(ctx->group,
119                                              AVAHI_IF_UNSPEC,
120                                              AVAHI_PROTO_UNSPEC,
121                                              0,
122                                              dsi->bonjourname,
123                                              AFP_DNS_SERVICE_TYPE,
124                                              NULL,
125                                              NULL,
126                                              port,
127                                              NULL) < 0) {
128                LOG(log_error, logtype_afpd, "Failed to add service: %s",
129                    avahi_strerror(avahi_client_errno(ctx->client)));
130                goto fail;
131            }
132
133            if (i && avahi_entry_group_add_service_strlst(ctx->group,
134                                                          AVAHI_IF_UNSPEC,
135                                                          AVAHI_PROTO_UNSPEC,
136                                                          0,
137                                                          dsi->bonjourname,
138                                                          ADISK_SERVICE_TYPE,
139                                                          NULL,
140                                                          NULL,
141                                                          9, /* discard */
142                                                          strlist) < 0) {
143                LOG(log_error, logtype_afpd, "Failed to add service: %s",
144                    avahi_strerror(avahi_client_errno(ctx->client)));
145                goto fail;
146            }	/* if */
147
148            if (config->obj.options.mimicmodel) {
149                strlist2 = avahi_string_list_add_printf(strlist2, "model=%s", config->obj.options.mimicmodel);
150                if (avahi_entry_group_add_service_strlst(ctx->group,
151                                                         AVAHI_IF_UNSPEC,
152                                                         AVAHI_PROTO_UNSPEC,
153                                                         0,
154                                                         dsi->bonjourname,
155                                                         DEV_INFO_SERVICE_TYPE,
156                                                         NULL,
157                                                         NULL,
158                                                         0,
159                                                         strlist2) < 0) {
160                    LOG(log_error, logtype_afpd, "Failed to add service: %s",
161                        avahi_strerror(avahi_client_errno(ctx->client)));
162                    goto fail;
163                }
164            } /* if (config->obj.options.mimicmodel) */
165
166        }	/* for config*/
167
168        if (avahi_entry_group_commit(ctx->group) < 0) {
169            LOG(log_error, logtype_afpd, "Failed to commit entry group: %s",
170                avahi_strerror(avahi_client_errno(ctx->client)));
171            goto fail;
172        }
173
174    }	/* if avahi_entry_group_is_empty*/
175
176    return;
177
178fail:
179    avahi_client_free (ctx->client);
180    avahi_threaded_poll_quit(ctx->threaded_poll);
181}
182
183/* Called when publishing of service data completes */
184static void publish_reply(AvahiEntryGroup *g,
185                          AvahiEntryGroupState state,
186                          AVAHI_GCC_UNUSED void *userdata)
187{
188    assert(ctx->group == NULL || g == ctx->group);
189
190    switch (state) {
191
192    case AVAHI_ENTRY_GROUP_ESTABLISHED :
193        /* The entry group has been established successfully */
194        LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED");
195        break;
196
197    case AVAHI_ENTRY_GROUP_COLLISION:
198        /* With multiple names there's no way to know which one collided */
199        LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION",
200            avahi_strerror(avahi_client_errno(ctx->client)));
201        avahi_client_free(avahi_entry_group_get_client(g));
202        avahi_threaded_poll_quit(ctx->threaded_poll);
203        break;
204
205    case AVAHI_ENTRY_GROUP_FAILURE:
206        LOG(log_error, logtype_afpd, "Failed to register service: %s",
207            avahi_strerror(avahi_client_errno(ctx->client)));
208        avahi_client_free(avahi_entry_group_get_client(g));
209        avahi_threaded_poll_quit(ctx->threaded_poll);
210        break;
211
212    case AVAHI_ENTRY_GROUP_UNCOMMITED:
213        break;
214    case AVAHI_ENTRY_GROUP_REGISTERING:
215        break;
216    }
217}
218
219static void client_callback(AvahiClient *client,
220                            AvahiClientState state,
221                            void *userdata)
222{
223    ctx->client = client;
224
225    switch (state) {
226    case AVAHI_CLIENT_S_RUNNING:
227        /* The server has startup successfully and registered its host
228         * name on the network, so it's time to create our services */
229        if (!ctx->group)
230            register_stuff();
231        break;
232
233    case AVAHI_CLIENT_S_COLLISION:
234        if (ctx->group)
235            avahi_entry_group_reset(ctx->group);
236        break;
237
238    case AVAHI_CLIENT_FAILURE: {
239        if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
240            int error;
241
242            avahi_client_free(ctx->client);
243            ctx->client = NULL;
244            ctx->group = NULL;
245
246            /* Reconnect to the server */
247            if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
248                                                 AVAHI_CLIENT_NO_FAIL,
249                                                 client_callback,
250                                                 ctx,
251                                                 &error))) {
252
253                LOG(log_error, logtype_afpd, "Failed to contact server: %s",
254                    avahi_strerror(error));
255
256                avahi_client_free (ctx->client);
257                avahi_threaded_poll_quit(ctx->threaded_poll);
258            }
259
260        } else {
261            LOG(log_error, logtype_afpd, "Client failure: %s",
262                avahi_strerror(avahi_client_errno(client)));
263            avahi_client_free (ctx->client);
264            avahi_threaded_poll_quit(ctx->threaded_poll);
265        }
266        break;
267    }
268
269    case AVAHI_CLIENT_S_REGISTERING:
270        break;
271    case AVAHI_CLIENT_CONNECTING:
272        break;
273    }
274}
275
276/************************************************************************
277 * Public funcions
278 ************************************************************************/
279
280/*
281 * Tries to setup the Zeroconf thread and any
282 * neccessary config setting.
283 */
284void av_zeroconf_setup(const AFPConfig *configs) {
285    int error;
286
287    /* initialize the struct that holds our config settings. */
288    if (ctx) {
289        LOG(log_debug, logtype_afpd, "Resetting zeroconf records");
290        avahi_entry_group_reset(ctx->group);
291    } else {
292        ctx = calloc(1, sizeof(struct context));
293        ctx->configs = configs;
294        assert(ctx);
295    }
296
297/* first of all we need to initialize our threading env */
298    if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
299        goto fail;
300    }
301
302/* now we need to acquire a client */
303    if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
304                                         AVAHI_CLIENT_NO_FAIL,
305                                         client_callback,
306                                         NULL,
307                                         &error))) {
308        LOG(log_error, logtype_afpd, "Failed to create client object: %s",
309            avahi_strerror(avahi_client_errno(ctx->client)));
310        goto fail;
311    }
312
313    return;
314
315fail:
316    if (ctx)
317        av_zeroconf_unregister();
318
319    return;
320}
321
322/*
323 * This function finally runs the loop impl.
324 */
325int av_zeroconf_run(void) {
326    /* Finally, start the event loop thread */
327    if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
328        LOG(log_error, logtype_afpd, "Failed to create thread: %s",
329            avahi_strerror(avahi_client_errno(ctx->client)));
330        goto fail;
331    } else {
332        LOG(log_info, logtype_afpd, "Successfully started avahi loop.");
333    }
334
335    ctx->thread_running = 1;
336    return 0;
337
338fail:
339    if (ctx)
340        av_zeroconf_unregister();
341
342    return -1;
343}
344
345/*
346 * Tries to shutdown this loop impl.
347 * Call this function from outside this thread.
348 */
349void av_zeroconf_shutdown() {
350    /* Call this when the app shuts down */
351    avahi_threaded_poll_stop(ctx->threaded_poll);
352    avahi_client_free(ctx->client);
353    avahi_threaded_poll_free(ctx->threaded_poll);
354    free(ctx);
355    ctx = NULL;
356}
357
358/*
359 * Tries to shutdown this loop impl.
360 * Call this function from inside this thread.
361 */
362int av_zeroconf_unregister() {
363    if (ctx->thread_running) {
364        /* First, block the event loop */
365        avahi_threaded_poll_lock(ctx->threaded_poll);
366
367        /* Than, do your stuff */
368        avahi_threaded_poll_quit(ctx->threaded_poll);
369
370        /* Finally, unblock the event loop */
371        avahi_threaded_poll_unlock(ctx->threaded_poll);
372        ctx->thread_running = 0;
373    }
374
375    if (ctx->client)
376        avahi_client_free(ctx->client);
377
378    if (ctx->threaded_poll)
379        avahi_threaded_poll_free(ctx->threaded_poll);
380
381    free(ctx);
382    ctx = NULL;
383
384    return 0;
385}
386
387#endif /* USE_AVAHI */
388
389