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