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