]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/afp_avahi.c
Merge remote-tracking branch 'remotes/origin/branch-netatalk-2-1'
[netatalk.git] / etc / afpd / afp_avahi.c
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  *****************************************************************/
30 struct context *ctx = NULL;
31
32 /*****************************************************************
33  * Private functions
34  *****************************************************************/
35
36 static 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  */
44 static 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
178 fail:
179     avahi_client_free (ctx->client);
180     avahi_threaded_poll_quit(ctx->threaded_poll);
181 }
182
183 /* Called when publishing of service data completes */
184 static 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
219 static 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  */
284 void 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
315 fail:
316     if (ctx)
317         av_zeroconf_unregister();
318
319     return;
320 }
321
322 /*
323  * This function finally runs the loop impl.
324  */
325 int 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
338 fail:
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  */
349 void 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  */
362 int 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