]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/afp_avahi.c
Merge branch '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     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, CH_UTF8,
99                                config->obj.options.server ? config->obj.options.server : config->obj.options.hostname, -1,
100                                name, MAXINSTANCENAMELEN) <= 0) {
101                 LOG ( log_error, logtype_afpd, "Could not set Zeroconf instance name");
102                 goto fail;
103             }
104
105             if (avahi_entry_group_add_service(ctx->group,
106                                               AVAHI_IF_UNSPEC,
107                                               AVAHI_PROTO_UNSPEC,
108                                               0,
109                                               name,
110                                               AFP_DNS_SERVICE_TYPE,
111                                               NULL,
112                                               NULL,
113                                               port,
114                                               NULL) < 0) {
115                 LOG(log_error, logtype_afpd, "Failed to add service: %s",
116                     avahi_strerror(avahi_client_errno(ctx->client)));
117                 goto fail;
118             }
119
120             if (i && avahi_entry_group_add_service_strlst(ctx->group,
121                                                           AVAHI_IF_UNSPEC,
122                                                           AVAHI_PROTO_UNSPEC,
123                                                           0,
124                                                           name,
125                                                           ADISK_SERVICE_TYPE,
126                                                           NULL,
127                                                           NULL,
128                                                           9, /* discard */
129                                                           strlist) < 0) {
130                 LOG(log_error, logtype_afpd, "Failed to add service: %s",
131                     avahi_strerror(avahi_client_errno(ctx->client)));
132                 goto fail;
133             }   /* if */
134         }       /* for config*/
135
136         if (avahi_entry_group_commit(ctx->group) < 0) {
137             LOG(log_error, logtype_afpd, "Failed to commit entry group: %s",
138                 avahi_strerror(avahi_client_errno(ctx->client)));
139             goto fail;
140         }
141
142     }   /* if avahi_entry_group_is_empty*/
143
144     return;
145
146 fail:
147     avahi_client_free (ctx->client);
148     avahi_threaded_poll_quit(ctx->threaded_poll);
149 }
150
151 /* Called when publishing of service data completes */
152 static void publish_reply(AvahiEntryGroup *g,
153                           AvahiEntryGroupState state,
154                           AVAHI_GCC_UNUSED void *userdata)
155 {
156     assert(ctx->group == NULL || g == ctx->group);
157
158     switch (state) {
159
160     case AVAHI_ENTRY_GROUP_ESTABLISHED :
161         /* The entry group has been established successfully */
162         LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED");
163         break;
164
165     case AVAHI_ENTRY_GROUP_COLLISION:
166         /* With multiple names there's no way to know which one collided */
167         LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION",
168             avahi_strerror(avahi_client_errno(ctx->client)));
169         avahi_client_free(avahi_entry_group_get_client(g));
170         avahi_threaded_poll_quit(ctx->threaded_poll);
171         break;
172                 
173     case AVAHI_ENTRY_GROUP_FAILURE:
174         LOG(log_error, logtype_afpd, "Failed to register service: %s",
175             avahi_strerror(avahi_client_errno(ctx->client)));
176         avahi_client_free(avahi_entry_group_get_client(g));
177         avahi_threaded_poll_quit(ctx->threaded_poll);
178         break;
179
180     case AVAHI_ENTRY_GROUP_UNCOMMITED:
181         break;
182     case AVAHI_ENTRY_GROUP_REGISTERING:
183         break;
184     }
185 }
186
187 static void client_callback(AvahiClient *client,
188                             AvahiClientState state,
189                             void *userdata)
190 {
191     ctx->client = client;
192
193     switch (state) {
194     case AVAHI_CLIENT_S_RUNNING:
195         /* The server has startup successfully and registered its host
196          * name on the network, so it's time to create our services */
197         if (!ctx->group)
198             register_stuff();
199         break;
200
201     case AVAHI_CLIENT_S_COLLISION:
202         if (ctx->group)
203             avahi_entry_group_reset(ctx->group);
204         break;
205
206     case AVAHI_CLIENT_FAILURE: {
207         if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
208             int error;
209
210             avahi_client_free(ctx->client);
211             ctx->client = NULL;
212             ctx->group = NULL;
213
214             /* Reconnect to the server */
215             if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
216                                                  AVAHI_CLIENT_NO_FAIL,
217                                                  client_callback,
218                                                  ctx,
219                                                  &error))) {
220
221                 LOG(log_error, logtype_afpd, "Failed to contact server: %s",
222                     avahi_strerror(error));
223
224                 avahi_client_free (ctx->client);
225                 avahi_threaded_poll_quit(ctx->threaded_poll);
226             }
227
228         } else {
229             LOG(log_error, logtype_afpd, "Client failure: %s",
230                 avahi_strerror(avahi_client_errno(client)));
231             avahi_client_free (ctx->client);
232             avahi_threaded_poll_quit(ctx->threaded_poll);
233         }
234         break;
235     }
236
237     case AVAHI_CLIENT_S_REGISTERING:
238         break;
239     case AVAHI_CLIENT_CONNECTING:
240         break;
241     }
242 }
243
244 /************************************************************************
245  * Public funcions
246  ************************************************************************/
247
248 /*
249  * Tries to setup the Zeroconf thread and any
250  * neccessary config setting.
251  */
252 void av_zeroconf_setup(const AFPConfig *configs) {
253     int error;
254
255     /* initialize the struct that holds our config settings. */
256     if (ctx) {
257         LOG(log_debug, logtype_afpd, "Resetting zeroconf records");
258         avahi_entry_group_reset(ctx->group);
259     } else {
260         ctx = calloc(1, sizeof(struct context));
261         ctx->configs = configs;
262         assert(ctx);
263     }
264
265 /* first of all we need to initialize our threading env */
266     if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
267         goto fail;
268     }
269
270 /* now we need to acquire a client */
271     if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
272                                          AVAHI_CLIENT_NO_FAIL,
273                                          client_callback,
274                                          NULL,
275                                          &error))) {
276         LOG(log_error, logtype_afpd, "Failed to create client object: %s",
277             avahi_strerror(avahi_client_errno(ctx->client)));
278         goto fail;
279     }
280
281     return;
282
283 fail:
284     if (ctx)
285         av_zeroconf_unregister();
286
287     return;
288 }
289
290 /*
291  * This function finally runs the loop impl.
292  */
293 int av_zeroconf_run(void) {
294     /* Finally, start the event loop thread */
295     if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
296         LOG(log_error, logtype_afpd, "Failed to create thread: %s",
297             avahi_strerror(avahi_client_errno(ctx->client)));
298         goto fail;
299     } else {
300         LOG(log_info, logtype_afpd, "Successfully started avahi loop.");
301     }
302
303     ctx->thread_running = 1;
304     return 0;
305
306 fail:
307     if (ctx)
308         av_zeroconf_unregister();
309
310     return -1;
311 }
312
313 /*
314  * Tries to shutdown this loop impl.
315  * Call this function from outside this thread.
316  */
317 void av_zeroconf_shutdown() {
318     /* Call this when the app shuts down */
319     avahi_threaded_poll_stop(ctx->threaded_poll);
320     avahi_client_free(ctx->client);
321     avahi_threaded_poll_free(ctx->threaded_poll);
322     free(ctx);
323     ctx = NULL;
324 }
325
326 /*
327  * Tries to shutdown this loop impl.
328  * Call this function from inside this thread.
329  */
330 int av_zeroconf_unregister() {
331     if (ctx->thread_running) {
332         /* First, block the event loop */
333         avahi_threaded_poll_lock(ctx->threaded_poll);
334
335         /* Than, do your stuff */
336         avahi_threaded_poll_quit(ctx->threaded_poll);
337
338         /* Finally, unblock the event loop */
339         avahi_threaded_poll_unlock(ctx->threaded_poll);
340         ctx->thread_running = 0;
341     }
342
343     if (ctx->client)
344         avahi_client_free(ctx->client);
345
346     if (ctx->threaded_poll)
347         avahi_threaded_poll_free(ctx->threaded_poll);
348
349     free(ctx);
350     ctx = NULL;
351
352     return 0;
353 }
354
355 #endif /* USE_AVAHI */
356