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