# HG changeset patch # User Mark Doliner # Date 1080013513 0 # Node ID c3ffec7fab9482b8f1c87d9401f18c1b50486fca # Parent 538f4d0faf1dcac58d905203990d3d9f9f10a827 [gaim-migrate @ 9222] Apparently if you don't cvs add a directory before you cvs add files in that directory, cvs just puts the files in the napster directory. ... Seems logical enough to me. committer: Tailor Script diff -r 538f4d0faf1d -r c3ffec7fab94 src/protocols/napster/mdns.c --- a/src/protocols/napster/mdns.c Tue Mar 23 03:39:06 2004 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,541 +0,0 @@ -/** - * @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 538f4d0faf1d -r c3ffec7fab94 src/protocols/napster/mdns.h --- a/src/protocols/napster/mdns.h Tue Mar 23 03:39:06 2004 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -/** - * @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 538f4d0faf1d -r c3ffec7fab94 src/protocols/napster/rendezvous.c --- a/src/protocols/napster/rendezvous.c Tue Mar 23 03:39:06 2004 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,530 +0,0 @@ -/* - * 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); diff -r 538f4d0faf1d -r c3ffec7fab94 src/protocols/rendezvous/mdns.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/rendezvous/mdns.c Tue Mar 23 03:45:13 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 538f4d0faf1d -r c3ffec7fab94 src/protocols/rendezvous/mdns.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/rendezvous/mdns.h Tue Mar 23 03:45:13 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 538f4d0faf1d -r c3ffec7fab94 src/protocols/rendezvous/rendezvous.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/rendezvous/rendezvous.c Tue Mar 23 03:45:13 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);