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#include <time.h> 16 17#include <avahi-common/strlst.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_avahi.h" 27 28/***************************************************************** 29 * Global variables 30 *****************************************************************/ 31struct context *ctx = NULL; 32 33/***************************************************************** 34 * Private functions 35 *****************************************************************/ 36 37static void publish_reply(AvahiEntryGroup *g, 38 AvahiEntryGroupState state, 39 void *userdata); 40 41/* 42 * This function tries to register the AFP DNS 43 * SRV service type. 44 */ 45static void register_stuff(void) { 46 uint port; 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_u8mname, -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 (dsi = ctx->obj->dsi; dsi; dsi = dsi->next) { 95 port = getip_port((struct sockaddr *)&dsi->server); 96 97 LOG(log_info, logtype_afpd, "hostname: %s", ctx->obj->options.hostname); 98 99 if (convert_string(ctx->obj->options.unixcharset, 100 CH_UTF8, 101 ctx->obj->options.hostname, 102 -1, 103 name, 104 MAXINSTANCENAMELEN) <= 0) { 105 LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name: %s", ctx->obj->options.hostname); 106 goto fail; 107 } 108 if ((dsi->bonjourname = strdup(name)) == NULL) { 109 LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name"); 110 goto fail; 111 112 } 113 LOG(log_info, logtype_afpd, "Registering server '%s' with Bonjour", 114 dsi->bonjourname); 115 116 if (avahi_entry_group_add_service(ctx->group, 117 AVAHI_IF_UNSPEC, 118 AVAHI_PROTO_UNSPEC, 119 0, 120 dsi->bonjourname, 121 AFP_DNS_SERVICE_TYPE, 122 NULL, 123 NULL, 124 port, 125 NULL) < 0) { 126 LOG(log_error, logtype_afpd, "Failed to add service: %s", 127 avahi_strerror(avahi_client_errno(ctx->client))); 128 goto fail; 129 } 130 131 if (i && avahi_entry_group_add_service_strlst(ctx->group, 132 AVAHI_IF_UNSPEC, 133 AVAHI_PROTO_UNSPEC, 134 0, 135 dsi->bonjourname, 136 ADISK_SERVICE_TYPE, 137 NULL, 138 NULL, 139 9, /* discard */ 140 strlist) < 0) { 141 LOG(log_error, logtype_afpd, "Failed to add service: %s", 142 avahi_strerror(avahi_client_errno(ctx->client))); 143 goto fail; 144 } /* if */ 145 146 if (ctx->obj->options.mimicmodel) { 147 strlist2 = avahi_string_list_add_printf(strlist2, "model=%s", ctx->obj->options.mimicmodel); 148 if (avahi_entry_group_add_service_strlst(ctx->group, 149 AVAHI_IF_UNSPEC, 150 AVAHI_PROTO_UNSPEC, 151 0, 152 dsi->bonjourname, 153 DEV_INFO_SERVICE_TYPE, 154 NULL, 155 NULL, 156 0, 157 strlist2) < 0) { 158 LOG(log_error, logtype_afpd, "Failed to add service: %s", 159 avahi_strerror(avahi_client_errno(ctx->client))); 160 goto fail; 161 } 162 } /* if (config->obj.options.mimicmodel) */ 163 164 } /* for config*/ 165 166 if (avahi_entry_group_commit(ctx->group) < 0) { 167 LOG(log_error, logtype_afpd, "Failed to commit entry group: %s", 168 avahi_strerror(avahi_client_errno(ctx->client))); 169 goto fail; 170 } 171 172 } /* if avahi_entry_group_is_empty*/ 173 174 return; 175 176fail: 177 time(NULL); 178// avahi_threaded_poll_quit(ctx->threaded_poll); 179} 180 181/* Called when publishing of service data completes */ 182static void publish_reply(AvahiEntryGroup *g, 183 AvahiEntryGroupState state, 184 AVAHI_GCC_UNUSED void *userdata) 185{ 186 assert(ctx->group == NULL || g == ctx->group); 187 188 switch (state) { 189 190 case AVAHI_ENTRY_GROUP_ESTABLISHED : 191 /* The entry group has been established successfully */ 192 LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED"); 193 break; 194 195 case AVAHI_ENTRY_GROUP_COLLISION: 196 /* With multiple names there's no way to know which one collided */ 197 LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION", 198 avahi_strerror(avahi_client_errno(ctx->client))); 199 avahi_threaded_poll_quit(ctx->threaded_poll); 200 break; 201 202 case AVAHI_ENTRY_GROUP_FAILURE: 203 LOG(log_error, logtype_afpd, "Failed to register service: %s", 204 avahi_strerror(avahi_client_errno(ctx->client))); 205 avahi_threaded_poll_quit(ctx->threaded_poll); 206 break; 207 208 case AVAHI_ENTRY_GROUP_UNCOMMITED: 209 break; 210 case AVAHI_ENTRY_GROUP_REGISTERING: 211 break; 212 } 213} 214 215static void client_callback(AvahiClient *client, 216 AvahiClientState state, 217 void *userdata) 218{ 219 ctx->client = client; 220 221 switch (state) { 222 case AVAHI_CLIENT_S_RUNNING: 223 /* The server has startup successfully and registered its host 224 * name on the network, so it's time to create our services */ 225 if (!ctx->group) 226 register_stuff(); 227 break; 228 229 case AVAHI_CLIENT_S_COLLISION: 230 if (ctx->group) 231 avahi_entry_group_reset(ctx->group); 232 break; 233 234 case AVAHI_CLIENT_FAILURE: { 235 if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) { 236 int error; 237 238 avahi_client_free(ctx->client); 239 ctx->client = NULL; 240 ctx->group = NULL; 241 242 /* Reconnect to the server */ 243 if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll), 244 AVAHI_CLIENT_NO_FAIL, 245 client_callback, 246 ctx, 247 &error))) { 248 249 LOG(log_error, logtype_afpd, "Failed to contact server: %s", 250 avahi_strerror(error)); 251 252 avahi_threaded_poll_quit(ctx->threaded_poll); 253 } 254 255 } else { 256 LOG(log_error, logtype_afpd, "Client failure: %s", 257 avahi_strerror(avahi_client_errno(client))); 258 avahi_threaded_poll_quit(ctx->threaded_poll); 259 } 260 break; 261 } 262 263 case AVAHI_CLIENT_S_REGISTERING: 264 break; 265 case AVAHI_CLIENT_CONNECTING: 266 break; 267 } 268} 269 270/************************************************************************ 271 * Public funcions 272 ************************************************************************/ 273 274/* 275 * Tries to setup the Zeroconf thread and any 276 * neccessary config setting. 277 */ 278void av_zeroconf_register(const AFPObj *obj) { 279 int error; 280 281 /* initialize the struct that holds our config settings. */ 282 if (ctx) { 283 LOG(log_debug, logtype_afpd, "Resetting zeroconf records"); 284 avahi_entry_group_reset(ctx->group); 285 } else { 286 ctx = calloc(1, sizeof(struct context)); 287 ctx->obj = obj; 288 assert(ctx); 289 } 290 291/* first of all we need to initialize our threading env */ 292 if (!(ctx->threaded_poll = avahi_threaded_poll_new())) { 293 goto fail; 294 } 295 296/* now we need to acquire a client */ 297 if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll), 298 AVAHI_CLIENT_NO_FAIL, 299 client_callback, 300 NULL, 301 &error))) { 302 LOG(log_error, logtype_afpd, "Failed to create client object: %s", 303 avahi_strerror(error)); 304 goto fail; 305 } 306 307 if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) { 308 LOG(log_error, logtype_afpd, "Failed to create thread: %s", 309 avahi_strerror(avahi_client_errno(ctx->client))); 310 goto fail; 311 } else { 312 LOG(log_info, logtype_afpd, "Successfully started avahi loop."); 313 } 314 315 ctx->thread_running = 1; 316 return; 317 318fail: 319 av_zeroconf_unregister(); 320 321 return; 322} 323 324/* 325 * Tries to shutdown this loop impl. 326 * Call this function from inside this thread. 327 */ 328int av_zeroconf_unregister() { 329 LOG(log_error, logtype_afpd, "av_zeroconf_unregister"); 330 331 if (ctx) { 332 LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_stop"); 333 if (ctx->threaded_poll) 334 avahi_threaded_poll_stop(ctx->threaded_poll); 335 LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_client_free"); 336 if (ctx->client) 337 avahi_client_free(ctx->client); 338 LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_free"); 339 if (ctx->threaded_poll) 340 avahi_threaded_poll_free(ctx->threaded_poll); 341 free(ctx); 342 ctx = NULL; 343 } 344 return 0; 345} 346 347#endif /* USE_AVAHI */ 348 349