]> arthur.barton.de Git - netatalk.git/blob - etc/netatalk/afp_mdns.c
netatalk: fix a crash on Solaris when registering with mDNS
[netatalk.git] / etc / netatalk / afp_mdns.c
1 /*
2  * Author:   Lee Essen <lee.essen@nowonline.co.uk>
3  * Based on: avahi support from Daniel S. Haischt <me@daniel.stefan.haischt.name>
4  * Purpose:  mdns based Zeroconf support
5  *
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #ifdef HAVE_MDNS
13
14 #include <unistd.h>
15 #include <time.h>
16 #include <pthread.h>
17 #include <poll.h>
18
19 #include <atalk/logger.h>
20 #include <atalk/util.h>
21 #include <atalk/unicode.h>
22 #include <atalk/netatalk_conf.h>
23
24 #include "afp_zeroconf.h"
25 #include "afp_mdns.h"
26
27 /*
28  * We'll store all the DNSServiceRef's here so that we can
29  * deallocate them later
30  */
31 static DNSServiceRef   *svc_refs = NULL;
32 static int             svc_ref_count = 0;
33 static pthread_t       poller;
34
35 /*
36  * Its easier to use asprintf to set the TXT record values
37  */
38
39 int TXTRecordPrintf(TXTRecordRef * rec, const char * key, const char * fmt, ... ) 
40 {
41     int ret = 0;
42     char *str;
43     va_list ap;
44     va_start( ap, fmt );
45
46     if( 0 > vasprintf(&str, fmt, ap ) ) {
47         va_end(ap);
48         return -1;    
49     }
50     va_end(ap);
51
52     if( kDNSServiceErr_NoError != TXTRecordSetValue(rec, key, strlen(str), str) ) {
53         ret = -1;
54     }
55
56     free(str);
57     return ret;
58 }
59
60 int TXTRecordKeyPrintf(TXTRecordRef * rec, const char * key_fmt, int key_var, const char * fmt, ...) 
61 {
62     int ret = 0;
63     char *key = NULL, *str = NULL;
64     va_list ap;
65
66     if( 0 > asprintf(&key, key_fmt, key_var))
67         return -1;
68
69     va_start( ap, fmt );
70     if( 0 > vasprintf(&str, fmt, ap )) {
71         va_end(ap);
72         ret = -1;
73         goto exit;
74     }
75     va_end(ap);
76
77     if( kDNSServiceErr_NoError != TXTRecordSetValue(rec, key, strlen(str), str) ) {
78         ret = -1;
79         goto exit;
80     }
81
82 exit:
83     if (str)
84         free(str);
85     if (key)
86         free(key);
87     return ret;
88 }
89
90 static struct pollfd *fds;
91
92 /*
93  * This is the thread that polls the filehandles
94  */
95 static void *polling_thread(void *arg) {
96     // First we loop through getting the filehandles and adding them to our poll, we
97     // need to allocate our pollfd's
98     DNSServiceErrorType error;
99     fds = calloc(svc_ref_count, sizeof(struct pollfd));
100     assert(fds);
101
102     for(int i=0; i < svc_ref_count; i++) {
103         int fd = DNSServiceRefSockFD(svc_refs[i]);
104         fds[i].fd = fd;
105         fds[i].events = POLLIN;
106     }
107
108     // Now we can poll and process the results...
109     while(poll(fds, svc_ref_count, -1) > 0) {
110         for(int i=0; i < svc_ref_count; i++) {
111             if(fds[i].revents & POLLIN) {
112                 error = DNSServiceProcessResult(svc_refs[i]);
113             }
114         }
115     }
116     return(NULL);
117 }
118
119 /*
120  * This is the callback for the service register function ... actually there isn't a lot
121  * we can do if we get problems, so we don't really need to do anything other than report
122  * the issue.
123  */
124 static void RegisterReply(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
125                           const char *name, const char *regtype, const char *domain, void *context)
126 {
127     if (errorCode != kDNSServiceErr_NoError) {
128         LOG(log_error, logtype_afpd, "Failed to register mDNS service: %s%s%s: code=%d",
129             name, regtype, domain, errorCode);
130     }
131 }
132
133 /*
134  * This function unregisters anything we have already
135  * registered and frees associated memory
136  */
137 static void unregister_stuff() {
138     pthread_cancel(poller);    
139
140     for (int i = 0; i < svc_ref_count; i++)
141         close(fds[i].fd);
142     free(fds);
143     fds = NULL;
144
145     if(svc_refs) {
146         for(int i=0; i < svc_ref_count; i++) {
147             DNSServiceRefDeallocate(svc_refs[i]);
148         }
149         free(svc_refs);
150         svc_refs = NULL;
151         svc_ref_count = 0;
152     }
153 }
154
155 /*
156  * This function tries to register the AFP DNS
157  * SRV service type.
158  */
159 static void register_stuff(const AFPObj *obj) {
160     uint                                        port;
161     const struct vol                *volume;
162     char                                        name[MAXINSTANCENAMELEN+1];
163     DNSServiceErrorType         error;
164     TXTRecordRef                        txt_adisk;
165     TXTRecordRef                        txt_devinfo;
166     char                                        tmpname[256];
167
168     // If we had already registered, then we will unregister and re-register
169     if(svc_refs) unregister_stuff();
170
171     /* Register our service, prepare the TXT record */
172     TXTRecordCreate(&txt_adisk, 0, NULL);
173     if( 0 > TXTRecordPrintf(&txt_adisk, "sys", "waMa=0,adVF=0x100") ) {
174         LOG ( log_error, logtype_afpd, "Could not create Zeroconf TXTRecord for sys");
175         goto fail;
176     }
177
178     /* Build AFP volumes list */
179     int i = 0;
180
181     for (volume = getvolumes(); volume; volume = volume->v_next) {
182
183         if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_u8mname, -1, tmpname, 255) <= 0) {
184             LOG ( log_error, logtype_afpd, "Could not set Zeroconf volume name for TimeMachine");
185             goto fail;
186         }
187
188         if (volume->v_flags & AFPVOL_TM) {
189             if (volume->v_uuid) {
190                 LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine",
191                     volume->v_localname, volume->v_uuid);
192                 if( 0 > TXTRecordKeyPrintf(&txt_adisk, "dk%u", i++, "adVN=%s,adVF=0xa1,adVU=%s",
193                                    tmpname, volume->v_uuid) ) {
194                     LOG ( log_error, logtype_afpd, "Could not set Zeroconf TXTRecord for dk%u", i);
195                     goto fail;
196                 }
197             } else {
198                 LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.",
199                     volume->v_localname);
200                 if( 0 > TXTRecordKeyPrintf(&txt_adisk, "dk%u", i++, "adVN=%s,adVF=0xa1", tmpname) ) {
201                     LOG ( log_error, logtype_afpd, "Could not set Zeroconf TXTRecord for dk%u", i);
202                     goto fail;
203                 }
204             }
205         }
206     }
207
208     /* AFP_DNS_SERVICE_TYPE */
209     svc_ref_count = 1;
210     if (i) {
211         /* ADISK_SERVICE_TYPE */
212         svc_ref_count++;
213     }
214     if (obj->options.mimicmodel) {
215         /* DEV_INFO_SERVICE_TYPE */
216         svc_ref_count++;
217     }
218
219     // Allocate the memory to store our service refs
220     svc_refs = calloc(svc_ref_count, sizeof(DNSServiceRef));
221     assert(svc_refs);
222     svc_ref_count = 0;
223
224     port = atoi(obj->options.port);
225
226     if (convert_string(obj->options.unixcharset,
227                        CH_UTF8,
228                        obj->options.hostname,
229                        -1,
230                        name,
231                        MAXINSTANCENAMELEN) <= 0) {
232         LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name");
233         goto fail;
234     }
235
236     error = DNSServiceRegister(&svc_refs[svc_ref_count++],
237                                0,               // no flags
238                                0,               // all network interfaces
239                                name,
240                                AFP_DNS_SERVICE_TYPE,
241                                "",            // default domains
242                                NULL,            // default host name
243                                htons(port),
244                                0,               // length of TXT
245                                NULL,            // no TXT
246                                RegisterReply,           // callback
247                                NULL);       // no context
248     if (error != kDNSServiceErr_NoError) {
249         LOG(log_error, logtype_afpd, "Failed to add service: %s, error=%d",
250             AFP_DNS_SERVICE_TYPE, error);
251         goto fail;
252     }
253
254     if (i) {
255         error = DNSServiceRegister(&svc_refs[svc_ref_count++],
256                                    0,               // no flags
257                                    0,               // all network interfaces
258                                    name,
259                                    ADISK_SERVICE_TYPE,
260                                    "",            // default domains
261                                    NULL,            // default host name
262                                    htons(port),
263                                    TXTRecordGetLength(&txt_adisk),
264                                    TXTRecordGetBytesPtr(&txt_adisk),
265                                    RegisterReply,           // callback
266                                    NULL);       // no context
267         if (error != kDNSServiceErr_NoError) {
268             LOG(log_error, logtype_afpd, "Failed to add service: %s, error=%d",
269                 ADISK_SERVICE_TYPE, error);
270             goto fail;
271         }
272     }
273
274     if (obj->options.mimicmodel) {
275         LOG(log_info, logtype_afpd, "Registering server as model '%s'",
276             obj->options.mimicmodel);
277         TXTRecordCreate(&txt_devinfo, 0, NULL);
278         if ( 0 > TXTRecordPrintf(&txt_devinfo, "model", obj->options.mimicmodel) ) {
279             LOG ( log_error, logtype_afpd, "Could not create Zeroconf TXTRecord for model");
280             goto fail;
281         }
282
283         error = DNSServiceRegister(&svc_refs[svc_ref_count++],
284                                    0,               // no flags
285                                    0,               // all network interfaces
286                                    name,
287                                    DEV_INFO_SERVICE_TYPE,
288                                    "",            // default domains
289                                    NULL,            // default host name
290                                    /*
291                                     * We would probably use port 0 zero, but we can't, from man DNSServiceRegister:
292                                     *   "A value of 0 for a port is passed to register placeholder services.
293                                     *    Place holder services are not found  when browsing, but other
294                                     *    clients cannot register with the same name as the placeholder service."
295                                     * We therefor use port 9 which is used by the adisk service type.
296                                     */
297                                    htons(9),
298                                    TXTRecordGetLength(&txt_devinfo),
299                                    TXTRecordGetBytesPtr(&txt_devinfo),
300                                    RegisterReply,           // callback
301                                    NULL);       // no context
302         TXTRecordDeallocate(&txt_devinfo);
303         if (error != kDNSServiceErr_NoError) {
304             LOG(log_error, logtype_afpd, "Failed to add service: %s, error=%d",
305                 DEV_INFO_SERVICE_TYPE, error);
306             goto fail;
307         }
308     } /* if (config->obj.options.mimicmodel) */
309
310     /*
311      * Now we can create the thread that will poll for the results
312      * and handle the calling of the callbacks
313      */
314     if(pthread_create(&poller, NULL, polling_thread, NULL) != 0) {
315         LOG(log_error, logtype_afpd, "Unable to start mDNS polling thread");
316         goto fail;
317     }
318
319 fail:
320     TXTRecordDeallocate(&txt_adisk);
321     return;
322 }
323
324 /************************************************************************
325  * Public funcions
326  ************************************************************************/
327
328 /*
329  * Tries to setup the Zeroconf thread and any
330  * neccessary config setting.
331  */
332 void md_zeroconf_register(const AFPObj *obj) {
333     int error;
334
335     register_stuff(obj);
336     return;
337 }
338
339 /*
340  * Tries to shutdown this loop impl.
341  * Call this function from inside this thread.
342  */
343 int md_zeroconf_unregister() {
344     unregister_stuff();
345     return 0;
346 }
347
348 #endif /* USE_MDNS */
349