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