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