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