# HG changeset patch # User Mark Doliner # Date 1080013146 0 # Node ID 538f4d0faf1dcac58d905203990d3d9f9f10a827 # Parent db4a125edd7bbc18bca9469febe9240343498722 [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 diff -r db4a125edd7b -r 538f4d0faf1d src/protocols/napster/Makefile.am --- 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) \ diff -r db4a125edd7b -r 538f4d0faf1d src/protocols/napster/Makefile.mingw --- 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) ## diff -r db4a125edd7b -r 538f4d0faf1d src/protocols/napster/mdns.c --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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); +} diff -r db4a125edd7b -r 538f4d0faf1d src/protocols/napster/mdns.h --- /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 +#include +#include +#include +#include +#include + +#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_ */ diff -r db4a125edd7b -r 538f4d0faf1d src/protocols/napster/rendezvous.c --- /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 +#include + +#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, _("AIM Screen name: %s\n"), rb->aim); + + if (rb->msg != NULL) { + if (rb->status == UC_UNAVAILABLE) + g_string_append_printf(ret, _("Away Message: %s\n"), rb->msg); + else + g_string_append_printf(ret, _("Available Message: %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);