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