changeset 8486:538f4d0faf1d

[gaim-migrate @ 9221] Don't use this, it's doesn't work, it's buggy, and it has security problems. I just don't want to pull a Sean Egan and delete everthing I have so far. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Tue, 23 Mar 2004 03:39:06 +0000
parents db4a125edd7b
children c3ffec7fab94
files src/protocols/napster/Makefile.am src/protocols/napster/Makefile.mingw src/protocols/napster/mdns.c src/protocols/napster/mdns.h src/protocols/napster/rendezvous.c
diffstat 5 files changed, 1236 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/napster/Makefile.am	Mon Mar 22 06:00:12 2004 +0000
+++ b/src/protocols/napster/Makefile.am	Tue Mar 23 03:39:06 2004 +0000
@@ -1,26 +1,32 @@
+EXTRA_DIST = \
+		Makefile.mingw
+
 pkgdir = $(libdir)/gaim
 
-NAPSTERSOURCES = napster.c
+RENDEZVOUSSOURCES = \
+	mdns.c \
+	rendezvous.c
 
 AM_CFLAGS = $(st)
 
-libnapster_la_LDFLAGS = -module -avoid-version
+librendezvous_la_LDFLAGS = -module -avoid-version
 
-if STATIC_NAPSTER
+if STATIC_RENDEZVOUS
 
 st = -DGAIM_STATIC_PRPL
-noinst_LIBRARIES     = libnapster.a
-libnapster_a_SOURCES = $(NAPSTERSOURCES)
-libnapster_a_CFLAGS  = $(AM_CFLAGS)
+noinst_LIBRARIES     = librendezvous.a
+librendezvous_a_SOURCES = $(RENDEZVOUSSOURCES)
+librendezvous_a_CFLAGS  = $(AM_CFLAGS)
 
 else
 
 st =
-pkg_LTLIBRARIES       = libnapster.la
-libnapster_la_SOURCES = $(NAPSTERSOURCES)
+pkg_LTLIBRARIES       = librendezvous.la
+librendezvous_la_SOURCES = $(RENDEZVOUSSOURCES)
 
 endif
 
+
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src \
 	$(GLIB_CFLAGS) \
--- a/src/protocols/napster/Makefile.mingw	Mon Mar 22 06:00:12 2004 +0000
+++ b/src/protocols/napster/Makefile.mingw	Tue Mar 23 03:39:06 2004 +0000
@@ -1,7 +1,7 @@
 #
 # Makefile.mingw
 #
-# Description: Makefile for win32 (mingw) version of libnapster
+# Description: Makefile for win32 (mingw) version of librendezvous
 #
 
 #
@@ -11,14 +11,14 @@
 INCLUDE_DIR :=		.
 GTK_TOP :=		../../../../win32-dev/gtk_2_0
 GAIM_TOP :=		../../..
-NAPSTER_ROOT :=		.
+RENDEZVOUS_ROOT :=		.
 GAIM_INSTALL_DIR :=	$(GAIM_TOP)/win32-install-dir
 
 ##
 ## VARIABLE DEFINITIONS
 ##
 
-TARGET = libnapster
+TARGET = librendezvous
 
 # Compiler Options
 
@@ -47,7 +47,7 @@
 ## INCLUDE PATHS
 ##
 
-INCLUDE_PATHS +=	-I$(NAPSTER_ROOT) \
+INCLUDE_PATHS +=	-I$(RENDEZVOUS_ROOT) \
 			-I$(GTK_TOP)/include \
 			-I$(GTK_TOP)/include/gtk-2.0 \
 			-I$(GTK_TOP)/include/glib-2.0 \
@@ -68,7 +68,8 @@
 ##  SOURCES, OBJECTS
 ##
 
-C_SRC =			napster.c
+C_SRC =			mdns.c \
+				rendezvous.c
 
 
 OBJECTS = $(C_SRC:%.c=%.o)
@@ -106,7 +107,7 @@
 all: $(TARGET).dll
 
 install:
