From 923bc7a1afeb0b920e60e14846987ae1d2d7dca4 Mon Sep 17 00:00:00 2001
From: John Hixson <john@ixsystems.com>
Date: Thu, 7 Dec 2017 09:36:32 -0500
Subject: [PATCH] Freenas/master mdns fixes (#22)

* mDNS fixes for Samba (work in progress).

* Fix mDNS - Can advertise on individual interfaces

* Fix mDNS browsing in smbclient

Signed-off-by: Timur I. Bakeyev <timur@iXsystems.com>

--- source3/client/dnsbrowse.c.orig	2019-01-15 10:07:00 UTC
+++ source3/client/dnsbrowse.c
@@ -39,6 +39,7 @@ struct mdns_smbsrv_result
 struct mdns_browse_state
 {
 	struct mdns_smbsrv_result *listhead; /* Browse result list head */
+	TALLOC_CTX * ctx;
 	int browseDone;
 
 };
@@ -64,7 +65,7 @@ static void do_smb_resolve(struct mdns_s
 	struct timeval tv;
 	DNSServiceErrorType err;
 
-	TALLOC_CTX * ctx = talloc_tos();
+	TALLOC_CTX * ctx = talloc_new(NULL);
 
 	err = DNSServiceResolve(&mdns_conn_sdref, 0 /* flags */,
 		browsesrv->ifIndex,
@@ -91,7 +92,7 @@ static void do_smb_resolve(struct mdns_s
 		}
 	}
 
-	TALLOC_FREE(fdset);
+	TALLOC_FREE(ctx);
 	DNSServiceRefDeallocate(mdns_conn_sdref);
 }
 
@@ -124,18 +125,19 @@ do_smb_browse_reply(DNSServiceRef sdRef,
 		return;
 	}
 
-	bresult = talloc_array(talloc_tos(), struct mdns_smbsrv_result, 1);
+	bresult = talloc_array(bstatep->ctx, struct mdns_smbsrv_result, 1);
 	if (bresult == NULL) {
 		return;
 	}
 
+	bresult->nextResult = NULL;
 	if (bstatep->listhead != NULL) {
 		bresult->nextResult = bstatep->listhead;
 	}
 
-	bresult->serviceName = talloc_strdup(talloc_tos(), serviceName);
-	bresult->regType = talloc_strdup(talloc_tos(), regtype);
-	bresult->domain = talloc_strdup(talloc_tos(), replyDomain);
+	bresult->serviceName = talloc_strdup(bstatep->ctx, serviceName);
+	bresult->regType = talloc_strdup(bstatep->ctx, regtype);
+	bresult->domain = talloc_strdup(bstatep->ctx, replyDomain);
 	bresult->ifIndex = interfaceIndex;
 	bstatep->listhead = bresult;
 }
@@ -151,10 +153,13 @@ int do_smb_browse(void)
 	DNSServiceRef mdns_conn_sdref = NULL;
 	DNSServiceErrorType err;
 
-	TALLOC_CTX * ctx = talloc_stackframe();
+	TALLOC_CTX * ctx = talloc_new(NULL);
 
 	ZERO_STRUCT(bstate);
 
+	bstate.ctx = ctx;
+	bstate.listhead = NULL;
+
 	err = DNSServiceBrowse(&mdns_conn_sdref, 0, 0, "_smb._tcp", "",
 			do_smb_browse_reply, &bstate);
 
--- source3/smbd/dnsregister.c.orig	2019-01-15 10:07:00 UTC
+++ source3/smbd/dnsregister.c
@@ -29,6 +29,29 @@
  * browse for advertised SMB services.
  */
 
