1/* 2 * Author: Daniel S. Haischt <me@daniel.stefan.haischt.name> 3 * Purpose: Avahi based Zeroconf support 4 * Docs: http://avahi.org/download/doxygen/ 5 * 6 */ 7 8#ifdef HAVE_CONFIG_H 9#include <config.h> 10#endif 11 12#ifdef HAVE_AVAHI 13 14#include <unistd.h> 15 16#include <avahi-common/strlst.h> 17 18#include <atalk/logger.h> 19#include <atalk/util.h> 20#include <atalk/dsi.h> 21#include <atalk/unicode.h> 22 23#include "afp_avahi.h" 24#include "afp_config.h" 25#include "volume.h" 26 27/***************************************************************** 28 * Global variables 29 *****************************************************************/ 30struct context *ctx = NULL; 31 32/***************************************************************** 33 * Private functions 34 *****************************************************************/ 35 36static void publish_reply(AvahiEntryGroup *g, 37 AvahiEntryGroupState state, 38 void *userdata); 39 40/* 41 * This function tries to register the AFP DNS 42 * SRV service type. 43 */ 44static void register_stuff(void) { 45 uint port; 46 const AFPConfig *config; 47 const struct vol *volume; 48 DSI *dsi; 49 char name[MAXINSTANCENAMELEN+1]; 50 AvahiStringList *strlist = NULL; 51 AvahiStringList *strlist2 = NULL; 52 char tmpname[256]; 53 54 assert(ctx->client); 55 56 if (!ctx->group) { 57 if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) { 58 LOG(log_error, logtype_afpd, "Failed to create entry group: %s", 59 avahi_strerror(avahi_client_errno(ctx->client))); 60 goto fail; 61 } 62 } 63 64 if (avahi_entry_group_is_empty(ctx->group)) { 65 /* Register our service */ 66 67 /* Build AFP volumes list */ 68 int i = 0; 69 strlist = avahi_string_list_add_printf(strlist, "sys=waMa=0,adVF=0x100"); 70 71 for (volume = getvolumes(); volume; volume = volume->v_next) { 72 73 if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_name, -1, tmpname, 255) <= 0) { 74 LOG ( log_error, logtype_afpd, "Could not set Zeroconf volume name for TimeMachine"); 75 goto fail; 76 } 77 78 if (volume->v_flags & AFPVOL_TM) { 79 if (volume->v_uuid) { 80 LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine", 81 volume->v_localname, volume->v_uuid); 82 strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1,adVU=%s", 83 i++, tmpname, volume->v_uuid); 84 } else { 85 LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.", 86 volume->v_localname); 87 strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1", 88 i++, tmpname); 89 } 90 } 91 } 92 93 /* AFP server */ 94 for (config = ctx->configs; config; config = config->next) { 95 96 dsi = (DSI *)config->obj.handle; 97 port = getip_port((struct sockaddr *)&dsi->server); 98 99 if (convert_string(config->obj.options.unixcharset, 100 CH_UTF8, 101 config->obj.options.server ? 102 config->obj.options.server : 103 config->obj.options.hostname, 104 -1, 105 name, 106 MAXINSTANCENAMELEN) <= 0) { 107 LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name"); 108 goto fail; 109 } 110 if ((dsi->bonjourname = strdup(name)) == NULL) { 111 LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name"); 112 goto fail; 113 114 } 115 LOG(log_info, logtype_afpd, "Registering server '%s' with with Bonjour", 116 dsi->bonjourname); 117 118 if (avahi_entry_group_add_service(ctx->group, 119 AVAHI_IF_UNSPEC, 120 AVAHI_PROTO_UNSPEC, 121 0, 122 dsi->bonjourname, 123 AFP_DNS_SERVICE_TYPE, 124 NULL, 125 NULL, 126 port, 127 NULL) < 0) { 128 LOG(log_error, logtype_afpd, "Failed to add service: %s", 129 avahi_strerror(avahi_client_errno(ctx->client))); 130 goto fail; 131 } 132 133 if (i && avahi_entry_group_add_service_strlst(ctx->group, 134 AVAHI_IF_UNSPEC, 135 AVAHI_PROTO_UNSPEC, 136 0, 137 dsi->bonjourname, 138 ADISK_SERVICE_TYPE, 139 NULL, 140 NULL, 141 9, /* discard */ 142 strlist) < 0) { 143 LOG(log_error, logtype_afpd, "Failed to add service: %s", 144 avahi_strerror(avahi_client_errno(ctx->client))); 145 goto fail; 146 } /* if */ 147 148 if (config->obj.options.mimicmodel) { 149 strlist2 = avahi_string_list_add_printf(strlist2, "model=%s", config->obj.options.mimicmodel); 150 if (avahi_entry_group_add_service_strlst(ctx->group, 151 AVAHI_IF_UNSPEC, 152 AVAHI_PROTO_UNSPEC, 153 0, 154 dsi->bonjourname, 155 DEV_INFO_SERVICE_TYPE, 156 NULL, 157 NULL, 158 0, 159 strlist2) < 0) { 160 LOG(log_error, logtype_afpd, "Failed to add service: %s", 161 avahi_strerror(avahi_client_errno(ctx->client))); 162 goto fail; 163 } 164 } /* if (config->obj.options.mimicmodel) */ 165 166 } /* for config*/ 167 168 if (avahi_entry_group_commit(ctx->group) < 0) { 169 LOG(log_error, logtype_afpd, "Failed to commit entry group: %s", 170 avahi_strerror(avahi_client_errno(ctx->client))); 171 goto fail; 172 } 173 174 } /* if avahi_entry_group_is_empty*/ 175 176 return; 177 178fail: 179 avahi_client_free (ctx->client); 180 avahi_threaded_poll_quit(ctx->threaded_poll); 181} 182 183/* Called when publishing of service data completes */ 184static void publish_reply(AvahiEntryGroup *g, 185 AvahiEntryGroupState state, 186 AVAHI_GCC_UNUSED void *userdata) 187{ 188 assert(ctx->group == NULL || g == ctx->group); 189 190 switch (state) { 191 192 case AVAHI_ENTRY_GROUP_ESTABLISHED : 193 /* The entry group has been established successfully */ 194 LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED"); 195 break; 196 197 case AVAHI_ENTRY_GROUP_COLLISION: 198 /* With multiple names there's no way to know which one collided */ 199 LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION", 200 avahi_strerror(avahi_client_errno(ctx->client))); 201 avahi_client_free(avahi_entry_group_get_client(g)); 202 avahi_threaded_poll_quit(ctx->threaded_poll); 203 break; 204 205 case AVAHI_ENTRY_GROUP_FAILURE: 206 LOG(log_error, logtype_afpd, "Failed to register service: %s", 207 avahi_strerror(avahi_client_errno(ctx->client))); 208 avahi_client_free(avahi_entry_group_get_client(g)); 209 avahi_threaded_poll_quit(ctx->threaded_poll); 210 break; 211 212 case AVAHI_ENTRY_GROUP_UNCOMMITED: 213 break; 214 case AVAHI_ENTRY_GROUP_REGISTERING: 215 break; 216 } 217} 218 219static void client_callback(AvahiClient *client, 220 AvahiClientState state, 221 void *userdata) 222{ 223 ctx->client = client; 224 225 switch (state) { 226 case AVAHI_CLIENT_S_RUNNING: 227 /* The server has startup successfully and registered its host 228 * name on the network, so it's time to create our services */ 229 if (!ctx->group) 230 register_stuff(); 231 break; 232 233 case AVAHI_CLIENT_S_COLLISION: 234 if (ctx->group) 235 avahi_entry_group_reset(ctx->group); 236 break; 237 238 case AVAHI_CLIENT_FAILURE: { 239 if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) { 240 int error; 241 242 avahi_client_free(ctx->client); 243 ctx->client = NULL; 244 ctx->group = NULL; 245 246 /* Reconnect to the server */ 247 if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll), 248 AVAHI_CLIENT_NO_FAIL, 249 client_callback, 250 ctx, 251 &error))) { 252 253 LOG(log_error, logtype_afpd, "Failed to contact server: %s", 254 avahi_strerror(error)); 255 256 avahi_client_free (ctx->client); 257 avahi_threaded_poll_quit(ctx->threaded_poll); 258 } 259 260 } else { 261 LOG(log_error, logtype_afpd, "Client failure: %s", 262 avahi_strerror(avahi_client_errno(client))); 263 avahi_client_free (ctx->client); 264 avahi_threaded_poll_quit(ctx->threaded_poll); 265 } 266 break; 267 } 268 269 case AVAHI_CLIENT_S_REGISTERING: 270 break; 271 case AVAHI_CLIENT_CONNECTING: 272 break; 273 } 274} 275 276/************************************************************************ 277 * Public funcions 278 ************************************************************************/ 279 280/* 281 * Tries to setup the Zeroconf thread and any 282 * neccessary config setting. 283 */ 284void av_zeroconf_setup(const AFPConfig *configs) { 285 int error; 286 287 /* initialize the struct that holds our config settings. */ 288 if (ctx) { 289 LOG(log_debug, logtype_afpd, "Resetting zeroconf records"); 290 avahi_entry_group_reset(ctx->group); 291 } else { 292 ctx = calloc(1, sizeof(struct context)); 293 ctx->configs = configs; 294 assert(ctx); 295 } 296 297/* first of all we need to initialize our threading env */ 298 if (!(ctx->threaded_poll = avahi_threaded_poll_new())) { 299 goto fail; 300 } 301 302/* now we need to acquire a client */ 303 if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll), 304 AVAHI_CLIENT_NO_FAIL, 305 client_callback, 306 NULL, 307 &error))) { 308 LOG(log_error, logtype_afpd, "Failed to create client object: %s", 309 avahi_strerror(avahi_client_errno(ctx->client))); 310 goto fail; 311 } 312 313 return; 314 315fail: 316 if (ctx) 317 av_zeroconf_unregister(); 318 319 return; 320} 321 322/* 323 * This function finally runs the loop impl. 324 */ 325int av_zeroconf_run(void) { 326 /* Finally, start the event loop thread */ 327 if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) { 328 LOG(log_error, logtype_afpd, "Failed to create thread: %s", 329 avahi_strerror(avahi_client_errno(ctx->client))); 330 goto fail; 331 } else { 332 LOG(log_info, logtype_afpd, "Successfully started avahi loop."); 333 } 334 335 ctx->thread_running = 1; 336 return 0; 337 338fail: 339 if (ctx) 340 av_zeroconf_unregister(); 341 342 return -1; 343} 344 345/* 346 * Tries to shutdown this loop impl. 347 * Call this function from outside this thread. 348 */ 349void av_zeroconf_shutdown() { 350 /* Call this when the app shuts down */ 351 avahi_threaded_poll_stop(ctx->threaded_poll); 352 avahi_client_free(ctx->client); 353 avahi_threaded_poll_free(ctx->threaded_poll); 354 free(ctx); 355 ctx = NULL; 356} 357 358/* 359 * Tries to shutdown this loop impl. 360 * Call this function from inside this thread. 361 */ 362int av_zeroconf_unregister() { 363 if (ctx->thread_running) { 364 /* First, block the event loop */ 365 avahi_threaded_poll_lock(ctx->threaded_poll); 366 367 /* Than, do your stuff */ 368 avahi_threaded_poll_quit(ctx->threaded_poll); 369 370 /* Finally, unblock the event loop */ 371 avahi_threaded_poll_unlock(ctx->threaded_poll); 372 ctx->thread_running = 0; 373 } 374 375 if (ctx->client) 376 avahi_client_free(ctx->client); 377 378 if (ctx->threaded_poll) 379 avahi_threaded_poll_free(ctx->threaded_poll); 380 381 free(ctx); 382 ctx = NULL; 383 384 return 0; 385} 386 387#endif /* USE_AVAHI */ 388 389