libpurple/protocols/bonjour/mdns_common.c

changeset 42670
a3b862b8dcde
parent 42669
6d41b29637ef
child 42671
68cc8544b438
equal deleted inserted replaced
42669:6d41b29637ef 42670:a3b862b8dcde
1 /*
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.
6 *
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.
11 *
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.
15 */
16
17 #include <string.h>
18
19 #include <purple.h>
20
21 #include "mdns_common.h"
22 #include "bonjour.h"
23 #include "buddy.h"
24
25
26 /****************************
27 * mdns_interface functions *
28 ****************************/
29
30 mdns_init_session_func *_mdns_init_session = NULL;
31 mdns_publish_func *_mdns_publish = NULL;
32 mdns_browse_func *_mdns_browse = NULL;
33 mdns_stop_func *_mdns_stop = NULL;
34 mdns_set_buddy_icon_data_func *_mdns_set_buddy_icon_data = NULL;
35 mdns_init_buddy_func *_mdns_init_buddy = NULL;
36 mdns_delete_buddy_func *_mdns_delete_buddy = NULL;
37 mdns_retrieve_buddy_icon_func *_mdns_retrieve_buddy_icon = NULL;
38
39 /**
40 * Allocate space for the dns-sd data.
41 */
42 BonjourDnsSd *
43 bonjour_dns_sd_new(void) {
44 BonjourDnsSd *data = g_new0(BonjourDnsSd, 1);
45 return data;
46 }
47
48 /**
49 * Deallocate the space of the dns-sd data.
50 */
51 void bonjour_dns_sd_free(BonjourDnsSd *data) {
52 g_free(data->first);
53 g_free(data->last);
54 g_free(data->phsh);
55 g_free(data->status);
56 g_free(data->vc);
57 g_free(data->msg);
58 g_free(data);
59 }
60
61 #define MAX_TXT_CONSTITUENT_LEN 255
62
63 /* Make sure that the value isn't longer than it is supposed to be */
64 static const char*
65 get_max_txt_record_value(const char *key, const char *value)
66 {
67 /* "each constituent string of a DNS TXT record is limited to 255 bytes"
68 * This includes the key and the '='
69 */
70 static char buffer[MAX_TXT_CONSTITUENT_LEN + 1];
71 gchar *end_valid = NULL;
72 int len = MIN(strlen(value), MAX_TXT_CONSTITUENT_LEN - (strlen(key) + 2));
73
74 strncpy(buffer, value, len);
75
76 buffer[len] = '\0';
77
78 /* If we've cut part of a utf-8 character, kill it */
79 if (!g_utf8_validate(buffer, -1, (const gchar **)&end_valid))
80 *end_valid = '\0';
81
82 return buffer;
83 }
84
85 static inline GSList *
86 _add_txt_record(GSList *list, const gchar *key, const gchar *value)
87 {
88 const char *max_value = get_max_txt_record_value(key, value);
89 PurpleKeyValuePair *kvp = purple_key_value_pair_new_full(key, g_strdup(max_value), g_free);
90 return g_slist_prepend(list, kvp);
91 }
92
93 static GSList *generate_presence_txt_records(BonjourDnsSd *data) {
94 GSList *ret = NULL;
95 char portstring[6];
96 const char *jid, *aim, *email;
97
98 /* Convert the port to a string */
99 g_snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
100
101 jid = purple_account_get_string(data->account, "jid", NULL);
102 aim = purple_account_get_string(data->account, "AIM", NULL);
103 email = purple_account_get_string(data->account, "email", NULL);
104
105 /* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
106 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
107 */
108
109 /* Large TXT records are problematic.
110 * While it is technically possible for this to exceed a standard 512-byte
111 * DNS message, it shouldn't happen unless we get wacky data entered for
112 * some of the freeform fields. It is even less likely to exceed the
113 * recommended maximum of 1300 bytes.
114 */
115
116 /* Needed by iChat */
117 ret = _add_txt_record(ret, "txtvers", "1");
118 /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
119 ret = _add_txt_record(ret, "1st", data->first);
120 /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
121 ret = _add_txt_record(ret, "last", data->last);
122 /* Needed by Adium */
123 ret = _add_txt_record(ret, "port.p2pj", portstring);
124 /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
125 ret = _add_txt_record(ret, "status", data->status);
126 ret = _add_txt_record(ret, "node", "libpurple");
127 ret = _add_txt_record(ret, "ver", VERSION);
128 /* Currently always set to "!" since we don't support AV and won't ever be in a conference */
129 ret = _add_txt_record(ret, "vc", data->vc);
130 if (email != NULL && *email != '\0') {
131 ret = _add_txt_record(ret, "email", email);
132 }
133 if (jid != NULL && *jid != '\0') {
134 ret = _add_txt_record(ret, "jid", jid);
135 }
136 /* Nonstandard, but used by iChat */
137 if (aim != NULL && *aim != '\0') {
138 ret = _add_txt_record(ret, "AIM", aim);
139 }
140 if (data->msg != NULL && *data->msg != '\0') {
141 ret = _add_txt_record(ret, "msg", data->msg);
142 }
143 if (data->phsh != NULL && *data->phsh != '\0') {
144 ret = _add_txt_record(ret, "phsh", data->phsh);
145 }
146
147 /* TODO: ext, nick */
148 return ret;
149 }
150
151 static gboolean publish_presence(BonjourDnsSd *data, PublishType type) {
152 GSList *txt_records;
153 gboolean ret;
154
155 txt_records = generate_presence_txt_records(data);
156 ret = _mdns_publish(data, type, txt_records);
157 g_slist_free_full(txt_records, (GDestroyNotify)purple_key_value_pair_free);
158
159 return ret;
160 }
161
162 /**
163 * Send a new dns-sd packet updating our status.
164 */
165 void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) {
166 g_free(data->status);
167 g_free(data->msg);
168
169 data->status = g_strdup(status);
170 data->msg = g_strdup(status_message);
171
172 /* Update our text record with the new status */
173 publish_presence(data, PUBLISH_UPDATE);
174 }
175
176 /**
177 * Retrieve the buddy icon blob
178 */
179 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
180 _mdns_retrieve_buddy_icon(buddy);
181 }
182
183 void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data) {
184 PurpleImage *img;
185
186 if ((img = purple_buddy_icons_find_account_icon(data->account))) {
187 gconstpointer avatar_data;
188 gsize avatar_len;
189
190 avatar_data = purple_image_get_data(img);
191 avatar_len = purple_image_get_data_size(img);
192
193 if (_mdns_set_buddy_icon_data(data, avatar_data, avatar_len)) {
194 g_free(data->phsh);
195 data->phsh = NULL;
196
197 data->phsh = g_compute_checksum_for_data(
198 G_CHECKSUM_SHA1, avatar_data, avatar_len);
199
200 /* Update our TXT record */
201 publish_presence(data, PUBLISH_UPDATE);
202 }
203
204 g_object_unref(img);
205 } else {
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 */
211 g_free(data->phsh);
212 data->phsh = NULL;
213 /* Update our TXT record */
214 publish_presence(data, PUBLISH_UPDATE);
215 }
216 }
217 }
218
219 /**
220 * Advertise our presence within the dns-sd daemon and start browsing
221 * for other bonjour peers.
222 */
223 gboolean bonjour_dns_sd_start(BonjourDnsSd *data) {
224
225 /* Initialize the dns-sd data and session */
226 if (!_mdns_init_session(data))
227 return FALSE;
228
229 /* Publish our bonjour IM client at the mDNS daemon */
230 if (!publish_presence(data, PUBLISH_START))
231 return FALSE;
232
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");
236 return FALSE;
237 }
238
239 return TRUE;
240 }
241
242 /**
243 * Unregister the "_presence._tcp" service at the mDNS daemon.
244 */
245
246 void bonjour_dns_sd_stop(BonjourDnsSd *data) {
247 _mdns_stop(data);
248 }
249
250 void
251 bonjour_dns_sd_set_jid(PurpleAccount *account, const char *hostname)
252 {
253 PurpleConnection *conn = purple_account_get_connection(account);
254 PurpleContactInfo *info = PURPLE_CONTACT_INFO(account);
255 BonjourData *bd = purple_connection_get_protocol_data(conn);
256 const char *tmp, *account_name = purple_contact_info_get_username(info);
257
258 /* Previously we allowed the hostname part of the jid to be set
259 * explicitly when it should always be the current hostname.
260 * That is what this is intended to deal with.
261 */
262 if ((tmp = strchr(account_name, '@'))
263 && strstr(tmp, hostname) == (tmp + 1)
264 && *((tmp + 1) + strlen(hostname)) == '\0')
265 bd->jid = g_strdup(account_name);
266 else {
267 const char *tmp2;
268 GString *str = g_string_new("");
269 /* Escape an '@' in the account name */
270 tmp = account_name;
271 while ((tmp2 = strchr(tmp, '@')) != NULL) {
272 g_string_append_len(str, tmp, tmp2 - tmp);
273 g_string_append(str, "\\40");
274 tmp = tmp2 + 1;
275 }
276 g_string_append(str, tmp);
277 g_string_append_c(str, '@');
278 g_string_append(str, hostname);
279
280 bd->jid = g_string_free(str, FALSE);
281 }
282 }

mercurial