+/*
+ * Time Machine Errata:
+ * sys=adVF=0x100 -- this is required when ._adisk._tcp is present on device. When it is
+ * set, the MacOS client will send a NetShareEnumAll IOCTL and shares will be visible.
+ * Otherwise, Finder will only see the Time Machine share. In the absence of ._adisk._tcp
+ * MacOS will _always_ send NetShareEnumAll IOCTL.
+ *
+ * waMa=0 -- MacOS server uses waMa=0, while embedded devices have it set to their Mac Address.
+ * Speculation in Samba-Technical indicates that this stands for "Wireless AirDisk Mac Address".
+ *
+ * adVU -- AirDisk Volume UUID. Mac OS servers generate a UUID. Time machine over SMB works without one
+ * set. Netatalk generates a UUID and stores it persistently in afp_voluuid.conf. This can be
+ * set by adding the share parameter "fruit:volume_uuid = "
+ *
+ * dk(n)=adVF=
+ *      0xa1, 0x81 - AFP support
+ *      0xa2, 0x82 - SMB support
+ *      0xa3, 0x83 - AFP and SMB support
+ *
+ * adVN -- AirDisk Volume Name. We set this to the share name.
+ *
+ */
+
 #define DNS_REG_RETRY_INTERVAL (5*60)  /* in seconds */
 
 #ifdef WITH_DNSSD_SUPPORT
@@ -36,85 +59,177 @@
 #include <dns_sd.h>
 
 struct dns_reg_state {
-	struct tevent_context *event_ctx;
-	uint16_t port;
-	DNSServiceRef srv_ref;
-	struct tevent_timer *te;
-	int fd;
-	struct tevent_fd *fde;
+	int count;
+	struct reg_state {
+		DNSServiceRef srv_ref;
+		TALLOC_CTX *mem_ctx;
+		struct tevent_context *event_ctx;
+		struct tevent_timer *te;
+		struct tevent_fd *fde;
+		uint16_t port;
+		int if_index;
+		int fd;
+	} *drs;
 };
 
