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