/*
   Unix SMB/CIFS implementation.
   DNS-SD browse client
   Copyright (C) Rishi Srivatsavai 2007

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"
#include "client/client_proto.h"

#ifdef WITH_DNSSD_SUPPORT

#include <dns_sd.h>

/* Holds service instances found during DNS browse */
struct mdns_smbsrv_result
{
	char *serviceName;
	char *regType;
	char *domain;
	uint32_t ifIndex;
	struct mdns_smbsrv_result *nextResult;
};

/* Maintains state during DNS browse */
struct mdns_browse_state
{
	struct mdns_smbsrv_result *listhead; /* Browse result list head */
	int browseDone;

};


static void
do_smb_resolve_reply (DNSServiceRef sdRef, DNSServiceFlags flags,
		uint32_t interfaceIndex, DNSServiceErrorType errorCode,
		const char *fullname, const char *hosttarget, uint16_t port,
		uint16_t txtLen, const unsigned char *txtRecord, void *context)
{
	printf("SMB service available on %s port %u\n",
		hosttarget, ntohs(port));
}


static void do_smb_resolve(struct mdns_smbsrv_result *browsesrv)
{
	DNSServiceRef mdns_conn_sdref = NULL;
	int mdnsfd;
	int fdsetsz;
	int ret;
	fd_set *fdset = NULL;
	struct timeval tv;
	DNSServiceErrorType err;

	TALLOC_CTX * ctx = talloc_tos();

	err = DNSServiceResolve(&mdns_conn_sdref, 0 /* flags */,
		browsesrv->ifIndex,
		browsesrv->serviceName, browsesrv->regType, browsesrv->domain,
		do_smb_resolve_reply, NULL);

	if (err != kDNSServiceErr_NoError) {
		return;
	}

	mdnsfd = DNSServiceRefSockFD(mdns_conn_sdref);
	for (;;)  {
		if (fdset != NULL) {
			TALLOC_FREE(fdset);
		}

		fdsetsz = howmany(mdnsfd + 1, NFDBITS) * sizeof(fd_mask);
		fdset = TALLOC_ZERO(ctx, fdsetsz);
		FD_SET(mdnsfd, fdset);

		tv.tv_sec = 1;
		tv.tv_usec = 0;

		/* Wait until response received from mDNS daemon */
		ret = sys_select(mdnsfd + 1, fdset, NULL, NULL, &tv);
		if (ret <= 0 && errno != EINTR) {
			break;
		}

		if (FD_ISSET(mdnsfd, fdset)) {
			/* Invoke callback function */
			DNSServiceProcessResult(mdns_conn_sdref);
			break;
		}
	}

	TALLOC_FREE(fdset);
	DNSServiceRefDeallocate(mdns_conn_sdref);
}


static void
do_smb_browse_reply(DNSServiceRef sdRef, DNSServiceFlags flags,
        uint32_t interfaceIndex, DNSServiceErrorType errorCode,
        const char  *serviceName, const char *regtype,
        const char  *replyDomain, void  *context)
{
	struct mdns_browse_state *bstatep = (struct mdns_browse_state *)context;
	struct mdns_smbsrv_result *bresult;

	if (bstatep == NULL) {
		return;
	}

	if (errorCode != kDNSServiceErr_NoError) {
		bstatep->browseDone = 1;
		return;
	}

	if (flags & kDNSServiceFlagsMoreComing) {
		bstatep->browseDone = 0;
	} else {
		bstatep->browseDone = 1;
	}

	if (!(flags & kDNSServiceFlagsAdd)) {
		return;
	}

	bresult = TALLOC_ARRAY(talloc_tos(), struct mdns_smbsrv_result, 1);
	if (bresult == NULL) {
		return;
	}

	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->ifIndex = interfaceIndex;
	bstatep->listhead = bresult;
}

int do_smb_browse(void)
{
	int mdnsfd;
	int fdsetsz;
	int ret;
	fd_set *fdset = NULL;
	struct mdns_browse_state bstate;
	struct mdns_smbsrv_result *resptr;
	struct timeval tv;
	DNSServiceRef mdns_conn_sdref = NULL;
	DNSServiceErrorType err;

	TALLOC_CTX * ctx = talloc_stackframe();

	ZERO_STRUCT(bstate);

	err = DNSServiceBrowse(&mdns_conn_sdref, 0, 0, "_smb._tcp", "",
			do_smb_browse_reply, &bstate);

	if (err != kDNSServiceErr_NoError) {
		d_printf("Error connecting to the Multicast DNS daemon\n");
		TALLOC_FREE(ctx);
		return 1;
	}

	mdnsfd = DNSServiceRefSockFD(mdns_conn_sdref);
	for (;;)  {
		if (fdset != NULL) {
			TALLOC_FREE(fdset);
		}

		fdsetsz = howmany(mdnsfd + 1, NFDBITS) * sizeof(fd_mask);
		fdset = TALLOC_ZERO(ctx, fdsetsz);
		FD_SET(mdnsfd, fdset);

		tv.tv_sec = 1;
		tv.tv_usec = 0;

		/* Wait until response received from mDNS daemon */
		ret = sys_select(mdnsfd + 1, fdset, NULL, NULL, &tv);
		if (ret <= 0 && errno != EINTR) {
			break;
		}

		if (FD_ISSET(mdnsfd, fdset)) {
			/* Invoke callback function */
			if (DNSServiceProcessResult(mdns_conn_sdref)) {
				break;
			}
			if (bstate.browseDone) {
				break;
			}
		}
	}

	DNSServiceRefDeallocate(mdns_conn_sdref);

	if (bstate.listhead != NULL) {
		resptr = bstate.listhead;
		while (resptr != NULL) {
			struct mdns_smbsrv_result *oldresptr;
			oldresptr = resptr;

			/* Resolve smb service instance */
			do_smb_resolve(resptr);

			resptr = resptr->nextResult;
		}
	}

	TALLOC_FREE(ctx);
	return 0;
}

#else /* WITH_DNSSD_SUPPORT */

int do_smb_browse(void)
{
    d_printf("DNS-SD browsing is not supported on this platform\n");
    return 1;
}

#endif /* WITH_DNSSD_SUPPORT */