-static int dns_reg_state_destructor(struct dns_reg_state *dns_state)
+static void dns_register_smbd_retry(struct tevent_context *ctx,
+				    struct tevent_timer *te,
+				    struct timeval now,
+				    void *private_data);
+static void dns_register_smbd_fde_handler(struct tevent_context *ev,
+					  struct tevent_fd *fde,
+					  uint16_t flags,
+					  void *private_data);
+
+
+static int reg_state_destructor(struct reg_state *state)
 {
-	if (dns_state->srv_ref != NULL) {
+	if (state == NULL) {
+		return -1;
+	}
+
+	if (state->srv_ref != NULL) {
 		/* Close connection to the mDNS daemon */
-		DNSServiceRefDeallocate(dns_state->srv_ref);
-		dns_state->srv_ref = NULL;
+		DNSServiceRefDeallocate(state->srv_ref);
+		state->srv_ref = NULL;
 	}
 
 	/* Clear event handler */
-	TALLOC_FREE(dns_state->te);
-	TALLOC_FREE(dns_state->fde);
-	dns_state->fd = -1;
+	TALLOC_FREE(state->te);
+	TALLOC_FREE(state->fde);
+	state->fd = -1;
 
 	return 0;
 }
 
-static void dns_register_smbd_retry(struct tevent_context *ctx,
-				    struct tevent_timer *te,
-				    struct timeval now,
-				    void *private_data);
-static void dns_register_smbd_fde_handler(struct tevent_context *ev,
-					  struct tevent_fd *fde,
-					  uint16_t flags,
-					  void *private_data);
+int TXTRecordPrintf(TXTRecordRef * rec, const char * key, const char * fmt, ... )
+{
+	int ret = 0;
+	char *str;
+	va_list ap;
+	va_start( ap, fmt );
 
-static bool dns_register_smbd_schedule(struct dns_reg_state *dns_state,
+	if( 0 > vasprintf(&str, fmt, ap ) ) {
+		va_end(ap);
+		return -1;
+	}
+	va_end(ap);
+
+	if( kDNSServiceErr_NoError != TXTRecordSetValue(rec, key, strlen(str), str) ) {
+		ret = -1;
+	}
+
+	free(str);
+	return ret;
+}
+
+int TXTRecordKeyPrintf(TXTRecordRef * rec, const char * key_fmt, int key_var, const char * fmt, ...)
+{
+	int ret = 0;
+	char *key = NULL, *str = NULL;
+	va_list ap;
+
+	if( 0 > asprintf(&key, key_fmt, key_var)) {
+		DEBUG(1, ("Failed in asprintf\n"));
+		return -1; 
+	}
+
+	va_start( ap, fmt );
+	if( 0 > vasprintf(&str, fmt, ap )) {
+		va_end(ap);
+		DEBUG(1, ("Failed in vasprintf\n"));
+		ret = -1;
+		goto exit;
+	}
+	va_end(ap);
+
+	if( kDNSServiceErr_NoError != TXTRecordSetValue(rec, key, strlen(str), str) ) {
+		DEBUG(1, ("Failed in TXTRecordSetValuen"));
+		ret = -1;
+		goto exit;
+	}
+
+	exit:
+	if (str)
+		free(str);
+	if (key)
+		free(key);
+	return ret;
+}
+
+
+static bool dns_register_smbd_schedule(struct reg_state *state,
 				       struct timeval tval)
 {
-	dns_reg_state_destructor(dns_state);
+	reg_state_destructor(state);
 
-	dns_state->te = tevent_add_timer(dns_state->event_ctx,
-					 dns_state,
+	state->te = tevent_add_timer(state->event_ctx,
+					 state->mem_ctx,
 					 tval,
 					 dns_register_smbd_retry,
-					 dns_state);
-	if (!dns_state->te) {
+					 state);
+	if (!state->te) {
 		return false;
 	}
 
 	return true;
 }
 
+static void dns_register_smbd_callback(DNSServiceRef service,
+				       DNSServiceFlags flags,
+				       DNSServiceErrorType errorCode,
+				       const char *name,
+				       const char *type,
+				       const char *domain,
+				       void *context)
+{
+	if (errorCode != kDNSServiceErr_NoError) {
+		DEBUG(6, ("error=%d\n", errorCode));
+	} else {
+		DEBUG(6, ("%-15s %s.%s%s\n", "REGISTER", name, type, domain));
+	}
+}
+
 static void dns_register_smbd_retry(struct tevent_context *ctx,
 				    struct tevent_timer *te,
 				    struct timeval now,
 				    void *private_data)
 {
-	struct dns_reg_state *dns_state = talloc_get_type_abort(private_data,
-					  struct dns_reg_state);
+	struct reg_state *state = (struct reg_state *)private_data;
 	DNSServiceErrorType err;
+	int snum;
+	size_t dk = 0;
+	bool sys_txt_created = false;
+	TXTRecordRef txt_adisk;
+	TXTRecordRef txt_devinfo;
+	char *servname;
+	char *v_uuid;
+	int num_services = lp_numservices();
 
-	dns_reg_state_destructor(dns_state);
+	reg_state_destructor(state);
 
-	DEBUG(6, ("registering _smb._tcp service on port %d\n",
-		  dns_state->port));
+	TXTRecordCreate(&txt_adisk, 0, NULL);
+
+	DEBUG(6, ("registering _smb._tcp service on port %d index %d\n",
+		  state->port, state->if_index));
 
 	/* Register service with DNS. Connects with the mDNS
 	 * daemon running on the local system to perform DNS
 	 * service registration.
 	 */
-	err = DNSServiceRegister(&dns_state->srv_ref, 0 /* flags */,
-			kDNSServiceInterfaceIndexAny,
-			NULL /* service name */,
-			"_smb._tcp" /* service type */,
-			NULL /* domain */,
-			"" /* SRV target host name */,
-			htons(dns_state->port),
-			0 /* TXT record len */,
-			NULL /* TXT record data */,
-			NULL /* callback func */,
-			NULL /* callback context */);
+	err = DNSServiceRegister(&state->srv_ref,
+			0		/* flags */,
+			state->if_index /* interface index */,
+			NULL		/* service name */,
+			"_smb._tcp"	/* service type */,
+			NULL		/* domain */,
+			""		/* SRV target host name */,
+			htons(state->port) /* port */,
+			0		/* TXT record len */,
+			NULL		/* TXT record data */,
+			dns_register_smbd_callback /* callback func */,
+			NULL		/* callback context */);
+
 
 	if (err != kDNSServiceErr_NoError) {
 		/* Failed to register service. Schedule a re-try attempt.
@@ -123,24 +238,96 @@ static void dns_register_smbd_retry(stru
 		goto retry;
 	}
 
-	dns_state->fd = DNSServiceRefSockFD(dns_state->srv_ref);
-	if (dns_state->fd == -1) {
+	/*
+	 * Check for services that are configured as Time Machine targets
+	 *
+	 */
+	for (snum = 0; snum < num_services; snum++) {
+		if (lp_snum_ok(snum) && lp_parm_bool(snum, "fruit", "time machine", false))
+		{
+			if (!sys_txt_created) {
+				if( 0 > TXTRecordPrintf(&txt_adisk, "sys", "adVF=0x100") ) {
+					DEBUG(1, ("Failed to create Zeroconf TXTRecord for sys") );
+					goto retry;
+				}
+				else
+				{
+					sys_txt_created = true;
+				}
+			}
+
+			v_uuid = lp_parm_const_string(snum, "fruit", "volume_uuid", NULL);
+			servname = lp_const_servicename(snum);
+			DEBUG(1, ("Registering volume %s for TimeMachine\n", servname));
+			if (v_uuid) {
+				if( 0 > TXTRecordKeyPrintf(&txt_adisk, "dk%zu", dk++, "adVN=%s,adVF=0x82,adVU=%s",
+					servname, v_uuid) ) {
+					DEBUG(1, ("Could not set Zeroconf TXTRecord for dk%zu \n", dk));
+					goto retry;
+				} 
+				DEBUG(1, ("Registering TimeMachine with the following TXT parameters: "
+					  "dk%zu,adVN=%s,adVF=0x82,adVU=%s\n", dk, servname, v_uuid) );
+			}
+			else {
+				if( 0 > TXTRecordKeyPrintf(&txt_adisk, "dk%zu", dk++, "adVN=%s,adVF=0x82",
+					servname) ) {
+					DEBUG(1, ("Could not set Zeroconf TXTRecord for dk%zu \n", dk));
+					goto retry;
+				}
+				DEBUG(1, ("Registering TimeMachine with the following TXT parameters: "
+					  "dk%zu,adVN=%s,adVF=0x82\n", dk, servname) );
+			}
+		}
+	}
+
+	if (dk) {
+		err = DNSServiceRegister(&state->srv_ref,
+				0		/* flags */,
+				state->if_index /* interface index */,
+				NULL		/* service name */,
+				"_adisk._tcp"	/* service type */,
+				NULL		/* domain */,
+				""		/* SRV target host name */,
+				/*
+				 * We would probably use port 0 zero, but we can't, from man DNSServiceRegister:
+				 *   "A value of 0 for a port is passed to register placeholder services.
+				 *    Place holder services are not found  when browsing, but other
+				 *    clients cannot register with the same name as the placeholder service."
+				 * We therefor use port 9 which is used by the adisk service type.
+				 */
+				htons(9)	/* port */,
+				TXTRecordGetLength(&txt_adisk)		/* TXT record len */,
+				TXTRecordGetBytesPtr(&txt_adisk)	/* TXT record data */,
+				dns_register_smbd_callback /* callback func */,
+				NULL		/* callback context */);
+
+
+		if (err != kDNSServiceErr_NoError) {
+			/* Failed to register service. Schedule a re-try attempt.
+			 */
+			DEBUG(1, ("unable to register with mDNS (err %d)\n", err));
+			goto retry;
+		}
+	}
+
+	state->fd = DNSServiceRefSockFD(state->srv_ref);
+	if (state->fd == -1) {
 		goto retry;
 	}
 
-	dns_state->fde = tevent_add_fd(dns_state->event_ctx,
-				       dns_state,
-				       dns_state->fd,
-				       TEVENT_FD_READ,
-				       dns_register_smbd_fde_handler,
-				       dns_state);
-	if (!dns_state->fde) {
+	state->fde = tevent_add_fd(state->event_ctx,
+				   state->mem_ctx,
+				   state->fd,
+				   TEVENT_FD_READ,
+				   dns_register_smbd_fde_handler,
+				   state);
+	if (!state->fde) {
 		goto retry;
 	}
 
 	return;
  retry:
-	dns_register_smbd_schedule(dns_state,
+	dns_register_smbd_schedule(state,
 		timeval_current_ofs(DNS_REG_RETRY_INTERVAL, 0));
 }
 
@@ -150,44 +337,77 @@ static void dns_register_smbd_fde_handle
 					  uint16_t flags,
 					  void *private_data)
 {
-	struct dns_reg_state *dns_state = talloc_get_type_abort(private_data,
-					  struct dns_reg_state);
+	struct reg_state *state = (struct reg_state *)private_data;
 	DNSServiceErrorType err;
 
-	err = DNSServiceProcessResult(dns_state->srv_ref);
+	err = DNSServiceProcessResult(state->srv_ref);
 	if (err != kDNSServiceErr_NoError) {
-		DEBUG(3, ("failed to process mDNS result (err %d), re-trying\n",
-			    err));
+		DEBUG(3, ("failed to process mDNS result (err %d), re-trying\n", err));
 		goto retry;
 	}
 
-	talloc_free(dns_state);
 	return;
 
  retry:
-	dns_register_smbd_schedule(dns_state,
-		timeval_current_ofs(DNS_REG_RETRY_INTERVAL, 0));
+	dns_register_smbd_schedule(state, timeval_zero());
+}
+
+static int dns_reg_state_destructor(struct dns_reg_state *state)
+{
+	if (state != NULL) {
+		talloc_free(state);
+	}
+	return 0;
 }
 
+
 bool smbd_setup_mdns_registration(struct tevent_context *ev,
 				  TALLOC_CTX *mem_ctx,
 				  uint16_t port)
 {
 	struct dns_reg_state *dns_state;
+	bool bind_all = true;
+	int i;
 
 	dns_state = talloc_zero(mem_ctx, struct dns_reg_state);
-	if (dns_state == NULL) {
+	if (dns_state == NULL)
+		return false;
+
+	if (lp_interfaces() && lp_bind_interfaces_only())
+		bind_all = false;
+
+	dns_state->count = iface_count();
+	if (dns_state->count <= 0 || bind_all == true)
+		dns_state->count = 1;
+
+	dns_state->drs = talloc_array(mem_ctx, struct reg_state, dns_state->count);
+	if (dns_state->drs == NULL) {
+		talloc_free(dns_state);
 		return false;
 	}
-	dns_state->event_ctx = ev;
-	dns_state->port = port;
-	dns_state->fd = -1;
 
-	talloc_set_destructor(dns_state, dns_reg_state_destructor);
+	for (i = 0; i < dns_state->count; i++) {
+		struct interface *iface = get_interface(i);
+		struct reg_state *state = &dns_state->drs[i];
 
-	return dns_register_smbd_schedule(dns_state, timeval_zero());
+		state->mem_ctx = mem_ctx;
+		state->srv_ref = NULL;
+		state->event_ctx = ev;
+		state->te = NULL;
+		state->fde = NULL;
+		state->port = port;
+		state->fd = -1;
+
+		state->if_index = bind_all ? kDNSServiceInterfaceIndexAny : iface->if_index;
+
+		dns_register_smbd_schedule(&dns_state->drs[i], timeval_zero());
+	}
+	
+	talloc_set_destructor(dns_state, dns_reg_state_destructor);
+	return true;
 }
 
+
 #else /* WITH_DNSSD_SUPPORT */
 
 bool smbd_setup_mdns_registration(struct tevent_context *ev,
