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