| 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 } |
|