2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU Library General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
22 #include "mdns_common.h"
23 #include "mdns_interface.h"
29 * Allocate space for the dns-sd data.
31 BonjourDnsSd
* bonjour_dns_sd_new() {
32 BonjourDnsSd
*data
= g_new0(BonjourDnsSd
, 1);
37 * Deallocate the space of the dns-sd data.
39 void bonjour_dns_sd_free(BonjourDnsSd
*data
) {
49 #define MAX_TXT_CONSTITUENT_LEN 255
51 /* Make sure that the value isn't longer than it is supposed to be */
53 get_max_txt_record_value(const char *key
, const char *value
)
55 /* "each constituent string of a DNS TXT record is limited to 255 bytes"
56 * This includes the key and the '='
58 static char buffer
[MAX_TXT_CONSTITUENT_LEN
+ 1];
59 gchar
*end_valid
= NULL
;
60 int len
= MIN(strlen(value
), MAX_TXT_CONSTITUENT_LEN
- (strlen(key
) + 2));
62 strncpy(buffer
, value
, len
);
66 /* If we've cut part of a utf-8 character, kill it */
67 if (!g_utf8_validate(buffer
, -1, (const gchar
**)&end_valid
))
73 static inline GSList
*
74 _add_txt_record(GSList
*list
, const gchar
*key
, const gchar
*value
)
76 PurpleKeyValuePair
*kvp
= g_new0(PurpleKeyValuePair
, 1);
77 kvp
->key
= g_strdup(key
);
78 kvp
->value
= g_strdup(get_max_txt_record_value(key
, value
));
79 return g_slist_prepend(list
, kvp
);
82 static GSList
*generate_presence_txt_records(BonjourDnsSd
*data
) {
85 const char *jid
, *aim
, *email
;
87 /* Convert the port to a string */
88 snprintf(portstring
, sizeof(portstring
), "%d", data
->port_p2pj
);
90 jid
= purple_account_get_string(data
->account
, "jid", NULL
);
91 aim
= purple_account_get_string(data
->account
, "AIM", NULL
);
92 email
= purple_account_get_string(data
->account
, "email", NULL
);
94 /* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
95 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
98 /* Large TXT records are problematic.
99 * While it is technically possible for this to exceed a standard 512-byte
100 * DNS message, it shouldn't happen unless we get wacky data entered for
101 * some of the freeform fields. It is even less likely to exceed the
102 * recommended maximum of 1300 bytes.
105 /* Needed by iChat */
106 ret
= _add_txt_record(ret
, "txtvers", "1");
107 /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
108 ret
= _add_txt_record(ret
, "1st", data
->first
);
109 /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
110 ret
= _add_txt_record(ret
, "last", data
->last
);
111 /* Needed by Adium */
112 ret
= _add_txt_record(ret
, "port.p2pj", portstring
);
113 /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
114 ret
= _add_txt_record(ret
, "status", data
->status
);
115 ret
= _add_txt_record(ret
, "node", "libpurple");
116 ret
= _add_txt_record(ret
, "ver", VERSION
);
117 /* Currently always set to "!" since we don't support AV and wont ever be in a conference */
118 ret
= _add_txt_record(ret
, "vc", data
->vc
);
119 if (email
!= NULL
&& *email
!= '\0') {
120 ret
= _add_txt_record(ret
, "email", email
);
122 if (jid
!= NULL
&& *jid
!= '\0') {
123 ret
= _add_txt_record(ret
, "jid", jid
);
125 /* Nonstandard, but used by iChat */
126 if (aim
!= NULL
&& *aim
!= '\0') {
127 ret
= _add_txt_record(ret
, "AIM", aim
);
129 if (data
->msg
!= NULL
&& *data
->msg
!= '\0') {
130 ret
= _add_txt_record(ret
, "msg", data
->msg
);
132 if (data
->phsh
!= NULL
&& *data
->phsh
!= '\0') {
133 ret
= _add_txt_record(ret
, "phsh", data
->phsh
);
136 /* TODO: ext, nick */
140 static void free_presence_txt_records(GSList
*lst
) {
141 PurpleKeyValuePair
*kvp
;
147 lst
= g_slist_delete_link(lst
, lst
);
151 static gboolean
publish_presence(BonjourDnsSd
*data
, PublishType type
) {
155 txt_records
= generate_presence_txt_records(data
);
156 ret
= _mdns_publish(data
, type
, txt_records
);
157 free_presence_txt_records(txt_records
);
163 * Send a new dns-sd packet updating our status.
165 void bonjour_dns_sd_send_status(BonjourDnsSd
*data
, const char *status
, const char *status_message
) {
166 g_free(data
->status
);
169 data
->status
= g_strdup(status
);
170 data
->msg
= g_strdup(status_message
);
172 /* Update our text record with the new status */
173 publish_presence(data
, PUBLISH_UPDATE
);
177 * Retrieve the buddy icon blob
179 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy
* buddy
) {
180 _mdns_retrieve_buddy_icon(buddy
);
183 void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd
*data
) {
186 if ((img
= purple_buddy_icons_find_account_icon(data
->account
))) {
187 gconstpointer avatar_data
;
190 avatar_data
= purple_image_get_data(img
);
191 avatar_len
= purple_image_get_data_size(img
);
193 if (_mdns_set_buddy_icon_data(data
, avatar_data
, avatar_len
)) {
197 data
->phsh
= g_compute_checksum_for_data(
198 G_CHECKSUM_SHA1
, avatar_data
, avatar_len
);
200 /* Update our TXT record */
201 publish_presence(data
, PUBLISH_UPDATE
);
206 /* We need to do this regardless of whether data->phsh is set so that we
207 * cancel any icons that are currently in the process of being set */
208 _mdns_set_buddy_icon_data(data
, NULL
, 0);
209 if (data
->phsh
!= NULL
) {
210 /* Clear the buddy icon */
213 /* Update our TXT record */
214 publish_presence(data
, PUBLISH_UPDATE
);
220 * Advertise our presence within the dns-sd daemon and start browsing
221 * for other bonjour peers.
223 gboolean
bonjour_dns_sd_start(BonjourDnsSd
*data
) {
225 /* Initialize the dns-sd data and session */
226 if (!_mdns_init_session(data
))
229 /* Publish our bonjour IM client at the mDNS daemon */
230 if (!publish_presence(data
, PUBLISH_START
))
233 /* Advise the daemon that we are waiting for connections */
234 if (!_mdns_browse(data
)) {
235 purple_debug_error("bonjour", "Unable to get service.\n");
243 * Unregister the "_presence._tcp" service at the mDNS daemon.
246 void bonjour_dns_sd_stop(BonjourDnsSd
*data
) {
251 bonjour_dns_sd_set_jid(PurpleAccount
*account
, const char *hostname
)
253 PurpleConnection
*conn
= purple_account_get_connection(account
);
254 BonjourData
*bd
= purple_connection_get_protocol_data(conn
);
255 const char *tmp
, *account_name
= purple_account_get_username(account
);
257 /* Previously we allowed the hostname part of the jid to be set
258 * explicitly when it should always be the current hostname.
259 * That is what this is intended to deal with.
261 if ((tmp
= strchr(account_name
, '@'))
262 && strstr(tmp
, hostname
) == (tmp
+ 1)
263 && *((tmp
+ 1) + strlen(hostname
)) == '\0')
264 bd
->jid
= g_strdup(account_name
);
267 GString
*str
= g_string_new("");
268 /* Escape an '@' in the account name */
270 while ((tmp2
= strchr(tmp
, '@')) != NULL
) {
271 g_string_append_len(str
, tmp
, tmp2
- tmp
);
272 g_string_append(str
, "\\40");
275 g_string_append(str
, tmp
);
276 g_string_append_c(str
, '@');
277 g_string_append(str
, hostname
);
279 bd
->jid
= g_string_free(str
, FALSE
);