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
24#include "afp_zeroconf.h"
25#include "afp_avahi.h"
26#include "afp_config.h"
27#include "volume.h"
28
29/*****************************************************************
30 * Global variables
31 *****************************************************************/
32struct context *ctx = NULL;
33
34/*****************************************************************
35 * Private functions
36 *****************************************************************/
37
38static void publish_reply(AvahiEntryGroup *g,
39                          AvahiEntryGroupState state,
40                          void *userdata);
41
42/*
43 * This function tries to register the AFP DNS
44 * SRV service type.
45 */
46static void register_stuff(void) {
47    uint port;
48    const AFPConfig *config;
49    const struct vol *volume;
50    DSI *dsi;
51    char name[MAXINSTANCENAMELEN+1];
52    AvahiStringList *strlist = NULL;
53    AvahiStringList *strlist2 = NULL;
54    char tmpname[256];
55
56    assert(ctx->client);
57
58    if (!ctx->group) {
59        if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) {
60            LOG(log_error, logtype_afpd, "Failed to create entry group: %s",
61                avahi_strerror(avahi_client_errno(ctx->client)));
62            goto fail;
63        }
64    }
65
66    if (avahi_entry_group_is_empty(ctx->group)) {
67        /* Register our service */
68
69        /* Build AFP volumes list */
70        int i = 0;
71        strlist = avahi_string_list_add_printf(strlist, "sys=waMa=0,adVF=0x100");
72
73        for (volume = getvolumes(); volume; volume = volume->v_next) {
74
75            if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_u8mname, -1, tmpname, 255) <= 0) {
76                LOG ( log_error, logtype_afpd, "Could not set Zeroconf volume name for TimeMachine");
77                goto fail;
78            }
79
80            if (volume->v_flags & AFPVOL_TM) {
81                if (volume->v_uuid) {
82                    LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine",
83                        volume->v_localname, volume->v_uuid);
84                    strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1,adVU=%s",
85                                                           i++, tmpname, volume->v_uuid);
86                } else {
87                    LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.",
88                        volume->v_localname);
89                    strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1",
90                                                           i++, tmpname);
91                }
92            }
93        }
94
95        /* AFP server */
96        for (config = ctx->configs; config; config = config->next) {
97
98            dsi = (DSI *)config->obj.handle;
99            port = getip_port((struct sockaddr *)&dsi->server);
100
101            if (convert_string(config->obj.options.unixcharset,
102                               CH_UTF8,
103                               config->obj.options.server ?
104                               config->obj.options.server :
105                               config->obj.options.hostname,
106                               -1,
107                               name,
108                               MAXINSTANCENAMELEN) <= 0) {
109                LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name");
110                goto fail;
111            }
112            if ((dsi->bonjourname = strdup(name)) == NULL) {
113                LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name");
114                goto fail;
115
116            }
117            LOG(log_info, logtype_afpd, "Registering server '%s' with Bonjour",
118                dsi->bonjourname);
119
120            if (avahi_entry_group_add_service(ctx->group,
121                                              AVAHI_IF_UNSPEC,
122                                              AVAHI_PROTO_UNSPEC,
123                                              0,
124                                              dsi->bonjourname,
125                                              AFP_DNS_SERVICE_TYPE,
126                                              NULL,
127                                              NULL,
128                                              port,
129                                              NULL) < 0) {
130                LOG(log_error, logtype_afpd, "Failed to add service: %s",
131                    avahi_strerror(avahi_client_errno(ctx->client)));
132                goto fail;
133            }
134
135            if (i && avahi_entry_group_add_service_strlst(ctx->group,
136                                                          AVAHI_IF_UNSPEC,
137                                                          AVAHI_PROTO_UNSPEC,
138                                                          0,
139                                                          dsi->bonjourname,
140                                                          ADISK_SERVICE_TYPE,
141                                                          NULL,
142                                                          NULL,
143                                                          9, /* discard */
144                                                          strlist) < 0) {
145                LOG(log_error, logtype_afpd, "Failed to add service: %s",
146                    avahi_strerror(avahi_client_errno(ctx->client)));
147                goto fail;
148            }	/* if */
149
150            if (config->obj.options.mimicmodel) {
151                strlist2 = avahi_string_list_add_printf(strlist2, "model=%s", config->obj.options.mimicmodel);
152                if (avahi_entry_group_add_service_strlst(ctx->group,
153                                                         AVAHI_IF_UNSPEC,
154                                                         AVAHI_PROTO_UNSPEC,
155                                                         0,
156                                                         dsi->bonjourname,
157                                                         DEV_INFO_SERVICE_TYPE,
158                                                         NULL,
159                                                         NULL,
160                                                         0,
161                                                         strlist2) < 0) {
162                    LOG(log_error, logtype_afpd, "Failed to add service: %s",
163                        avahi_strerror(avahi_client_errno(ctx->client)));
164                    goto fail;
165                }
166            } /* if (config->obj.options.mimicmodel) */
167
168        }	/* for config*/
169
170        if (avahi_entry_group_commit(ctx->group) < 0) {
171            LOG(log_error, logtype_afpd, "Failed to commit entry group: %s",
172                avahi_strerror(avahi_client_errno(ctx->client)));
173            goto fail;
174        }
175
176    }	/* if avahi_entry_group_is_empty*/
177
178    return;
179
180fail:
181    time(NULL);
182//    avahi_threaded_poll_quit(ctx->threaded_poll);
183}
184
185/* Called when publishing of service data completes */
186static void publish_reply(AvahiEntryGroup *g,
187                          AvahiEntryGroupState state,
188                          AVAHI_GCC_UNUSED void *userdata)
189{
190    assert(ctx->group == NULL || g == ctx->group);
191
192    switch (state) {
193
194    case AVAHI_ENTRY_GROUP_ESTABLISHED :
195        /* The entry group has been established successfully */
196        LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED");
197        break;
198
199    case AVAHI_ENTRY_GROUP_COLLISION:
200        /* With multiple names there's no way to know which one collided */
201        LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION",
202            avahi_strerror(avahi_client_errno(ctx->client)));
203        avahi_threaded_poll_quit(ctx->threaded_poll);
204        break;
205
206    case AVAHI_ENTRY_GROUP_FAILURE:
207        LOG(log_error, logtype_afpd, "Failed to register service: %s",
208            avahi_strerror(avahi_client_errno(ctx->client)));
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_threaded_poll_quit(ctx->threaded_poll);
257            }
258
259        } else {
260            LOG(log_error, logtype_afpd, "Client failure: %s",
261                avahi_strerror(avahi_client_errno(client)));
262            avahi_threaded_poll_quit(ctx->threaded_poll);
263        }
264        break;
265    }
266
267    case AVAHI_CLIENT_S_REGISTERING:
268        break;
269    case AVAHI_CLIENT_CONNECTING:
270        break;
271    }
272}
273
274/************************************************************************
275 * Public funcions
276 ************************************************************************/
277
278/*
279 * Tries to setup the Zeroconf thread and any
280 * neccessary config setting.
281 */
282void av_zeroconf_register(const AFPConfig *configs) {
283    int error;
284
285    /* initialize the struct that holds our config settings. */
286    if (ctx) {
287        LOG(log_debug, logtype_afpd, "Resetting zeroconf records");
288        avahi_entry_group_reset(ctx->group);
289    } else {
290        ctx = calloc(1, sizeof(struct context));
291        ctx->configs = configs;
292        assert(ctx);
293    }
294
295/* first of all we need to initialize our threading env */
296    if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
297        goto fail;
298    }
299
300/* now we need to acquire a client */
301    if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
302                                         AVAHI_CLIENT_NO_FAIL,
303                                         client_callback,
304                                         NULL,
305                                         &error))) {
306        LOG(log_error, logtype_afpd, "Failed to create client object: %s",
307            avahi_strerror(error));
308        goto fail;
309    }
310
311    if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
312        LOG(log_error, logtype_afpd, "Failed to create thread: %s",
313            avahi_strerror(avahi_client_errno(ctx->client)));
314        goto fail;
315    } else {
316        LOG(log_info, logtype_afpd, "Successfully started avahi loop.");
317    }
318
319    ctx->thread_running = 1;
320    return;
321
322fail:
323    av_zeroconf_unregister();
324
325    return;
326}
327
328/*
329 * Tries to shutdown this loop impl.
330 * Call this function from inside this thread.
331 */
332int av_zeroconf_unregister() {
333    LOG(log_error, logtype_afpd, "av_zeroconf_unregister");
334
335    if (ctx) {
336        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_stop");
337        if (ctx->threaded_poll)
338            avahi_threaded_poll_stop(ctx->threaded_poll);
339        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_client_free");
340        if (ctx->client)
341            avahi_client_free(ctx->client);
342        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_free");
343        if (ctx->threaded_poll)
344            avahi_threaded_poll_free(ctx->threaded_poll);
345        free(ctx);
346        ctx = NULL;
347    }
348    return 0;
349}
350
351#endif /* USE_AVAHI */
352
353