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 24#include "afp_zeroconf.h" 25#include "afp_avahi.h" 26#include "afp_config.h" 27#include "volume.h" 28 29/***************************************************************** 30 * Global variables 31 *****************************************************************/ 32struct context *ctx = NULL; 33 34/***************************************************************** 35 * Private functions 36 *****************************************************************/ 37 38static void publish_reply(AvahiEntryGroup *g, 39 AvahiEntryGroupState state, 40 void *userdata); 41 42/* 43 * This function tries to register the AFP DNS 44 * SRV service type. 45 */ 46static void register_stuff(void) { 47 uint port; 48 const AFPConfig *config; 49 const struct vol *volume; 50 DSI *dsi; 51 char name[MAXINSTANCENAMELEN+1]; 52 AvahiStringList *strlist = NULL; 53 AvahiStringList *strlist2 = NULL; 54 char tmpname[256]; 55 56 assert(ctx->client); 57 58 if (!ctx->group) { 59 if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) { 60 LOG(log_error, logtype_afpd, "Failed to create entry group: %s", 61 avahi_strerror(avahi_client_errno(ctx->client))); 62 goto fail; 63 } 64 } 65 66 if (avahi_entry_group_is_empty(ctx->group)) { 67 /* Register our service */ 68 69 /* Build AFP volumes list */ 70 int i = 0; 71 strlist = avahi_string_list_add_printf(strlist, "sys=waMa=0,adVF=0x100"); 72 73 for (volume = getvolumes(); volume; volume = volume->v_next) { 74 75 if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_u8mname, -1, tmpname, 255) <= 0) { 76 LOG ( log_error, logtype_afpd, "Could not set Zeroconf volume name for TimeMachine"); 77 goto fail; 78 } 79 80 if (volume->v_flags & AFPVOL_TM) { 81 if (volume->v_uuid) { 82 LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine", 83 volume->v_localname, volume->v_uuid); 84 strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1,adVU=%s", 85 i++, tmpname, volume->v_uuid); 86 } else { 87 LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.", 88 volume->v_localname); 89 strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1", 90 i++, tmpname); 91 } 92 } 93 } 94 95 /* AFP server */ 96 for (config = ctx->configs; config; config = config->next) { 97 98 dsi = (DSI *)config->obj.handle; 99 port = getip_port((struct sockaddr *)&dsi->server); 100 101 if (convert_string(config->obj.options.unixcharset, 102 CH_UTF8, 103 config->obj.options.server ? 104 config->obj.options.server : 105 config->obj.options.hostname, 106 -1, 107 name, 108 MAXINSTANCENAMELEN) <= 0) { 109 LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name"); 110 goto fail; 111 } 112 if ((dsi->bonjourname = strdup(name)) == NULL) { 113 LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name"); 114 goto fail; 115 116 } 117 LOG(log_info, logtype_afpd, "Registering server '%s' with Bonjour", 118 dsi->bonjourname); 119 120 if (avahi_entry_group_add_service(ctx->group, 121 AVAHI_IF_UNSPEC, 122 AVAHI_PROTO_UNSPEC, 123 0, 124 dsi->bonjourname, 125 AFP_DNS_SERVICE_TYPE, 126 NULL, 127 NULL, 128 port, 129 NULL) < 0) { 130 LOG(log_error, logtype_afpd, "Failed to add service: %s", 131 avahi_strerror(avahi_client_errno(ctx->client))); 132 goto fail; 133 } 134 135 if (i && avahi_entry_group_add_service_strlst(ctx->group, 136 AVAHI_IF_UNSPEC, 137 AVAHI_PROTO_UNSPEC, 138 0, 139 dsi->bonjourname, 140 ADISK_SERVICE_TYPE, 141 NULL, 142 NULL, 143 9, /* discard */ 144 strlist) < 0) { 145 LOG(log_error, logtype_afpd, "Failed to add service: %s", 146 avahi_strerror(avahi_client_errno(ctx->client))); 147 goto fail; 148 } /* if */ 149 150 if (config->obj.options.mimicmodel) { 151 strlist2 = avahi_string_list_add_printf(strlist2, "model=%s", config->obj.options.mimicmodel); 152 if (avahi_entry_group_add_service_strlst(ctx->group, 153 AVAHI_IF_UNSPEC, 154 AVAHI_PROTO_UNSPEC, 155 0, 156 dsi->bonjourname, 157 DEV_INFO_SERVICE_TYPE, 158 NULL, 159 NULL, 160 0, 161 strlist2) < 0) { 162 LOG(log_error, logtype_afpd, "Failed to add service: %s", 163 avahi_strerror(avahi_client_errno(ctx->client))); 164 goto fail; 165 } 166 } /* if (config->obj.options.mimicmodel) */ 167 168 } /* for config*/ 169 170 if (avahi_entry_group_commit(ctx->group) < 0) { 171 LOG(log_error, logtype_afpd, "Failed to commit entry group: %s", 172 avahi_strerror(avahi_client_errno(ctx->client))); 173 goto fail; 174 } 175 176 } /* if avahi_entry_group_is_empty*/ 177 178 return; 179 180fail: 181 time(NULL); 182// avahi_threaded_poll_quit(ctx->threaded_poll); 183} 184 185/* Called when publishing of service data completes */ 186static void publish_reply(AvahiEntryGroup *g, 187 AvahiEntryGroupState state, 188 AVAHI_GCC_UNUSED void *userdata) 189{ 190 assert(ctx->group == NULL || g == ctx->group); 191 192 switch (state) { 193 194 case AVAHI_ENTRY_GROUP_ESTABLISHED : 195 /* The entry group has been established successfully */ 196 LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED"); 197 break; 198 199 case AVAHI_ENTRY_GROUP_COLLISION: 200 /* With multiple names there's no way to know which one collided */ 201 LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION", 202 avahi_strerror(avahi_client_errno(ctx->client))); 203 avahi_threaded_poll_quit(ctx->threaded_poll); 204 break; 205 206 case AVAHI_ENTRY_GROUP_FAILURE: 207 LOG(log_error, logtype_afpd, "Failed to register service: %s", 208 avahi_strerror(avahi_client_errno(ctx->client))); 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_threaded_poll_quit(ctx->threaded_poll); 257 } 258 259 } else { 260 LOG(log_error, logtype_afpd, "Client failure: %s", 261 avahi_strerror(avahi_client_errno(client))); 262 avahi_threaded_poll_quit(ctx->threaded_poll); 263 } 264 break; 265 } 266 267 case AVAHI_CLIENT_S_REGISTERING: 268 break; 269 case AVAHI_CLIENT_CONNECTING: 270 break; 271 } 272} 273 274/************************************************************************ 275 * Public funcions 276 ************************************************************************/ 277 278/* 279 * Tries to setup the Zeroconf thread and any 280 * neccessary config setting. 281 */ 282void av_zeroconf_register(const AFPConfig *configs) { 283 int error; 284 285 /* initialize the struct that holds our config settings. */ 286 if (ctx) { 287 LOG(log_debug, logtype_afpd, "Resetting zeroconf records"); 288 avahi_entry_group_reset(ctx->group); 289 } else { 290 ctx = calloc(1, sizeof(struct context)); 291 ctx->configs = configs; 292 assert(ctx); 293 } 294 295/* first of all we need to initialize our threading env */ 296 if (!(ctx->threaded_poll = avahi_threaded_poll_new())) { 297 goto fail; 298 } 299 300/* now we need to acquire a client */ 301 if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll), 302 AVAHI_CLIENT_NO_FAIL, 303 client_callback, 304 NULL, 305 &error))) { 306 LOG(log_error, logtype_afpd, "Failed to create client object: %s", 307 avahi_strerror(error)); 308 goto fail; 309 } 310 311 if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) { 312 LOG(log_error, logtype_afpd, "Failed to create thread: %s", 313 avahi_strerror(avahi_client_errno(ctx->client))); 314 goto fail; 315 } else { 316 LOG(log_info, logtype_afpd, "Successfully started avahi loop."); 317 } 318 319 ctx->thread_running = 1; 320 return; 321 322fail: 323 av_zeroconf_unregister(); 324 325 return; 326} 327 328/* 329 * Tries to shutdown this loop impl. 330 * Call this function from inside this thread. 331 */ 332int av_zeroconf_unregister() { 333 LOG(log_error, logtype_afpd, "av_zeroconf_unregister"); 334 335 if (ctx) { 336 LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_stop"); 337 if (ctx->threaded_poll) 338 avahi_threaded_poll_stop(ctx->threaded_poll); 339 LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_client_free"); 340 if (ctx->client) 341 avahi_client_free(ctx->client); 342 LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_free"); 343 if (ctx->threaded_poll) 344 avahi_threaded_poll_free(ctx->threaded_poll); 345 free(ctx); 346 ctx = NULL; 347 } 348 return 0; 349} 350 351#endif /* USE_AVAHI */ 352 353