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#include <time.h>
16
17#include <avahi-common/strlst.h>
18
19#include <atalk/logger.h>
20#include <atalk/util.h>
21#include <atalk/dsi.h>
22#include <atalk/unicode.h>
23#include <atalk/netatalk_conf.h>
24
25#include "afp_zeroconf.h"
26#include "afp_avahi.h"
27
28/*****************************************************************
29 * Global variables
30 *****************************************************************/
31struct context *ctx = NULL;
32
33/*****************************************************************
34 * Private functions
35 *****************************************************************/
36
37static void publish_reply(AvahiEntryGroup *g,
38                          AvahiEntryGroupState state,
39                          void *userdata);
40
41/*
42 * This function tries to register the AFP DNS
43 * SRV service type.
44 */
45static void register_stuff(void) {
46    uint port;
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_u8mname, -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 (dsi = ctx->obj->dsi; dsi; dsi = dsi->next) {
95            port = getip_port((struct sockaddr *)&dsi->server);
96
97            LOG(log_info, logtype_afpd, "hostname: %s", ctx->obj->options.hostname);
98
99            if (convert_string(ctx->obj->options.unixcharset,
100                               CH_UTF8,
101                               ctx->obj->options.hostname,
102                               -1,
103                               name,
104                               MAXINSTANCENAMELEN) <= 0) {
105                LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name: %s", ctx->obj->options.hostname);
106                goto fail;
107            }
108            if ((dsi->bonjourname = strdup(name)) == NULL) {
109                LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name");
110                goto fail;
111
112            }
113            LOG(log_info, logtype_afpd, "Registering server '%s' with Bonjour",
114                dsi->bonjourname);
115
116            if (avahi_entry_group_add_service(ctx->group,
117                                              AVAHI_IF_UNSPEC,
118                                              AVAHI_PROTO_UNSPEC,
119                                              0,
120                                              dsi->bonjourname,
121                                              AFP_DNS_SERVICE_TYPE,
122                                              NULL,
123                                              NULL,
124                                              port,
125                                              NULL) < 0) {
126                LOG(log_error, logtype_afpd, "Failed to add service: %s",
127                    avahi_strerror(avahi_client_errno(ctx->client)));
128                goto fail;
129            }
130
131            if (i && avahi_entry_group_add_service_strlst(ctx->group,
132                                                          AVAHI_IF_UNSPEC,
133                                                          AVAHI_PROTO_UNSPEC,
134                                                          0,
135                                                          dsi->bonjourname,
136                                                          ADISK_SERVICE_TYPE,
137                                                          NULL,
138                                                          NULL,
139                                                          9, /* discard */
140                                                          strlist) < 0) {
141                LOG(log_error, logtype_afpd, "Failed to add service: %s",
142                    avahi_strerror(avahi_client_errno(ctx->client)));
143                goto fail;
144            }	/* if */
145
146            if (ctx->obj->options.mimicmodel) {
147                strlist2 = avahi_string_list_add_printf(strlist2, "model=%s", ctx->obj->options.mimicmodel);
148                if (avahi_entry_group_add_service_strlst(ctx->group,
149                                                         AVAHI_IF_UNSPEC,
150                                                         AVAHI_PROTO_UNSPEC,
151                                                         0,
152                                                         dsi->bonjourname,
153                                                         DEV_INFO_SERVICE_TYPE,
154                                                         NULL,
155                                                         NULL,
156                                                         0,
157                                                         strlist2) < 0) {
158                    LOG(log_error, logtype_afpd, "Failed to add service: %s",
159                        avahi_strerror(avahi_client_errno(ctx->client)));
160                    goto fail;
161                }
162            } /* if (config->obj.options.mimicmodel) */
163
164        }	/* for config*/
165
166        if (avahi_entry_group_commit(ctx->group) < 0) {
167            LOG(log_error, logtype_afpd, "Failed to commit entry group: %s",
168                avahi_strerror(avahi_client_errno(ctx->client)));
169            goto fail;
170        }
171
172    }	/* if avahi_entry_group_is_empty*/
173
174    return;
175
176fail:
177    time(NULL);
178//    avahi_threaded_poll_quit(ctx->threaded_poll);
179}
180
181/* Called when publishing of service data completes */
182static void publish_reply(AvahiEntryGroup *g,
183                          AvahiEntryGroupState state,
184                          AVAHI_GCC_UNUSED void *userdata)
185{
186    assert(ctx->group == NULL || g == ctx->group);
187
188    switch (state) {
189
190    case AVAHI_ENTRY_GROUP_ESTABLISHED :
191        /* The entry group has been established successfully */
192        LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED");
193        break;
194
195    case AVAHI_ENTRY_GROUP_COLLISION:
196        /* With multiple names there's no way to know which one collided */
197        LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION",
198            avahi_strerror(avahi_client_errno(ctx->client)));
199        avahi_threaded_poll_quit(ctx->threaded_poll);
200        break;
201
202    case AVAHI_ENTRY_GROUP_FAILURE:
203        LOG(log_error, logtype_afpd, "Failed to register service: %s",
204            avahi_strerror(avahi_client_errno(ctx->client)));
205        avahi_threaded_poll_quit(ctx->threaded_poll);
206        break;
207
208    case AVAHI_ENTRY_GROUP_UNCOMMITED:
209        break;
210    case AVAHI_ENTRY_GROUP_REGISTERING:
211        break;
212    }
213}
214
215static void client_callback(AvahiClient *client,
216                            AvahiClientState state,
217                            void *userdata)
218{
219    ctx->client = client;
220
221    switch (state) {
222    case AVAHI_CLIENT_S_RUNNING:
223        /* The server has startup successfully and registered its host
224         * name on the network, so it's time to create our services */
225        if (!ctx->group)
226            register_stuff();
227        break;
228
229    case AVAHI_CLIENT_S_COLLISION:
230        if (ctx->group)
231            avahi_entry_group_reset(ctx->group);
232        break;
233
234    case AVAHI_CLIENT_FAILURE: {
235        if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
236            int error;
237
238            avahi_client_free(ctx->client);
239            ctx->client = NULL;
240            ctx->group = NULL;
241
242            /* Reconnect to the server */
243            if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
244                                                 AVAHI_CLIENT_NO_FAIL,
245                                                 client_callback,
246                                                 ctx,
247                                                 &error))) {
248
249                LOG(log_error, logtype_afpd, "Failed to contact server: %s",
250                    avahi_strerror(error));
251
252                avahi_threaded_poll_quit(ctx->threaded_poll);
253            }
254
255        } else {
256            LOG(log_error, logtype_afpd, "Client failure: %s",
257                avahi_strerror(avahi_client_errno(client)));
258            avahi_threaded_poll_quit(ctx->threaded_poll);
259        }
260        break;
261    }
262
263    case AVAHI_CLIENT_S_REGISTERING:
264        break;
265    case AVAHI_CLIENT_CONNECTING:
266        break;
267    }
268}
269
270/************************************************************************
271 * Public funcions
272 ************************************************************************/
273
274/*
275 * Tries to setup the Zeroconf thread and any
276 * neccessary config setting.
277 */
278void av_zeroconf_register(const AFPObj *obj) {
279    int error;
280
281    /* initialize the struct that holds our config settings. */
282    if (ctx) {
283        LOG(log_debug, logtype_afpd, "Resetting zeroconf records");
284        avahi_entry_group_reset(ctx->group);
285    } else {
286        ctx = calloc(1, sizeof(struct context));
287        ctx->obj = obj;
288        assert(ctx);
289    }
290
291/* first of all we need to initialize our threading env */
292    if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
293        goto fail;
294    }
295
296/* now we need to acquire a client */
297    if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
298                                         AVAHI_CLIENT_NO_FAIL,
299                                         client_callback,
300                                         NULL,
301                                         &error))) {
302        LOG(log_error, logtype_afpd, "Failed to create client object: %s",
303            avahi_strerror(error));
304        goto fail;
305    }
306
307    if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
308        LOG(log_error, logtype_afpd, "Failed to create thread: %s",
309            avahi_strerror(avahi_client_errno(ctx->client)));
310        goto fail;
311    } else {
312        LOG(log_info, logtype_afpd, "Successfully started avahi loop.");
313    }
314
315    ctx->thread_running = 1;
316    return;
317
318fail:
319    av_zeroconf_unregister();
320
321    return;
322}
323
324/*
325 * Tries to shutdown this loop impl.
326 * Call this function from inside this thread.
327 */
328int av_zeroconf_unregister() {
329    LOG(log_error, logtype_afpd, "av_zeroconf_unregister");
330
331    if (ctx) {
332        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_stop");
333        if (ctx->threaded_poll)
334            avahi_threaded_poll_stop(ctx->threaded_poll);
335        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_client_free");
336        if (ctx->client)
337            avahi_client_free(ctx->client);
338        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_free");
339        if (ctx->threaded_poll)
340            avahi_threaded_poll_free(ctx->threaded_poll);
341        free(ctx);
342        ctx = NULL;
343    }
344    return 0;
345}
346
347#endif /* USE_AVAHI */
348
349