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