]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/afp_mdns.c
Spotlight: use async Tracker SPARQL API
[netatalk.git] / etc / afpd / 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     // Now we can count the configs so we know how many service
211     // records to allocate
212     for (dsi = obj->dsi; dsi; dsi = dsi->next) {
213         svc_ref_count++;                    // AFP_DNS_SERVICE_TYPE
214         if (i) svc_ref_count++;      // ADISK_SERVICE_TYPE
215         if (obj->options.mimicmodel) svc_ref_count++;        // DEV_INFO_SERVICE_TYPE
216     }
217
218     // Allocate the memory to store our service refs
219     svc_refs = calloc(svc_ref_count, sizeof(DNSServiceRef));
220     assert(svc_refs);
221     svc_ref_count = 0;
222
223     /* AFP server */
224     for (dsi = obj->dsi; dsi; dsi = dsi->next) {
225
226         port = getip_port((struct sockaddr *)&dsi->server);
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         if ((dsi->bonjourname = strdup(name)) == NULL) {
238             LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name");
239             goto fail;
240
241         }
242         LOG(log_info, logtype_afpd, "Registering server '%s' with Bonjour",
243             dsi->bonjourname);
244
245         error = DNSServiceRegister(&svc_refs[svc_ref_count++],
246                                    0,               // no flags
247                                    0,               // all network interfaces
248                                    dsi->bonjourname,
249                                    AFP_DNS_SERVICE_TYPE,
250                                    "",            // default domains
251                                    NULL,            // default host name
252                                    htons(port),
253                                    0,               // length of TXT
254                                    NULL,            // no TXT
255                                    RegisterReply,           // callback
256                                    NULL);       // no context
257         if(error != kDNSServiceErr_NoError) {
258             LOG(log_error, logtype_afpd, "Failed to add service: %s, error=%d",
259                 AFP_DNS_SERVICE_TYPE, error);
260             goto fail;
261         }
262
263         if(i) {
264             error = DNSServiceRegister(&svc_refs[svc_ref_count++],
265                                        0,               // no flags
266                                        0,               // all network interfaces
267                                        dsi->bonjourname,
268                                        ADISK_SERVICE_TYPE,
269                                        "",            // default domains
270                                        NULL,            // default host name
271                                        htons(port),
272                                        TXTRecordGetLength(&txt_adisk),
273                                        TXTRecordGetBytesPtr(&txt_adisk),
274                                        RegisterReply,           // callback
275                                        NULL);       // no context
276             if(error != kDNSServiceErr_NoError) {
277                 LOG(log_error, logtype_afpd, "Failed to add service: %s, error=%d",
278                     ADISK_SERVICE_TYPE, error);
279                 goto fail;
280             }
281         }
282
283         if (obj->options.mimicmodel) {
284             LOG(log_info, logtype_afpd, "Registering server '%s' with model '%s'",
285                 dsi->bonjourname, obj->options.mimicmodel);
286             TXTRecordCreate(&txt_devinfo, 0, NULL);
287             if( 0 > TXTRecordPrintf(&txt_devinfo, "model", obj->options.mimicmodel) ) {
288               LOG ( log_error, logtype_afpd, "Could not create Zeroconf TXTRecord for model");
289               goto fail;
290             }
291
292             error = DNSServiceRegister(&svc_refs[svc_ref_count++],
293                                        0,               // no flags
294                                        0,               // all network interfaces
295                                        dsi->bonjourname,
296                                        DEV_INFO_SERVICE_TYPE,
297                                        "",            // default domains
298                                        NULL,            // default host name
299                                        /*
300                                         * We would probably use port 0 zero, but we can't, from man DNSServiceRegister:
301                                         *   "A value of 0 for a port is passed to register placeholder services.
302                                         *    Place holder services are not found  when browsing, but other
303                                         *    clients cannot register with the same name as the placeholder service."
304                                         * We therefor use port 9 which is used by the adisk service type.
305                                         */
306                                        htons(9),
307                                        TXTRecordGetLength(&txt_devinfo),
308                                        TXTRecordGetBytesPtr(&txt_devinfo),
309                                        RegisterReply,           // callback
310                                        NULL);       // no context
311             TXTRecordDeallocate(&txt_devinfo);
312             if(error != kDNSServiceErr_NoError) {
313                 LOG(log_error, logtype_afpd, "Failed to add service: %s, error=%d",
314                     DEV_INFO_SERVICE_TYPE, error);
315                 goto fail;
316             }
317         } /* if (config->obj.options.mimicmodel) */
318     }   /* for config*/
319
320         /*
321          * Now we can create the thread that will poll for the results
322          * and handle the calling of the callbacks
323          */
324     if(pthread_create(&poller, NULL, polling_thread, NULL) != 0) {
325         LOG(log_error, logtype_afpd, "Unable to start mDNS polling thread");
326         goto fail;
327     }
328
329 fail:
330     TXTRecordDeallocate(&txt_adisk);
331     return;
332 }
333
334 /************************************************************************
335  * Public funcions
336  ************************************************************************/
337
338 /*
339  * Tries to setup the Zeroconf thread and any
340  * neccessary config setting.
341  */
342 void md_zeroconf_register(const AFPObj *obj) {
343     int error;
344
345     register_stuff(obj);
346     return;
347 }
348
349 /*
350  * Tries to shutdown this loop impl.
351  * Call this function from inside this thread.
352  */
353 int md_zeroconf_unregister() {
354     unregister_stuff();
355     return 0;
356 }
357
358 #endif /* USE_MDNS */
359