-	cp $(NAPSTER_ROOT)/$(TARGET).dll $(DLL_INSTALL_DIR)
+	cp $(RENDEZVOUS_ROOT)/$(TARGET).dll $(DLL_INSTALL_DIR)
 
 
 ##
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/napster/mdns.c	Tue Mar 23 03:39:06 2004 +0000
@@ -0,0 +1,541 @@
+/**
+ * @file mdns.c Multicast DNS connection code used by rendezvous.
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+/*
+ * If you want to understand this, read RFC1035 and 
+ * draft-cheshire-dnsext-multicastdns.txt
+ */
+
+/*
+ * XXX - THIS DOESN'T DO BOUNDS CHECKING!!!  DON'T USE IT ON AN UNTRUSTED
+ * NETWORK UNTIL IT DOES!!!  THERE ARE POSSIBLE REMOTE ACCESS VIA BUFFER
+ * OVERFLOW SECURITY HOLES!!!
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <glib.h>
+
+#include "debug.h"
+
+#include "mdns.h"
+#include "util.h"
+
+int
+mdns_establish_socket()
+{
+	int fd = -1;
+	struct sockaddr_in addr;
+	struct ip_mreq mreq;
+	unsigned char loop;
+	unsigned char ttl;
+	int reuseaddr;
+
+	gaim_debug_info("mdns", "Establishing multicast socket\n");
+
+	/* What's the difference between AF_INET and PF_INET? */
+	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+		gaim_debug_error("mdns", "Unable to create socket: %s\n", strerror(errno));
+		return -1;
+	}
+
+	/* Make the socket non-blocking (although it shouldn't matter) */
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+
+	/* Bind the socket to a local IP and port */
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(5353);
+	addr.sin_addr.s_addr = INADDR_ANY;
+	if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
+		gaim_debug_error("mdns", "Unable to bind socket to interface.\n");
+		close(fd);
+		return -1;
+	}
+
+	/* Ensure loopback is enabled (it should be enabled by default, by let's be sure) */
+	loop = 1;
+	if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(unsigned char)) == -1) {
+		gaim_debug_error("mdns", "Error calling setsockopt for IP_MULTICAST_LOOP\n");
+	}
+
+	/* Set TTL to 255--required by mDNS */
+	ttl = 255;
+	if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(unsigned char)) == -1) {
+		gaim_debug_error("mdns", "Error calling setsockopt for IP_MULTICAST_TTL\n");
+		close(fd);
+		return -1;
+	}
+
+	/* Join the .local multicast group */
+	mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.251");
+	mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+	if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)) == -1) {
+		gaim_debug_error("mdns", "Error calling setsockopt for IP_ADD_MEMBERSHIP\n");
+		close(fd);
+		return -1;
+	}
+
+	/* Make the local IP re-usable */
+	reuseaddr = 1;
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1) {
+		gaim_debug_error("mdns", "Error calling setsockopt for SO_REUSEADDR: %s\n", strerror(errno));
+	}
+
+	return fd;
+}
+
+int
+mdns_query(int fd, const char *domain)
+{
+	struct sockaddr_in addr;
+	unsigned int querylen;
+	unsigned char *query;
+	char *b, *c;
+	int i, n;
+
+	if (strlen(domain) > 255) {
+		return -EINVAL;
+	}
+
+	/*
+	 * Build the outgoing query packet.  It is made of the header with a
+	 * query made up of the given domain.  The header is 12 bytes.
+	 */
+	querylen = 12 + strlen(domain) + 2 + 4;
+	if (!(query = (unsigned char *)g_malloc(querylen))) {
+		return -ENOMEM;
+	}
+
+	/* The header section */
+	util_put32(&query[0], 0); /* The first 32 bits of the header are all 0's in mDNS */
+	util_put16(&query[4], 1); /* QDCOUNT */
+	util_put16(&query[6], 0); /* ANCOUNT */
+	util_put16(&query[8], 0); /* NSCOUNT */
+	util_put16(&query[10], 0); /* ARCOUNT */
+
+	/* The question section */
+	i = 12; /* Destination in query */
+	b = (char *)domain;
+	while ((c = strchr(b, '.'))) {
+		i += util_put8(&query[i], c - b); /* Length of domain-name segment */
+		memcpy(&query[i], b, c - b); /* Domain-name segment */
+		i += c - b; /* Increment the destination pointer */
+		b = c + 1;
+	}
+	i += util_put8(&query[i], strlen(b)); /* Length of domain-name segment */
+	strcpy(&query[i], b); /* Domain-name segment */
+	i += strlen(b) + 1; /* Increment the destination pointer */
+	i += util_put16(&query[i], 0x000c); /* QTYPE */
+	i += util_put16(&query[i], 0x8001); /* QCLASS */
+
+	/* Actually send the DNS query */
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(5353);
+	addr.sin_addr.s_addr = inet_addr("224.0.0.251");
+	n = sendto(fd, query, querylen, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
+	g_free(query);
+
+	if (n == -1) {
+		gaim_debug_error("mdns", "Error sending packet: %d\n", errno);
+		return -1;
+	} else if (n != querylen) {
+		gaim_debug_error("mdns", "Only sent %d of %d bytes of query.\n", n, querylen);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * XXX - Needs bounds checking!
+ *
+ * Read in a domain name from the given buffer starting at the given
+ * offset.  This handles using domain name compression to jump around
+ * the data buffer, if needed.
+ *
+ * @return A null-terminated string representation of the domain name.
+ *         This should be g_free'd when no longer needed.
+ */
+static gchar *
+mdns_read_name(const char *data, int datalen, int dataoffset)
+{
+	GString *ret = g_string_new("");
+	unsigned char tmp;
+
+	while ((tmp = util_get8(&data[dataoffset])) != 0) {
+		dataoffset++;
+
+		if ((tmp & 0xc0) == 0) { /* First two bits are 00 */
+			if (*ret->str)
+				g_string_append_c(ret, '.');
+			g_string_append_len(ret, &data[dataoffset], tmp);
+			dataoffset += tmp;
+
+		} else if ((tmp & 0x40) == 0) { /* First two bits are 10 */
+			/* Reserved for future use */
+
+		} else if ((tmp & 0x80) == 1) { /* First two bits are 01 */
+			/* Reserved for future use */
+
+		} else { /* First two bits are 11 */
+			/* Jump to another position in the data */
+			dataoffset = util_get8(&data[dataoffset]);
+
+		}
+	}
+
+	return g_string_free(ret, FALSE);
+}
+
+/*
+ * XXX - Needs bounds checking!
+ *
+ * Determine how many bytes long a portion of the domain name is
+ * at the given offset.  This does NOT jump around the data array
+ * in the case of domain name compression.
+ *
+ * @return The length of the portion of the domain name.
+ */
+static int
+mdns_read_name_len(const char *data, int datalen, int dataoffset)
+{
+	int startoffset = dataoffset;
+	unsigned char tmp;
+
+	while ((tmp = util_get8(&data[dataoffset++])) != 0) {
+
+		if ((tmp & 0xc0) == 0) { /* First two bits are 00 */
+			dataoffset += tmp;
+
+		} else if ((tmp & 0x40) == 0) { /* First two bits are 10 */
+			/* Reserved for future use */
+
+		} else if ((tmp & 0x80) == 1) { /* First two bits are 01 */
+			/* Reserved for future use */
+
+		} else { /* First two bits are 11 */
+			/* End of this portion of the domain name */
+			dataoffset++;
+			break;
+
+		}
+	}
+
+	return dataoffset - startoffset;
+}
+
+/*
+ * XXX - Needs bounds checking!
+ *
+ */
+static Question *
+mdns_read_questions(int numquestions, const char *data, int datalen, int *offset)
+{
+	Question *ret;
+	int i;
+
+	ret = (Question *)g_malloc0(numquestions * sizeof(Question));
+	for (i = 0; i < numquestions; i++) {
+		ret[i].name = mdns_read_name(data, 0, *offset);
+		*offset += mdns_read_name_len(data, 0, *offset);
+		ret[i].type = util_get16(&data[*offset]); /* QTYPE */
+		*offset += 2;
+		ret[i].class = util_get16(&data[*offset]); /* QCLASS */
+		*offset += 2;
+	}
+
+	return ret;
+}
+
+/*
+ * Read in a chunk of data, probably a buddy icon.
+ *
+ */
+static unsigned char *
+mdns_read_rr_rdata_null(const char *data, int datalen, int offset, unsigned short rdlength)
+{
+	unsigned char *ret = NULL;
+
+	if (offset + rdlength > datalen)
+		return NULL;
+
+	ret = (unsigned char *)g_malloc(rdlength);
+	memcpy(ret, &data[offset], rdlength);
+
+	return ret;
+}
+
+/*
+ * XXX - Needs bounds checking!
+ *
+ */
+static char *
+mdns_read_rr_rdata_ptr(const char *data, int datalen, int offset)
+{
+	char *ret = NULL;
+
+	ret = mdns_read_name(data, datalen, offset);
+
+	return ret;
+}
+
+/*
+ *
+ *
+ */
+static GHashTable *
+mdns_read_rr_rdata_txt(const char *data, int datalen, int offset, unsigned short rdlength)
+{
+	GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	int endoffset = offset + rdlength;
+	unsigned char tmp;
+	char buf[256], *key, *value;
+
+	while (offset < endoffset) {
+		/* Read in the length of the next name/value pair */
+		tmp = util_get8(&data[offset]);
+		offset++;
+
+		/* Ensure packet is valid */
+		if (offset + tmp > endoffset)
+			break;
+
+		/* Read in the next name/value pair */
+		strncpy(buf, &data[offset], tmp);
+		offset += tmp;
+
+		if (buf[0] == '=') {
+			/* Name/value pairs beginning with = are silently ignored */
+			continue;
+		}
+
+		/* The value is a substring of buf, starting just after the = */
+		buf[tmp] = '\0';
+		value = strchr(buf, '=');
+		if (value != NULL) {
+			value[0] = '\0';
+			value++;
+		}
+
+		/* Make the key all lowercase */
+		key = g_utf8_strdown(buf, -1);
+		if (!g_hash_table_lookup(ret, key))
+			g_hash_table_insert(ret, key, g_strdup(value));
+		else
+			g_free(key);
+	}
+
+	return ret;
+}
+
+/*
+ * XXX - Needs bounds checking!
+ *
+ */
+static ResourceRecord *
+mdns_read_rr(int numrecords, const char *data, int datalen, int *offset)
+{
+	ResourceRecord *ret;
+	int i;
+
+	ret = (ResourceRecord *)g_malloc0(numrecords * sizeof(ResourceRecord));
+	for (i = 0; i < numrecords; i++) {
+		ret[i].name = mdns_read_name(data, 0, *offset); /* NAME */
+		*offset += mdns_read_name_len(data, 0, *offset);
+		ret[i].type = util_get16(&data[*offset]); /* TYPE */
+		*offset += 2;
+		ret[i].class = util_get16(&data[*offset]); /* CLASS */
+		*offset += 2;
+		ret[i].ttl = util_get32(&data[*offset]); /* TTL */
+		*offset += 4;
+		ret[i].rdlength = util_get16(&data[*offset]); /* RDLENGTH */
+		*offset += 2;
+
+		/* RDATA */
+		switch (ret[i].type) {
+			case RENDEZVOUS_RRTYPE_NULL:
+				ret[i].rdata = mdns_read_rr_rdata_null(data, datalen, *offset, ret[i].rdlength);
+			break;
+
+			case RENDEZVOUS_RRTYPE_PTR:
+				ret[i].rdata = mdns_read_rr_rdata_ptr(data, datalen, *offset);
+			break;
+
+			case RENDEZVOUS_RRTYPE_TXT:
+				ret[i].rdata = mdns_read_rr_rdata_txt(data, datalen, *offset, ret[i].rdlength);
+			break;
+
+			default:
+				ret[i].rdata = NULL;
+			break;
+		}
+		*offset += ret[i].rdlength;
+	}
+
+	return ret;
+}
+
+/*
+ * XXX - Needs bounds checking!
+ *
+ */
+DNSPacket *
+mdns_read(int fd)
+{
+	DNSPacket *ret = NULL;
+	int i; /* Current position in datagram */
+	//char data[512];
+	char data[10096];
+	int datalen;
+	struct sockaddr_in addr;
+	socklen_t addrlen;
+
+	/* Read in an mDNS packet */
+	addrlen = sizeof(struct sockaddr_in);
+	if ((datalen = recvfrom(fd, data, sizeof(data), 0, (struct sockaddr *)&addr, &addrlen)) == -1) {
+		gaim_debug_error("mdns", "Error reading packet: %d\n", errno);
+		return NULL;
+	}
+
+	ret = (DNSPacket *)g_malloc0(sizeof(DNSPacket));
+
+	/* Parse the incoming packet, starting from 0 */
+	i = 0;
+
+	/* The header section */
+	ret->header.id = util_get16(&data[i]); /* ID */
+	i += 2;
+
+	/* For the flags, some bits must be 0 and some must be 1, the rest are ignored */
+	ret->header.flags = util_get16(&data[i]); /* Flags (QR, OPCODE, AA, TC, RD, RA, Z, AD, CD, and RCODE */
+	i += 2;
+	if ((ret->header.flags & 0x8000) == 0) {
+		/* QR should be 1 */
+		g_free(ret);
+		return NULL;
+	}
+	if ((ret->header.flags & 0x7800) != 0) {
+		/* OPCODE should be all 0's */
+		g_free(ret);
+		return NULL;
+	}
+
+	/* Read in the number of other things in the packet */
+	ret->header.numquestions = util_get16(&data[i]);
+	i += 2;
+	ret->header.numanswers = util_get16(&data[i]);
+	i += 2;
+	ret->header.numauthority = util_get16(&data[i]);
+	i += 2;
+	ret->header.numadditional = util_get16(&data[i]);
+	i += 2;
+
+	/* Read in all the questions */
+	ret->questions = mdns_read_questions(ret->header.numquestions, data, datalen, &i);
+
+	/* Read in all resource records */
+	ret->answers = mdns_read_rr(ret->header.numanswers, data, datalen, &i);
+
+	/* Read in all authority records */
+	ret->authority = mdns_read_rr(ret->header.numauthority, data, datalen, &i);
+
+	/* Read in all additional records */
+	ret->additional = mdns_read_rr(ret->header.numadditional, data, datalen, &i);
+
+	/* We should be at the end of the packet */
+	if (i != datalen) {
+		gaim_debug_error("mdns", "Finished parsing before end of DNS packet!  Only parsed %d of %d bytes.", i, datalen);
+		g_free(ret);
+		return NULL;
+	}
+
+	return ret;
+}
+
+/**
+ * Free the rdata associated with a given resource record.
+ */
+static void
+mdns_free_rr_rdata(unsigned short type, void *rdata)
+{
+	switch (type) {
+			case RENDEZVOUS_RRTYPE_NULL:
+			case RENDEZVOUS_RRTYPE_PTR:
+				g_free(rdata);
+			break;
+
+			case RENDEZVOUS_RRTYPE_TXT:
+				g_hash_table_destroy(rdata);
+			break;
+	}
+}
+
+/**
+ * Free a given question
+ */
+static void
+mdns_free_q(Question *q)
+{
+	g_free(q->name);
+}
+
+/**
+ * Free a given resource record.
+ */
+static void
+mdns_free_rr(ResourceRecord *rr)
+{
+	g_free(rr->name);
+	mdns_free_rr_rdata(rr->type, rr->rdata);
+}
+
+void
+mdns_free(DNSPacket *dns)
+{
+	int i;
+
+	for (i = 0; i < dns->header.numquestions; i++)
+		mdns_free_q(&dns->questions[i]);
+	for (i = 0; i < dns->header.numanswers; i++)
+		mdns_free_rr(&dns->answers[i]);
+	for (i = 0; i < dns->header.numauthority; i++)
+		mdns_free_rr(&dns->authority[i]);
+	for (i = 0; i < dns->header.numadditional; i++)
+		mdns_free_rr(&dns->additional[i]);
+
+	g_free(dns->questions);
+	g_free(dns->answers);
+	g_free(dns->authority);
+	g_free(dns->additional);
+	g_free(dns);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/napster/mdns.h	Tue Mar 23 03:39:06 2004 +0000
@@ -0,0 +1,144 @@
+/**
+ * @file mdns.h Multicast DNS connection code used by rendezvous.
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _MDNS_H_
+#define _MDNS_H_
+
+#include <errno.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "debug.h"
+
+/*
+ * Some #define's stolen from libfaim.  Used to put
+ * binary data (bytes, shorts and ints) into an array.
+ */
+#define util_put8(buf, data) ((*(buf) = (unsigned char)(data)&0xff),1)
+#define util_put16(buf, data) ( \
+		(*(buf) = (unsigned char)((data)>>8)&0xff), \
+		(*((buf)+1) = (unsigned char)(data)&0xff),  \
+		2)
+#define util_put32(buf, data) ( \
+		(*((buf)) = (unsigned char)((data)>>24)&0xff), \
+		(*((buf)+1) = (unsigned char)((data)>>16)&0xff), \
+		(*((buf)+2) = (unsigned char)((data)>>8)&0xff), \
+		(*((buf)+3) = (unsigned char)(data)&0xff), \
+		4)
+#define util_get8(buf) ((*(buf))&0xff)
+#define util_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff))
+#define util_get32(buf) ((((*(buf))<<24)&0xff000000) + \
+		(((*((buf)+1))<<16)&0x00ff0000) + \
+		(((*((buf)+2))<< 8)&0x0000ff00) + \
+		(((*((buf)+3)    )&0x000000ff)))
+
+/*
+ * Merriam-Webster's
+ */
+#define RENDEZVOUS_RRTYPE_A		1
+#define RENDEZVOUS_RRTYPE_NS	2
+#define RENDEZVOUS_RRTYPE_CNAME	5
+#define RENDEZVOUS_RRTYPE_NULL	10
+#define RENDEZVOUS_RRTYPE_PTR	12
+#define RENDEZVOUS_RRTYPE_TXT	16
+
+/*
+ * Express for Men's
+ */
+typedef struct _Header {
+	unsigned short id;
+	unsigned short flags;
+	unsigned short numquestions;
+	unsigned short numanswers;
+	unsigned short numauthority;
+	unsigned short numadditional;
+} Header;
+
+typedef struct _Question {
+	gchar *name;
+	unsigned short type;
+	unsigned short class;
+} Question;
+
+typedef struct ResourceRecord {
+	gchar *name;
+	unsigned short type;
+	unsigned short class;
+	int ttl;
+	unsigned short rdlength;
+	void *rdata;
+} ResourceRecord;
+
+typedef struct _DNSPacket {
+	Header header;
+	Question *questions;
+	ResourceRecord *answers;
+	ResourceRecord *authority;
+	ResourceRecord *additional;
+} DNSPacket;
+
+/*
+ * Bring in 'Da Noise, Bring in 'Da Functions
+ */
+
+/**
+ * Create a multicast socket that can be used for sending and 
+ * receiving multicast DNS packets.  The socket joins the
+ * link-local multicast group (224.0.0.251).
+ *
+ * @return The file descriptor of the new socket, or -1 if
+ *         there was an error establishing the socket.
+ */
+int mdns_establish_socket();
+
+/**
+ * Send a multicast DNS query for the given domain across the given
+ * socket.
+ *
+ * @param fd The file descriptor of a pre-established socket to
+ *        be used for sending the outgoing mDNS query.
+ * @param domain This is the domain name you wish to query.  It should 
+ *        be of the format "_presence._tcp.local" for example.
+ * @return 0 if sucessful.
+ */
+int mdns_query(int fd, const char *domain);
+
+/**
+ *
+ *
+ */
+DNSPacket *mdns_read(int fd);
+
+/**
+ * Free a DNSPacket structure.
+ *
+ * @param dns The DNSPacket that you want to free.
+ */
+void mdns_free(DNSPacket *dns);
+
+#endif /* _MDNS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/napster/rendezvous.c	Tue Mar 23 03:39:06 2004 +0000
@@ -0,0 +1,530 @@
+/*
+ * gaim - Rendezvous Protocol Plugin
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "conversation.h"
+#include "debug.h"
+#include "prpl.h"
+
+#include "mdns.h"
+#include "util.h"
+
+#define RENDEZVOUS_CONNECT_STEPS 2
+
+typedef struct _RendezvousData {
+	int fd;
+	GHashTable *buddies;
+} RendezvousData;
+
+typedef struct _RendezvousBuddy {
+	gchar *firstandlast;
+	gchar *aim;
+	int p2pjport;
+	int status;
+	int idle;
+	gchar *msg;
+} RendezvousBuddy;
+
+#define UC_IDLE 2
+
+/****************************/
+/* Utility Functions        */
+/****************************/
+static void rendezvous_buddy_free(gpointer data)
+{
+	RendezvousBuddy *rb = data;
+
+	g_free(rb->firstandlast);
+	g_free(rb->msg);
+	g_free(rb);
+}
+
+/*
+ * Extract the "user@host" name from a full presence domain
+ * of the form "user@host._presence._tcp.local"
+ *
+ * @return If the domain is NOT a _presence._tcp.local domain
+ *         then return NULL.  Otherwise return a newly allocated
+ *         null-terminated string containing the "user@host" for
+ *         the given domain.  This string should be g_free'd
+ *         when no longer needed.
+ */
+static gchar *rendezvous_extract_name(gchar *domain)
+{
+	gchar *ret, *suffix;
+
+	if (!g_str_has_suffix(domain, "._presence._tcp.local"))
+		return NULL;
+
+	suffix = strstr(domain, "._presence._tcp.local");
+	ret = g_strndup(domain, suffix - domain);
+
+	return ret;
+}
+
+/****************************/
+/* Buddy List Functions     */
+/****************************/
+static void rendezvous_addtolocal(GaimConnection *gc, const char *name, const char *domain)
+{
+	GaimAccount *account = gaim_connection_get_account(gc);
+	GaimBuddy *b;
+	GaimGroup *g;
+
+	g = gaim_find_group(domain);
+	if (g == NULL) {
+		g = gaim_group_new(domain);
+		gaim_blist_add_group(g, NULL);
+	}
+
+	b = gaim_find_buddy_in_group(account, name, g);
+	if (b != NULL)
+		return;
+
+	b = gaim_buddy_new(account, name, NULL);
+	gaim_blist_add_buddy(b, NULL, g, NULL);
+	serv_got_update(gc, b->name, 1, 0, 0, 0, 0);
+}
+
+static void rendezvous_removefromlocal(GaimConnection *gc, const char *name, const char *domain)
+{
+	GaimAccount *account = gaim_connection_get_account(gc);
+	GaimBuddy *b;
+	GaimGroup *g;
+
+	g = gaim_find_group(domain);
+	if (g == NULL)
+		return;
+
+	b = gaim_find_buddy_in_group(account, name, g);
+	if (b == NULL)
+		return;
+
+	serv_got_update(gc, b->name, 0, 0, 0, 0, 0);
+	gaim_blist_remove_buddy(b);
+}
+
+static void rendezvous_removeallfromlocal(GaimConnection *gc)
+{
+	GaimAccount *account = gaim_connection_get_account(gc);
+	GaimBuddyList *blist;
+	GaimBlistNode *gnode, *cnode, *bnode;
+	GaimBuddy *b;
+
+	/* Go through and remove all buddies that belong to this account */
+	if ((blist = gaim_get_blist()) != NULL) {
+		for (gnode = blist->root; gnode; gnode = gnode->next) {
+			if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+				continue;
+			for (cnode = gnode->child; cnode; cnode = cnode->next) {
+				if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+					continue;
+				for (bnode = cnode->child; bnode; bnode = bnode->next) {
+					if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+						continue;
+					b = (GaimBuddy *)bnode;
+					if (b->account != account)
+						continue;
+					serv_got_update(gc, b->name, 0, 0, 0, 0, 0);
+					gaim_blist_remove_buddy(b);
+				}
+			}
+		}
+	}
+}
+
+static void rendezvous_handle_rr_txt(GaimConnection *gc, ResourceRecord *rr, const gchar *name)
+{
+	RendezvousData *rd = gc->proto_data;
+	RendezvousBuddy *rb;
+	GHashTable *rdata;
+	gchar *tmp1, *tmp2;
+
+	rb = g_hash_table_lookup(rd->buddies, name);
+	if (rb == NULL) {
+		rb = g_malloc0(sizeof(RendezvousBuddy));
+		g_hash_table_insert(rd->buddies, g_strdup(name), rb);
+	}
+
+	rdata = rr->rdata;
+
+	tmp1 = g_hash_table_lookup(rdata, "1st");
+	tmp2 = g_hash_table_lookup(rdata, "last");
+	g_free(rb->firstandlast);
+	rb->firstandlast = g_strdup_printf("%s%s%s",
+							(tmp1 ? tmp1 : ""),
+							(tmp1 || tmp2 ? " " : ""),
+							(tmp2 ? tmp2 : ""));
+	serv_got_alias(gc, name, rb->firstandlast);
+
+	tmp1 = g_hash_table_lookup(rdata, "aim");
+	if (tmp1 != NULL) {
+		g_free(rb->aim);
+		rb->aim = g_strdup(tmp1);
+	}
+
+	tmp1 = g_hash_table_lookup(rdata, "port.p2pj");
+	rb->p2pjport = atoi(tmp1);
+
+	tmp1 = g_hash_table_lookup(rdata, "status");
+	if (tmp1 != NULL) {
+		if (!strcmp(tmp1, "dnd")) {
+			/* Available */
+			rb->status = 0;
+		} else if (!strcmp(tmp1, "away")) {
+			/* Idle */
+			tmp2 = g_hash_table_lookup(rdata, "away");
+			rb->idle = atoi(tmp2);
+			gaim_debug_error("XXX", "User has been idle for %d\n", rb->idle);
+			rb->status = UC_IDLE;
+		} else if (!strcmp(tmp1, "avail")) {
+			/* Away */
+			rb->status = UC_UNAVAILABLE;
+		}
+		serv_got_update(gc, name, 1, 0, 0, 0, rb->status);
+	}
+
+	tmp1 = g_hash_table_lookup(rdata, "msg");
+	if (tmp1 != NULL) {
+		g_free(rb->msg);
+		rb->msg = g_strdup(tmp1);
+	}
+}
+
+/*
+ * Parse a resource record and do stuff if we need to.
+ */
+static void rendezvous_handle_rr(GaimConnection *gc, ResourceRecord *rr)
+{
+	gchar *name;
+
+	gaim_debug_error("XXX", "Parsing resource record with domain name %s\n", rr->name);
+
+	switch (rr->type) {
+		case RENDEZVOUS_RRTYPE_NULL: {
+			if ((name = rendezvous_extract_name(rr->name)) != NULL) {
+				if (rr->rdlength > 0) {
+					/* Data is a buddy icon */
+					gaim_buddy_icons_set_for_user(gaim_connection_get_account(gc), name, rr->rdata, rr->rdlength);
+				}
+
+				g_free(name);
+			}
+		} break;
+
+		case RENDEZVOUS_RRTYPE_PTR: {
+			gchar *rdata = rr->rdata;
+			if ((name = rendezvous_extract_name(rdata)) != NULL) {
+				if (rr->ttl > 0)
+					rendezvous_addtolocal(gc, name, "Rendezvous");
+				else
+					rendezvous_removefromlocal(gc, name, "Rendezvous");
+
+				g_free(name);
+			}
+		} break;
+
+		case RENDEZVOUS_RRTYPE_TXT: {
+			if ((name = rendezvous_extract_name(rr->name)) != NULL) {
+				rendezvous_handle_rr_txt(gc, rr, name);
+				g_free(name);
+			}
+		} break;
+	}
+}
+
+/****************************/
+/* Icon and Emblem Funtions */
+/****************************/
+static const char* rendezvous_prpl_list_icon(GaimAccount *a, GaimBuddy *b)
+{
+	return "rendezvous";
+}
+
+static void rendezvous_prpl_list_emblems(GaimBuddy *b, char **se, char **sw, char **nw, char **ne)
+{
+	if (GAIM_BUDDY_IS_ONLINE(b)) {
+		if (b->uc & UC_UNAVAILABLE)
+			*se = "away";
+	} else {
+		*se = "offline";
+	}
+}
+
+static gchar *rendezvous_prpl_status_text(GaimBuddy *b)
+{
+	GaimConnection *gc = b->account->gc;
+	RendezvousData *rd = gc->proto_data;
+	RendezvousBuddy *rb;
+	gchar *ret;
+
+	rb = g_hash_table_lookup(rd->buddies, b->name);
+	if ((rb == NULL) || (rb->msg == NULL))
+		return NULL;
+
+	ret = g_strdup(rb->msg);
+
+	return ret;
+}
+
+static gchar *rendezvous_prpl_tooltip_text(GaimBuddy *b)
+{
+	GaimConnection *gc = b->account->gc;
+	RendezvousData *rd = gc->proto_data;
+	RendezvousBuddy *rb;
+	GString *ret;
+
+	rb = g_hash_table_lookup(rd->buddies, b->name);
+	if (rb == NULL)
+		return NULL;
+
+	ret = g_string_new("");
+
+	if (rb->aim != NULL)
+		g_string_append_printf(ret, _("<b>AIM Screen name</b>: %s\n"), rb->aim);
+
+	if (rb->msg != NULL) {
+		if (rb->status == UC_UNAVAILABLE)
+			g_string_append_printf(ret, _("<b>Away Message</b>: %s\n"), rb->msg);
+		else
+			g_string_append_printf(ret, _("<b>Available Message</b>: %s\n"), rb->msg);
+	}
+
+	/* XXX - Fix blist.c so we can prepend the \n's rather than appending them */
+
+	return g_string_free(ret, FALSE);
+}
+
+/****************************/
+/* Connection Funtions      */
+/****************************/
+static void rendezvous_callback(gpointer data, gint source, GaimInputCondition condition)
+{
+	GaimConnection *gc = data;
+	RendezvousData *rd = gc->proto_data;
+	DNSPacket *dns;
+	int i;
+
+	gaim_debug_misc("rendezvous", "Received rendezvous datagram\n");
+
+	dns = mdns_read(rd->fd);
+	if (dns == NULL)
+		return;
+
+	/* Handle the DNS packet */
+	for (i = 0; i < dns->header.numanswers; i++)
+		rendezvous_handle_rr(gc, &dns->answers[i]);
+	for (i = 0; i < dns->header.numauthority; i++)
+		rendezvous_handle_rr(gc, &dns->authority[i]);
+	for (i = 0; i < dns->header.numadditional; i++)
+		rendezvous_handle_rr(gc, &dns->additional[i]);
+
+	mdns_free(dns);
+}
+
+static void rendezvous_prpl_login(GaimAccount *account)
+{
+	GaimConnection *gc = gaim_account_get_connection(account);
+	RendezvousData *rd;
+
+	rd = g_new0(RendezvousData, 1);
+	rd->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, rendezvous_buddy_free);
+
+	gc->proto_data = rd;
+
+	gaim_connection_update_progress(gc, _("Preparing Buddy List"), 0, RENDEZVOUS_CONNECT_STEPS);
+
+	rendezvous_removeallfromlocal(gc);
+
+	gaim_connection_update_progress(gc, _("Connecting"), 1, RENDEZVOUS_CONNECT_STEPS);
+
+	rd->fd = mdns_establish_socket();
+	if (rd->fd == -1) {
+		gaim_connection_error(gc, _("Unable to login to rendezvous"));
+		return;
+	}
+
+	gc->inpa = gaim_input_add(rd->fd, GAIM_INPUT_READ, rendezvous_callback, gc);
+
+	gaim_connection_set_state(gc, GAIM_CONNECTED);
+
+	mdns_query(rd->fd, "_presence._tcp.local");
+
+#if 0
+	text_record_add("txtvers", "1");
+	text_record_add("status", "avail");
+	text_record_add("1st", gaim_account_get_string(account, "first", "Gaim"));
+	text_record_add("AIM", "markdoliner");
+	text_record_add("version", "1");
+	text_record_add("port.p2pj", "5298");
+	text_record_add("last", gaim_account_get_string(account, "last", _("User")));
+
+	publish(account->username, "_presence._tcp", 5298);
+#endif
+}
+
+static void rendezvous_prpl_close(GaimConnection *gc)
+{
+	RendezvousData *rd = (RendezvousData *)gc->proto_data;
+
+	if (gc->inpa)
+		gaim_input_remove(gc->inpa);
+
+	rendezvous_removeallfromlocal(gc);
+
+	if (!rd)
+		return;
+
+	if (rd->fd >= 0)
+		close(rd->fd);
+
+	g_hash_table_destroy(rd->buddies);
+
+	g_free(rd);
+}
+
+static int rendezvous_prpl_send_im(GaimConnection *gc, const char *who, const char *message, GaimConvImFlags flags)
+{
+	gaim_debug_info("rendezvous", "Sending IM\n");
+
+	return 1;
+}
+
+static void rendezvous_prpl_setaway(GaimConnection *gc, const char *state, const char *text)
+{
+	gaim_debug_error("rendezvous", "Set away, state=%s,  text=%s\n", state, text);
+}
+
+static GaimPlugin *my_protocol = NULL;
+
+static GaimPluginProtocolInfo prpl_info =
+{
+	OPT_PROTO_NO_PASSWORD,
+	NULL,
+	NULL,
+	NULL,
+	rendezvous_prpl_list_icon,
+	rendezvous_prpl_list_emblems,
+	rendezvous_prpl_status_text,
+	rendezvous_prpl_tooltip_text,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	rendezvous_prpl_login,
+	rendezvous_prpl_close,
+	rendezvous_prpl_send_im,
+	NULL,
+	NULL,
+	NULL,
+	rendezvous_prpl_setaway,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+static GaimPluginInfo info =
+{
+	2,                                                /**< api_version    */
+	GAIM_PLUGIN_PROTOCOL,                             /**< type           */
+	NULL,                                             /**< ui_requirement */
+	0,                                                /**< flags          */
+	NULL,                                             /**< dependencies   */
+	GAIM_PRIORITY_DEFAULT,                            /**< priority       */
+
+	"prpl-rendezvous",                                /**< id             */
+	"Rendezvous",                                     /**< name           */
+	VERSION,                                          /**< version        */
+	                                                  /**  summary        */
+	N_("Rendezvous Protocol Plugin"),
+	                                                  /**  description    */
+	N_("Rendezvous Protocol Plugin"),
+	NULL,                                             /**< author         */
+	GAIM_WEBSITE,                                     /**< homepage       */
+
+	NULL,                                             /**< load           */
+	NULL,                                             /**< unload         */
+	NULL,                                             /**< destroy        */
+
+	NULL,                                             /**< ui_info        */
+	&prpl_info                                        /**< extra_info     */
+};
+
+static void init_plugin(GaimPlugin *plugin)
+{
+	GaimAccountUserSplit *split;
+	GaimAccountOption *option;
+
+	/* Try to avoid making this configurable... */
+	split = gaim_account_user_split_new(_("Host Name"), "localhost", '@');
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+
+	option = gaim_account_option_string_new(_("First Name"), "first", "Gaim");
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	option = gaim_account_option_string_new(_("Last Name"), "last", _("User"));
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	option = gaim_account_option_bool_new(_("Share AIM screen name"), "shareaim", TRUE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	my_protocol = plugin;
+}
+
+GAIM_INIT_PLUGIN(rendezvous, init_plugin, info);